1use crate::declarations::{node_text as cpp_node_text, normalize_cpp_whitespace};
9use brokk_bifrost_core::analyzer::CodeUnit;
10use tree_sitter::Node;
11
12#[derive(Clone, PartialEq, Eq, Hash)]
13pub struct CppArgType {
14 pub name: String,
15 pub unit: Option<CodeUnit>,
16 pub indirection: i32,
17 pub pointee_const: bool,
18}
19
20pub fn cpp_signature_param_types(signature: &str) -> Option<Vec<String>> {
21 let inner = cpp_signature_parameter_text(signature)
22 .unwrap_or(signature)
23 .trim();
24 if inner.is_empty() || inner == "void" {
25 return Some(Vec::new());
26 }
27 Some(
28 cpp_split_top_level_commas(inner)
29 .map(cpp_parameter_type_text)
30 .collect(),
31 )
32}
33
34pub fn cpp_parameter_type_text(parameter: &str) -> String {
35 let mut text = parameter
36 .split_once('=')
37 .map(|(before, _)| before)
38 .unwrap_or(parameter)
39 .trim()
40 .trim_end_matches(';')
41 .trim();
42 let pointer_depth = cpp_type_text_pointer_depth(text);
43 if let Some((before, last)) = text.rsplit_once(char::is_whitespace)
44 && cpp_parameter_name_token(last)
45 {
46 text = before.trim();
47 }
48 let pointee_const = pointer_depth > 0 && cpp_type_text_pointee_is_const(text);
49 format!(
50 "{}{}{}",
51 if pointee_const { "const " } else { "" },
52 normalize_cpp_type_name(text),
53 "*".repeat(pointer_depth as usize)
54 )
55}
56
57pub fn normalize_cpp_type_name(text: &str) -> String {
58 let normalized = normalize_cpp_whitespace(text);
59 let base = cpp_type_text_base(&normalized)
60 .trim_start_matches("const ")
61 .trim();
62 strip_tag_type_prefix(base.strip_suffix(" const").unwrap_or(base)).to_string()
63}
64
65pub fn cpp_type_text_pointer_depth(text: &str) -> i32 {
66 cpp_type_text_shape(text).1
67}
68
69fn cpp_type_text_shape(text: &str) -> (usize, i32) {
70 let mut depth = 0i32;
71 let mut nesting = 0i32;
72 let mut base_end = text.len();
73 for (offset, ch) in text.char_indices() {
74 match ch {
75 '<' | '(' | '[' => nesting += 1,
76 '>' | ')' | ']' => nesting -= 1,
77 '*' if nesting <= 0 => {
78 base_end = base_end.min(offset);
79 depth += 1;
80 }
81 '&' if nesting <= 0 => base_end = base_end.min(offset),
82 _ => {}
83 }
84 }
85 (base_end, depth)
86}
87
88pub fn cpp_forwarding_call_argument<'tree>(call: Node<'tree>, source: &str) -> Option<Node<'tree>> {
106 if call.kind() != "call_expression" {
107 return None;
108 }
109 let mut callee = call.child_by_field_name("function")?;
110 if callee.kind() == "template_function" {
111 callee = callee.child_by_field_name("name")?;
112 }
113 if callee.kind() != "qualified_identifier" {
114 return None;
115 }
116 let scope = callee.child_by_field_name("scope")?;
117 if !matches!(scope.kind(), "namespace_identifier" | "identifier")
118 || cpp_node_text(scope, source).trim() != "std"
119 {
120 return None;
121 }
122 let mut name = callee.child_by_field_name("name")?;
123 if name.kind() == "template_function" {
127 name = name.child_by_field_name("name")?;
128 }
129 if !matches!(name.kind(), "identifier" | "field_identifier")
130 || !matches!(cpp_node_text(name, source).trim(), "move" | "forward")
131 {
132 return None;
133 }
134 let arguments = call.child_by_field_name("arguments")?;
135 let mut cursor = arguments.walk();
136 let forwarded = arguments
137 .named_children(&mut cursor)
138 .filter(|argument| argument.kind() != "comment")
139 .collect::<Vec<_>>();
140 let [argument] = forwarded.as_slice() else {
141 return None;
142 };
143 Some(*argument)
144}
145
146pub fn cpp_literal_arg_type(node: Node<'_>, source: &str) -> Option<CppArgType> {
147 let scalar = |name: &str| CppArgType {
148 name: name.to_string(),
149 unit: None,
150 indirection: 0,
151 pointee_const: false,
152 };
153 match node.kind() {
154 "number_literal" => {
155 let text = cpp_node_text(node, source);
156 if cpp_number_literal_is_float(text) {
157 Some(scalar("double"))
158 } else {
159 Some(scalar("int"))
160 }
161 }
162 "true" | "false" => Some(scalar("bool")),
163 "char_literal" => Some(scalar("char")),
164 "string_literal" => {
165 let text = cpp_node_text(node, source).trim_start();
166 (text.starts_with('"') || text.starts_with("R\"")).then(|| CppArgType {
167 name: "char".to_string(),
168 unit: None,
169 indirection: 1,
170 pointee_const: true,
171 })
172 }
173 "unary_expression" => {
174 let operator = node.child_by_field_name("operator")?;
175 let inner = node
176 .child_by_field_name("argument")
177 .or_else(|| node.named_child(0))?;
178 matches!(operator.kind(), "+" | "-")
179 .then(|| cpp_literal_arg_type(inner, source))
180 .flatten()
181 }
182 _ => None,
183 }
184}
185
186pub fn cpp_filter_candidates_by_args(
187 candidates: Vec<CodeUnit>,
188 arg_types: &[Option<CppArgType>],
189 resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
190 assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
191) -> Vec<CodeUnit> {
192 cpp_filter_candidates_by_args_with_parameter_types(
193 candidates,
194 arg_types,
195 &|candidate| cpp_signature_param_types(candidate.signature().unwrap_or_default()),
196 resolve_type,
197 assignable,
198 )
199}
200
201pub fn cpp_filter_candidates_by_args_with_parameter_types(
202 candidates: Vec<CodeUnit>,
203 arg_types: &[Option<CppArgType>],
204 parameter_types: &dyn Fn(&CodeUnit) -> Option<Vec<String>>,
205 resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
206 assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
207) -> Vec<CodeUnit> {
208 if candidates.len() <= 1 || arg_types.iter().any(Option::is_none) {
209 return candidates;
210 }
211 let args: Vec<&CppArgType> = arg_types.iter().flatten().collect();
212 debug_assert_eq!(args.len(), arg_types.len());
213
214 let exact = cpp_candidates_matching(
220 &candidates,
221 &args,
222 parameter_types,
223 &|param, arg, template_candidate| {
224 cpp_param_matches_arg(param, arg, template_candidate, resolve_type, assignable)
225 },
226 );
227 let filtered = if exact.is_empty() {
228 cpp_candidates_matching(
229 &candidates,
230 &args,
231 parameter_types,
232 &|param, arg, template_candidate| {
233 cpp_param_matches_arg(param, arg, template_candidate, resolve_type, assignable)
234 || cpp_standard_conversion_applies(arg, param)
235 },
236 )
237 } else {
238 exact
239 };
240 if filtered.is_empty() {
241 candidates
242 } else if filtered.iter().any(cpp_signature_is_template_candidate) {
243 candidates
248 } else {
249 filtered
250 }
251}
252
253fn cpp_candidates_matching(
256 candidates: &[CodeUnit],
257 args: &[&CppArgType],
258 parameter_types: &dyn Fn(&CodeUnit) -> Option<Vec<String>>,
259 matches: &dyn Fn(&str, &CppArgType, bool) -> bool,
260) -> Vec<CodeUnit> {
261 candidates
262 .iter()
263 .filter(|candidate| {
264 let template_candidate = cpp_signature_is_template_candidate(candidate);
265 parameter_types(candidate).is_some_and(|params| {
266 params.len() == args.len()
267 && params
268 .iter()
269 .zip(args.iter())
270 .all(|(param, arg)| matches(param, arg, template_candidate))
271 })
272 })
273 .cloned()
274 .collect()
275}
276
277fn cpp_signature_is_template_candidate(candidate: &CodeUnit) -> bool {
278 candidate
279 .signature()
280 .is_some_and(|signature| signature.trim_start().starts_with('<'))
281}
282
283fn cpp_param_matches_arg(
284 param: &str,
285 arg: &CppArgType,
286 template_candidate: bool,
287 resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
288 assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
289) -> bool {
290 if cpp_type_text_pointer_depth(param) != arg.indirection {
291 return false;
292 }
293 if arg.pointee_const && !cpp_type_text_pointee_is_const(param) {
294 return false;
295 }
296 if template_candidate {
302 return true;
303 }
304 let param_name = normalize_cpp_type_name(param);
305 match (resolve_type(¶m_name), arg.unit.as_ref()) {
306 (Some(param_unit), Some(arg_unit)) => assignable(arg_unit, ¶m_unit),
307 _ => param_name == arg.name,
308 }
309}
310
311fn cpp_standard_conversion_applies(arg: &CppArgType, param: &str) -> bool {
343 if cpp_type_text_pointer_depth(param) != 0 {
344 return false;
345 }
346 let param_name = normalize_cpp_type_name(param);
347 let span_element = cpp_template_arguments(¶m_name, "std::span")
348 .and_then(|arguments| arguments.first().copied());
349 if let Some(span_element) = span_element {
350 return arg.indirection == 0
351 && cpp_contiguous_range_element(&arg.name)
352 .is_some_and(|element| cpp_span_element_accepts(span_element, element));
353 }
354 if param_name == "std::string_view" {
355 return (arg.indirection == 0 && arg.name == "std::string")
356 || (arg.indirection == 1 && arg.name == "char");
357 }
358 false
359}
360
361fn cpp_template_arguments<'a>(text: &'a str, base: &str) -> Option<Vec<&'a str>> {
368 let inner = text
369 .strip_prefix(base)?
370 .trim_start()
371 .strip_prefix('<')?
372 .trim_end()
373 .strip_suffix('>')?;
374 Some(cpp_split_top_level_commas(inner).collect())
375}
376
377fn cpp_contiguous_range_element(name: &str) -> Option<&str> {
380 ["std::vector", "std::array"]
381 .into_iter()
382 .find_map(|base| cpp_template_arguments(name, base))
383 .and_then(|arguments| arguments.first().copied())
384}
385
386fn cpp_span_element_accepts(param_element: &str, arg_element: &str) -> bool {
389 cpp_type_text_pointer_depth(param_element) == cpp_type_text_pointer_depth(arg_element)
390 && normalize_cpp_type_name(param_element) == normalize_cpp_type_name(arg_element)
391 && (cpp_type_text_pointee_is_const(param_element)
392 || !cpp_type_text_pointee_is_const(arg_element))
393}
394
395fn cpp_type_text_pointee_is_const(text: &str) -> bool {
396 let normalized = normalize_cpp_whitespace(text);
397 let base = cpp_type_text_base(&normalized).trim();
398 base.starts_with("const ") || base.ends_with(" const")
399}
400
401fn cpp_type_text_base(text: &str) -> &str {
402 text[..cpp_type_text_shape(text).0].trim()
403}
404
405pub fn cpp_split_top_level_commas(value: &str) -> impl Iterator<Item = &str> {
406 struct TopLevelCommaSplit<'a> {
407 value: &'a str,
408 start: usize,
409 angle: usize,
410 paren: usize,
411 brace: usize,
412 bracket: usize,
413 }
414
415 impl<'a> Iterator for TopLevelCommaSplit<'a> {
416 type Item = &'a str;
417
418 fn next(&mut self) -> Option<Self::Item> {
419 if self.start > self.value.len() {
420 return None;
421 }
422 for (offset, ch) in self.value[self.start..].char_indices() {
423 let absolute = self.start + offset;
424 match ch {
425 '<' => self.angle += 1,
426 '>' => self.angle = self.angle.saturating_sub(1),
427 '(' => self.paren += 1,
428 ')' => self.paren = self.paren.saturating_sub(1),
429 '{' => self.brace += 1,
430 '}' => self.brace = self.brace.saturating_sub(1),
431 '[' => self.bracket += 1,
432 ']' => self.bracket = self.bracket.saturating_sub(1),
433 ',' if self.angle == 0
434 && self.paren == 0
435 && self.brace == 0
436 && self.bracket == 0 =>
437 {
438 let item = self.value[self.start..absolute].trim();
439 self.start = absolute + ch.len_utf8();
440 return Some(item);
441 }
442 _ => {}
443 }
444 }
445 let item = self.value[self.start..].trim();
446 self.start = self.value.len() + 1;
447 Some(item)
448 }
449 }
450
451 TopLevelCommaSplit {
452 value,
453 start: 0,
454 angle: 0,
455 paren: 0,
456 brace: 0,
457 bracket: 0,
458 }
459 .filter(|item| !item.is_empty())
460}
461
462fn cpp_signature_parameter_span(signature: &str) -> Option<(usize, usize)> {
464 let open = signature.find('(')?;
465 let mut depth = 0i32;
466 for (offset, ch) in signature[open..].char_indices() {
467 match ch {
468 '(' => depth += 1,
469 ')' => {
470 depth -= 1;
471 if depth == 0 {
472 return Some((open, open + offset));
473 }
474 }
475 _ => {}
476 }
477 }
478 None
479}
480
481fn cpp_signature_parameter_text(signature: &str) -> Option<&str> {
482 let (open, close) = cpp_signature_parameter_span(signature)?;
483 Some(signature[open + 1..close].trim())
484}
485
486pub fn cpp_signature_trailing_qualifiers(signature: &str) -> &str {
495 match cpp_signature_parameter_span(signature) {
496 Some((_, close)) => signature[close + 1..].trim(),
497 None => "",
498 }
499}
500
501fn cpp_parameter_name_token(token: &str) -> bool {
502 let token = token.trim_start_matches('*').trim_start_matches('&').trim();
503 token
504 .chars()
505 .next()
506 .is_some_and(|ch| ch == '_' || ch.is_ascii_lowercase())
507 && token
508 .chars()
509 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
510}
511
512fn strip_tag_type_prefix(value: &str) -> &str {
513 let value = value.trim_start_matches("const ");
514 value
515 .strip_prefix("struct ")
516 .or_else(|| value.strip_prefix("class "))
517 .or_else(|| value.strip_prefix("enum "))
518 .unwrap_or(value)
519 .trim()
520}
521
522fn cpp_number_literal_is_float(text: &str) -> bool {
523 let text = text.trim();
524 text.contains('.') || text.contains('e') || text.contains('E') || text.ends_with(['f', 'F'])
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530 use brokk_bifrost_core::analyzer::ProjectFile;
531 use brokk_bifrost_core::analyzer::model::CodeUnitType;
532
533 fn test_file() -> ProjectFile {
534 ProjectFile::new(std::env::temp_dir(), "test.cpp")
535 }
536
537 fn function(name: &str, signature: &str) -> CodeUnit {
538 CodeUnit::with_signature(
539 test_file(),
540 CodeUnitType::Function,
541 "ns",
542 name,
543 Some(signature.to_string()),
544 false,
545 )
546 }
547
548 fn class(name: &str) -> CodeUnit {
549 CodeUnit::new(test_file(), CodeUnitType::Class, "ns", name)
550 }
551
552 #[test]
553 fn cpp_filter_candidates_matches_named_unindexed_types() {
554 let candidates = vec![
555 function("format", "std::string format(const std::string& value)"),
556 function("format", "std::string format(int value)"),
557 ];
558 let filtered = cpp_filter_candidates_by_args(
559 candidates,
560 &[Some(CppArgType {
561 name: "std::string".to_string(),
562 unit: None,
563 indirection: 0,
564 pointee_const: false,
565 })],
566 &|_| None,
567 &|_, _| false,
568 );
569 assert_eq!(1, filtered.len());
570 assert!(filtered[0].signature().unwrap().contains("std::string&"));
571 }
572
573 #[test]
574 fn cpp_filter_candidates_matches_assignable_units() {
575 let arg = class("Arg");
576 let param = class("Param");
577 let filtered = cpp_filter_candidates_by_args(
578 vec![function("take", "void take(Param value)")],
579 &[Some(CppArgType {
580 name: "Arg".to_string(),
581 unit: Some(arg.clone()),
582 indirection: 0,
583 pointee_const: false,
584 })],
585 &|name| (name == "Param").then(|| param.clone()),
586 &|from, to| from == &arg && to == ¶m,
587 );
588 assert_eq!(1, filtered.len());
589 }
590
591 #[test]
592 fn cpp_filter_candidates_rejects_pointer_depth_mismatch() {
593 let candidates = vec![
594 function("take", "void take(int* value)"),
595 function("take", "void take(int value)"),
596 ];
597 let filtered = cpp_filter_candidates_by_args(
598 candidates,
599 &[Some(CppArgType {
600 name: "int".to_string(),
601 unit: None,
602 indirection: 0,
603 pointee_const: false,
604 })],
605 &|_| None,
606 &|_, _| false,
607 );
608 assert_eq!(1, filtered.len());
609 assert_eq!("void take(int value)", filtered[0].signature().unwrap());
610 }
611
612 #[test]
613 fn cpp_filter_candidates_uses_const_string_literal_pointer_evidence() {
614 let literal = Some(CppArgType {
615 name: "char".to_string(),
616 unit: None,
617 indirection: 1,
618 pointee_const: true,
619 });
620 let direct = cpp_filter_candidates_by_args(
621 vec![
622 function("select", "int select(int value)"),
623 function("select", "int select(const char* value)"),
624 ],
625 std::slice::from_ref(&literal),
626 &|_| None,
627 &|_, _| false,
628 );
629 assert_eq!(1, direct.len());
630 assert_eq!(
631 "int select(const char* value)",
632 direct[0].signature().unwrap()
633 );
634
635 for candidates in [
636 vec![
637 function("select", "int select(int value)"),
638 function("select", "int select(char* value)"),
639 ],
640 vec![
641 function("format", "int format(int value)"),
642 function("format", "int format(std::string value)"),
643 ],
644 ] {
645 let filtered = cpp_filter_candidates_by_args(
646 candidates.clone(),
647 std::slice::from_ref(&literal),
648 &|_| None,
649 &|_, _| false,
650 );
651 assert_eq!(
652 candidates, filtered,
653 "unmodeled or invalid conversions must remain conservative"
654 );
655 }
656 }
657
658 #[test]
659 fn cpp_parameter_type_keeps_pointer_const_distinct_from_pointee_const() {
660 assert_eq!("char*", cpp_parameter_type_text("char * const value"));
661 assert_eq!(
662 "const char*",
663 cpp_parameter_type_text("const char * const value")
664 );
665 assert_eq!("char", normalize_cpp_type_name("char * const"));
666 }
667
668 #[test]
669 fn cpp_filter_candidates_keeps_all_for_unknown_arguments() {
670 let candidates = vec![
671 function("format", "void format(std::string value)"),
672 function("format", "void format(int value)"),
673 ];
674 let filtered =
675 cpp_filter_candidates_by_args(candidates.clone(), &[None], &|_| None, &|_, _| false);
676 assert_eq!(candidates, filtered);
677 }
678
679 #[test]
680 fn cpp_filter_candidates_keeps_all_when_no_candidate_matches() {
681 let candidates = vec![
682 function("format", "void format(std::string value)"),
683 function("format", "void format(int value)"),
684 ];
685 let filtered = cpp_filter_candidates_by_args(
686 candidates.clone(),
687 &[Some(CppArgType {
688 name: "double".to_string(),
689 unit: None,
690 indirection: 0,
691 pointee_const: false,
692 })],
693 &|_| None,
694 &|_, _| false,
695 );
696 assert_eq!(candidates, filtered);
697 }
698
699 fn call_expressions(tree: &tree_sitter::Tree) -> Vec<Node<'_>> {
701 let mut calls = Vec::new();
702 let mut stack = vec![tree.root_node()];
703 while let Some(node) = stack.pop() {
704 if node.kind() == "call_expression" {
705 calls.push(node);
706 }
707 let mut cursor = node.walk();
708 stack.extend(node.named_children(&mut cursor));
709 }
710 calls.sort_by_key(Node::start_byte);
711 calls
712 }
713
714 fn parse(source: &str) -> tree_sitter::Tree {
715 let mut parser = tree_sitter::Parser::new();
716 parser
717 .set_language(&tree_sitter_cpp::LANGUAGE.into())
718 .expect("the C++ grammar loads");
719 parser.parse(source, None).expect("the fixture parses")
720 }
721
722 #[test]
726 fn a_forwarding_call_reports_the_one_argument_it_forwards() {
727 let source = r#"void f() {
728 sink(std::move(a));
729 sink(std::forward<T>(b));
730 sink(std::move(c, 1));
731 sink(std::swap(d, e));
732 sink(move(g));
733 sink(other::move(h));
734 sink(std::vector<int>(i));
735}
736"#;
737 let tree = parse(source);
738 let forwarded = call_expressions(&tree)
739 .into_iter()
740 .filter_map(|call| cpp_forwarding_call_argument(call, source))
741 .map(|argument| cpp_node_text(argument, source).to_string())
742 .collect::<Vec<_>>();
743 assert_eq!(forwarded, vec!["a".to_string(), "b".to_string()]);
744 }
745
746 fn value_arg(name: &str) -> Option<CppArgType> {
747 Some(CppArgType {
748 name: name.to_string(),
749 unit: None,
750 indirection: 0,
751 pointee_const: false,
752 })
753 }
754
755 #[test]
760 fn a_contiguous_range_satisfies_a_span_over_its_element() {
761 for arg in ["std::vector<uint8_t>", "std::array<uint8_t, 16>"] {
762 for param in ["std::span<const uint8_t>", "std::span<uint8_t>"] {
763 let candidates = vec![
764 function("take", &format!("void take({param} bytes)")),
765 function("take", "void take(int count)"),
766 ];
767 let filtered = cpp_filter_candidates_by_args(
768 candidates,
769 &[value_arg(arg)],
770 &|_| None,
771 &|_, _| false,
772 );
773 assert_eq!(filtered.len(), 1, "{arg} -> {param}");
774 assert!(
775 filtered[0].signature().unwrap().contains(param),
776 "{arg} -> {param}: {:?}",
777 filtered[0].signature()
778 );
779 }
780 }
781 }
782
783 #[test]
786 fn a_range_over_another_element_does_not_satisfy_a_span() {
787 for (arg, param) in [
788 ("std::vector<int>", "std::span<const uint8_t>"),
789 ("std::vector<const uint8_t>", "std::span<uint8_t>"),
790 ("std::vector<uint8_t>", "std::span<const uint8_t*>"),
791 ("std::deque<uint8_t>", "std::span<const uint8_t>"),
792 ] {
793 let candidates = vec![
794 function("take", &format!("void take({param} bytes)")),
795 function("take", "void take(int count)"),
796 ];
797 let filtered = cpp_filter_candidates_by_args(
798 candidates.clone(),
799 &[value_arg(arg)],
800 &|_| None,
801 &|_, _| false,
802 );
803 assert_eq!(
804 candidates, filtered,
805 "{arg} must not satisfy {param}, so every candidate stays"
806 );
807 }
808 }
809
810 #[test]
811 fn an_owned_string_and_a_character_pointer_satisfy_a_string_view() {
812 let literal = Some(CppArgType {
813 name: "char".to_string(),
814 unit: None,
815 indirection: 1,
816 pointee_const: true,
817 });
818 for arg in [value_arg("std::string"), literal] {
819 let filtered = cpp_filter_candidates_by_args(
820 vec![
821 function("label", "void label(std::string_view text)"),
822 function("label", "void label(int count)"),
823 ],
824 std::slice::from_ref(&arg),
825 &|_| None,
826 &|_, _| false,
827 );
828 assert_eq!(filtered.len(), 1, "{:?}", arg.as_ref().map(|arg| &arg.name));
829 assert!(
830 filtered[0]
831 .signature()
832 .unwrap()
833 .contains("std::string_view"),
834 "{:?}",
835 filtered[0].signature()
836 );
837 }
838 }
839
840 #[test]
844 fn an_exact_match_outranks_a_standard_conversion() {
845 let candidates = vec![
846 function("own", "void own(std::vector<uint8_t> bytes)"),
847 function("own", "void own(std::span<const uint8_t> bytes)"),
848 ];
849 let filtered = cpp_filter_candidates_by_args(
850 candidates,
851 &[value_arg("std::vector<uint8_t>")],
852 &|_| None,
853 &|_, _| false,
854 );
855 assert_eq!(filtered.len(), 1);
856 assert_eq!(
857 "void own(std::vector<uint8_t> bytes)",
858 filtered[0].signature().unwrap()
859 );
860 }
861
862 #[test]
863 fn cpp_filter_candidates_keeps_templates_when_only_type_shape_is_unknown() {
864 let candidates = vec![
865 function("take", "void take(Vec256<float> value)"),
866 function("take", "<typename T>(Vec256<T>)"),
867 ];
868 let filtered = cpp_filter_candidates_by_args(
869 candidates.clone(),
870 &[Some(CppArgType {
871 name: "Vec256<int>".to_string(),
872 unit: Some(class("Vec256")),
873 indirection: 0,
874 pointee_const: false,
875 })],
876 &|_| None,
877 &|_, _| false,
878 );
879 assert_eq!(filtered, candidates);
880 }
881}