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
501#[derive(Clone, Copy, Debug, PartialEq, Eq)]
502pub enum CppRefQualifier {
503 Lvalue,
504 Rvalue,
505}
506
507pub fn cpp_signature_ref_qualifier(signature: &str) -> Option<CppRefQualifier> {
514 let mut suffix = cpp_signature_trailing_qualifiers(signature);
515 loop {
516 if let Some(rest) = suffix.strip_prefix("const ") {
517 suffix = rest;
518 } else if let Some(rest) = suffix.strip_prefix("volatile ") {
519 suffix = rest;
520 } else {
521 break;
522 }
523 }
524 if suffix == "&&" || suffix.starts_with("&& ") {
525 Some(CppRefQualifier::Rvalue)
526 } else if suffix == "&" || suffix.starts_with("& ") {
527 Some(CppRefQualifier::Lvalue)
528 } else {
529 None
530 }
531}
532
533fn cpp_parameter_name_token(token: &str) -> bool {
534 let token = token.trim_start_matches('*').trim_start_matches('&').trim();
535 token
536 .chars()
537 .next()
538 .is_some_and(|ch| ch == '_' || ch.is_ascii_lowercase())
539 && token
540 .chars()
541 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
542}
543
544fn strip_tag_type_prefix(value: &str) -> &str {
545 let value = value.trim_start_matches("const ");
546 value
547 .strip_prefix("struct ")
548 .or_else(|| value.strip_prefix("class "))
549 .or_else(|| value.strip_prefix("enum "))
550 .unwrap_or(value)
551 .trim()
552}
553
554fn cpp_number_literal_is_float(text: &str) -> bool {
555 let text = text.trim();
556 text.contains('.') || text.contains('e') || text.contains('E') || text.ends_with(['f', 'F'])
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562 use brokk_bifrost_core::analyzer::ProjectFile;
563 use brokk_bifrost_core::analyzer::model::CodeUnitType;
564
565 fn test_file() -> ProjectFile {
566 ProjectFile::new(std::env::temp_dir(), "test.cpp")
567 }
568
569 fn function(name: &str, signature: &str) -> CodeUnit {
570 CodeUnit::with_signature(
571 test_file(),
572 CodeUnitType::Function,
573 "ns",
574 name,
575 Some(signature.to_string()),
576 false,
577 )
578 }
579
580 fn class(name: &str) -> CodeUnit {
581 CodeUnit::new(test_file(), CodeUnitType::Class, "ns", name)
582 }
583
584 #[test]
585 fn cpp_filter_candidates_matches_named_unindexed_types() {
586 let candidates = vec![
587 function("format", "std::string format(const std::string& value)"),
588 function("format", "std::string format(int value)"),
589 ];
590 let filtered = cpp_filter_candidates_by_args(
591 candidates,
592 &[Some(CppArgType {
593 name: "std::string".to_string(),
594 unit: None,
595 indirection: 0,
596 pointee_const: false,
597 })],
598 &|_| None,
599 &|_, _| false,
600 );
601 assert_eq!(1, filtered.len());
602 assert!(filtered[0].signature().unwrap().contains("std::string&"));
603 }
604
605 #[test]
606 fn cpp_filter_candidates_matches_assignable_units() {
607 let arg = class("Arg");
608 let param = class("Param");
609 let filtered = cpp_filter_candidates_by_args(
610 vec![function("take", "void take(Param value)")],
611 &[Some(CppArgType {
612 name: "Arg".to_string(),
613 unit: Some(arg.clone()),
614 indirection: 0,
615 pointee_const: false,
616 })],
617 &|name| (name == "Param").then(|| param.clone()),
618 &|from, to| from == &arg && to == ¶m,
619 );
620 assert_eq!(1, filtered.len());
621 }
622
623 #[test]
624 fn cpp_filter_candidates_rejects_pointer_depth_mismatch() {
625 let candidates = vec![
626 function("take", "void take(int* value)"),
627 function("take", "void take(int value)"),
628 ];
629 let filtered = cpp_filter_candidates_by_args(
630 candidates,
631 &[Some(CppArgType {
632 name: "int".to_string(),
633 unit: None,
634 indirection: 0,
635 pointee_const: false,
636 })],
637 &|_| None,
638 &|_, _| false,
639 );
640 assert_eq!(1, filtered.len());
641 assert_eq!("void take(int value)", filtered[0].signature().unwrap());
642 }
643
644 #[test]
645 fn cpp_filter_candidates_uses_const_string_literal_pointer_evidence() {
646 let literal = Some(CppArgType {
647 name: "char".to_string(),
648 unit: None,
649 indirection: 1,
650 pointee_const: true,
651 });
652 let direct = cpp_filter_candidates_by_args(
653 vec![
654 function("select", "int select(int value)"),
655 function("select", "int select(const char* value)"),
656 ],
657 std::slice::from_ref(&literal),
658 &|_| None,
659 &|_, _| false,
660 );
661 assert_eq!(1, direct.len());
662 assert_eq!(
663 "int select(const char* value)",
664 direct[0].signature().unwrap()
665 );
666
667 for candidates in [
668 vec![
669 function("select", "int select(int value)"),
670 function("select", "int select(char* value)"),
671 ],
672 vec![
673 function("format", "int format(int value)"),
674 function("format", "int format(std::string value)"),
675 ],
676 ] {
677 let filtered = cpp_filter_candidates_by_args(
678 candidates.clone(),
679 std::slice::from_ref(&literal),
680 &|_| None,
681 &|_, _| false,
682 );
683 assert_eq!(
684 candidates, filtered,
685 "unmodeled or invalid conversions must remain conservative"
686 );
687 }
688 }
689
690 #[test]
691 fn cpp_parameter_type_keeps_pointer_const_distinct_from_pointee_const() {
692 assert_eq!("char*", cpp_parameter_type_text("char * const value"));
693 assert_eq!(
694 "const char*",
695 cpp_parameter_type_text("const char * const value")
696 );
697 assert_eq!("char", normalize_cpp_type_name("char * const"));
698 }
699
700 #[test]
701 fn cpp_filter_candidates_keeps_all_for_unknown_arguments() {
702 let candidates = vec![
703 function("format", "void format(std::string value)"),
704 function("format", "void format(int value)"),
705 ];
706 let filtered =
707 cpp_filter_candidates_by_args(candidates.clone(), &[None], &|_| None, &|_, _| false);
708 assert_eq!(candidates, filtered);
709 }
710
711 #[test]
712 fn cpp_filter_candidates_keeps_all_when_no_candidate_matches() {
713 let candidates = vec![
714 function("format", "void format(std::string value)"),
715 function("format", "void format(int value)"),
716 ];
717 let filtered = cpp_filter_candidates_by_args(
718 candidates.clone(),
719 &[Some(CppArgType {
720 name: "double".to_string(),
721 unit: None,
722 indirection: 0,
723 pointee_const: false,
724 })],
725 &|_| None,
726 &|_, _| false,
727 );
728 assert_eq!(candidates, filtered);
729 }
730
731 fn call_expressions(tree: &tree_sitter::Tree) -> Vec<Node<'_>> {
733 let mut calls = Vec::new();
734 let mut stack = vec![tree.root_node()];
735 while let Some(node) = stack.pop() {
736 if node.kind() == "call_expression" {
737 calls.push(node);
738 }
739 let mut cursor = node.walk();
740 stack.extend(node.named_children(&mut cursor));
741 }
742 calls.sort_by_key(Node::start_byte);
743 calls
744 }
745
746 fn parse(source: &str) -> tree_sitter::Tree {
747 let mut parser = tree_sitter::Parser::new();
748 parser
749 .set_language(&tree_sitter_cpp::LANGUAGE.into())
750 .expect("the C++ grammar loads");
751 parser.parse(source, None).expect("the fixture parses")
752 }
753
754 #[test]
758 fn a_forwarding_call_reports_the_one_argument_it_forwards() {
759 let source = r#"void f() {
760 sink(std::move(a));
761 sink(std::forward<T>(b));
762 sink(std::move(c, 1));
763 sink(std::swap(d, e));
764 sink(move(g));
765 sink(other::move(h));
766 sink(std::vector<int>(i));
767}
768"#;
769 let tree = parse(source);
770 let forwarded = call_expressions(&tree)
771 .into_iter()
772 .filter_map(|call| cpp_forwarding_call_argument(call, source))
773 .map(|argument| cpp_node_text(argument, source).to_string())
774 .collect::<Vec<_>>();
775 assert_eq!(forwarded, vec!["a".to_string(), "b".to_string()]);
776 }
777
778 fn value_arg(name: &str) -> Option<CppArgType> {
779 Some(CppArgType {
780 name: name.to_string(),
781 unit: None,
782 indirection: 0,
783 pointee_const: false,
784 })
785 }
786
787 #[test]
792 fn a_contiguous_range_satisfies_a_span_over_its_element() {
793 for arg in ["std::vector<uint8_t>", "std::array<uint8_t, 16>"] {
794 for param in ["std::span<const uint8_t>", "std::span<uint8_t>"] {
795 let candidates = vec![
796 function("take", &format!("void take({param} bytes)")),
797 function("take", "void take(int count)"),
798 ];
799 let filtered = cpp_filter_candidates_by_args(
800 candidates,
801 &[value_arg(arg)],
802 &|_| None,
803 &|_, _| false,
804 );
805 assert_eq!(filtered.len(), 1, "{arg} -> {param}");
806 assert!(
807 filtered[0].signature().unwrap().contains(param),
808 "{arg} -> {param}: {:?}",
809 filtered[0].signature()
810 );
811 }
812 }
813 }
814
815 #[test]
818 fn a_range_over_another_element_does_not_satisfy_a_span() {
819 for (arg, param) in [
820 ("std::vector<int>", "std::span<const uint8_t>"),
821 ("std::vector<const uint8_t>", "std::span<uint8_t>"),
822 ("std::vector<uint8_t>", "std::span<const uint8_t*>"),
823 ("std::deque<uint8_t>", "std::span<const uint8_t>"),
824 ] {
825 let candidates = vec![
826 function("take", &format!("void take({param} bytes)")),
827 function("take", "void take(int count)"),
828 ];
829 let filtered = cpp_filter_candidates_by_args(
830 candidates.clone(),
831 &[value_arg(arg)],
832 &|_| None,
833 &|_, _| false,
834 );
835 assert_eq!(
836 candidates, filtered,
837 "{arg} must not satisfy {param}, so every candidate stays"
838 );
839 }
840 }
841
842 #[test]
843 fn an_owned_string_and_a_character_pointer_satisfy_a_string_view() {
844 let literal = Some(CppArgType {
845 name: "char".to_string(),
846 unit: None,
847 indirection: 1,
848 pointee_const: true,
849 });
850 for arg in [value_arg("std::string"), literal] {
851 let filtered = cpp_filter_candidates_by_args(
852 vec![
853 function("label", "void label(std::string_view text)"),
854 function("label", "void label(int count)"),
855 ],
856 std::slice::from_ref(&arg),
857 &|_| None,
858 &|_, _| false,
859 );
860 assert_eq!(filtered.len(), 1, "{:?}", arg.as_ref().map(|arg| &arg.name));
861 assert!(
862 filtered[0]
863 .signature()
864 .unwrap()
865 .contains("std::string_view"),
866 "{:?}",
867 filtered[0].signature()
868 );
869 }
870 }
871
872 #[test]
876 fn an_exact_match_outranks_a_standard_conversion() {
877 let candidates = vec![
878 function("own", "void own(std::vector<uint8_t> bytes)"),
879 function("own", "void own(std::span<const uint8_t> bytes)"),
880 ];
881 let filtered = cpp_filter_candidates_by_args(
882 candidates,
883 &[value_arg("std::vector<uint8_t>")],
884 &|_| None,
885 &|_, _| false,
886 );
887 assert_eq!(filtered.len(), 1);
888 assert_eq!(
889 "void own(std::vector<uint8_t> bytes)",
890 filtered[0].signature().unwrap()
891 );
892 }
893
894 #[test]
895 fn cpp_filter_candidates_keeps_templates_when_only_type_shape_is_unknown() {
896 let candidates = vec![
897 function("take", "void take(Vec256<float> value)"),
898 function("take", "<typename T>(Vec256<T>)"),
899 ];
900 let filtered = cpp_filter_candidates_by_args(
901 candidates.clone(),
902 &[Some(CppArgType {
903 name: "Vec256<int>".to_string(),
904 unit: Some(class("Vec256")),
905 indirection: 0,
906 pointee_const: false,
907 })],
908 &|_| None,
909 &|_, _| false,
910 );
911 assert_eq!(filtered, candidates);
912 }
913
914 #[test]
915 fn signature_ref_qualifier_comes_from_the_ast_normalized_suffix() {
916 assert_eq!(
917 cpp_signature_ref_qualifier("() const & noexcept"),
918 Some(CppRefQualifier::Lvalue)
919 );
920 assert_eq!(
921 cpp_signature_ref_qualifier("(int) volatile && noexcept"),
922 Some(CppRefQualifier::Rvalue)
923 );
924 assert_eq!(cpp_signature_ref_qualifier("() const noexcept"), None);
925 assert_eq!(
926 cpp_signature_ref_qualifier("() noexcept(left && right)"),
927 None
928 );
929 }
930}