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
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum CppArgumentApplicability {
29 Applicable,
30 Inapplicable,
31 Unknown,
32}
33
34pub fn cpp_signature_param_types(signature: &str) -> Option<Vec<String>> {
35 let inner = cpp_signature_parameter_text(signature)
36 .unwrap_or(signature)
37 .trim();
38 if inner.is_empty() || inner == "void" {
39 return Some(Vec::new());
40 }
41 Some(
42 cpp_split_top_level_commas(inner)
43 .map(cpp_parameter_type_text)
44 .collect(),
45 )
46}
47
48pub fn cpp_parameter_type_text(parameter: &str) -> String {
49 let mut text = parameter
50 .split_once('=')
51 .map(|(before, _)| before)
52 .unwrap_or(parameter)
53 .trim()
54 .trim_end_matches(';')
55 .trim();
56 let pointer_depth = cpp_type_text_pointer_depth(text);
57 if let Some((before, last)) = text.rsplit_once(char::is_whitespace)
58 && cpp_parameter_name_token(last)
59 {
60 text = before.trim();
61 }
62 let pointee_const = pointer_depth > 0 && cpp_type_text_pointee_is_const(text);
63 format!(
64 "{}{}{}",
65 if pointee_const { "const " } else { "" },
66 normalize_cpp_type_name(text),
67 "*".repeat(pointer_depth as usize)
68 )
69}
70
71pub fn normalize_cpp_type_name(text: &str) -> String {
72 let normalized = normalize_cpp_whitespace(text);
73 let base = cpp_type_text_base(&normalized)
74 .trim_start_matches("const ")
75 .trim();
76 strip_tag_type_prefix(base.strip_suffix(" const").unwrap_or(base)).to_string()
77}
78
79pub fn cpp_type_text_pointer_depth(text: &str) -> i32 {
80 cpp_type_text_shape(text).1
81}
82
83fn cpp_type_text_shape(text: &str) -> (usize, i32) {
84 let mut depth = 0i32;
85 let mut nesting = 0i32;
86 let mut base_end = text.len();
87 for (offset, ch) in text.char_indices() {
88 match ch {
89 '<' | '(' | '[' => nesting += 1,
90 '>' | ')' | ']' => nesting -= 1,
91 '*' if nesting <= 0 => {
92 base_end = base_end.min(offset);
93 depth += 1;
94 }
95 '&' if nesting <= 0 => base_end = base_end.min(offset),
96 _ => {}
97 }
98 }
99 (base_end, depth)
100}
101
102pub fn cpp_forwarding_call_argument<'tree>(call: Node<'tree>, source: &str) -> Option<Node<'tree>> {
120 if call.kind() != "call_expression" {
121 return None;
122 }
123 let mut callee = call.child_by_field_name("function")?;
124 if callee.kind() == "template_function" {
125 callee = callee.child_by_field_name("name")?;
126 }
127 if callee.kind() != "qualified_identifier" {
128 return None;
129 }
130 let scope = callee.child_by_field_name("scope")?;
131 if !matches!(scope.kind(), "namespace_identifier" | "identifier")
132 || cpp_node_text(scope, source).trim() != "std"
133 {
134 return None;
135 }
136 let mut name = callee.child_by_field_name("name")?;
137 if name.kind() == "template_function" {
141 name = name.child_by_field_name("name")?;
142 }
143 if !matches!(name.kind(), "identifier" | "field_identifier")
144 || !matches!(cpp_node_text(name, source).trim(), "move" | "forward")
145 {
146 return None;
147 }
148 let arguments = call.child_by_field_name("arguments")?;
149 let mut cursor = arguments.walk();
150 let forwarded = arguments
151 .named_children(&mut cursor)
152 .filter(|argument| argument.kind() != "comment")
153 .collect::<Vec<_>>();
154 let [argument] = forwarded.as_slice() else {
155 return None;
156 };
157 Some(*argument)
158}
159
160pub fn cpp_literal_arg_type(node: Node<'_>, source: &str) -> Option<CppArgType> {
161 let scalar = |name: &str| CppArgType {
162 name: name.to_string(),
163 unit: None,
164 indirection: 0,
165 pointee_const: false,
166 };
167 match node.kind() {
168 "number_literal" => {
169 let text = cpp_node_text(node, source);
170 if cpp_number_literal_is_float(text) {
171 Some(scalar("double"))
172 } else {
173 Some(scalar("int"))
174 }
175 }
176 "true" | "false" => Some(scalar("bool")),
177 "char_literal" => Some(scalar("char")),
178 "string_literal" => {
179 let text = cpp_node_text(node, source).trim_start();
180 (text.starts_with('"') || text.starts_with("R\"")).then(|| CppArgType {
181 name: "char".to_string(),
182 unit: None,
183 indirection: 1,
184 pointee_const: true,
185 })
186 }
187 "unary_expression" => {
188 let operator = node.child_by_field_name("operator")?;
189 let inner = node
190 .child_by_field_name("argument")
191 .or_else(|| node.named_child(0))?;
192 matches!(operator.kind(), "+" | "-")
193 .then(|| cpp_literal_arg_type(inner, source))
194 .flatten()
195 }
196 _ => None,
197 }
198}
199
200#[cfg(test)]
201fn cpp_filter_candidates_by_args(
202 candidates: Vec<CodeUnit>,
203 arg_types: &[Option<CppArgType>],
204 resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
205 assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
206) -> Vec<CodeUnit> {
207 cpp_filter_candidates_by_args_with_parameter_types(
208 candidates,
209 arg_types,
210 &|candidate| cpp_signature_param_types(candidate.signature().unwrap_or_default()),
211 &cpp_signature_is_template_candidate,
212 resolve_type,
213 assignable,
214 )
215}
216
217pub fn cpp_filter_candidates_by_args_with_parameter_types(
218 candidates: Vec<CodeUnit>,
219 arg_types: &[Option<CppArgType>],
220 parameter_types: &dyn Fn(&CodeUnit) -> Option<Vec<String>>,
221 is_template: &dyn Fn(&CodeUnit) -> bool,
222 resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
223 assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
224) -> Vec<CodeUnit> {
225 if candidates.len() <= 1 || arg_types.iter().any(Option::is_none) {
226 return candidates;
227 }
228 let args: Vec<&CppArgType> = arg_types.iter().flatten().collect();
229 debug_assert_eq!(args.len(), arg_types.len());
230
231 let exact = cpp_candidates_matching(
237 &candidates,
238 &args,
239 parameter_types,
240 &|param, arg, template_candidate| {
241 cpp_param_matches_arg(param, arg, template_candidate, resolve_type, assignable)
242 },
243 );
244 let filtered = if exact.is_empty() {
245 cpp_candidates_matching(
246 &candidates,
247 &args,
248 parameter_types,
249 &|param, arg, template_candidate| {
250 cpp_param_matches_arg(param, arg, template_candidate, resolve_type, assignable)
251 || cpp_standard_conversion_applies(arg, param)
252 },
253 )
254 } else {
255 exact
256 };
257 if filtered.is_empty() {
258 candidates
259 } else if filtered.iter().any(is_template) {
260 candidates
265 } else {
266 filtered
267 }
268}
269
270pub fn cpp_candidate_argument_applicability(
276 candidate: &CodeUnit,
277 arg_types: &[Option<CppArgType>],
278 parameter_types: &dyn Fn(&CodeUnit) -> Option<Vec<String>>,
279 is_template: &dyn Fn(&CodeUnit) -> bool,
280 resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
281 assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
282) -> CppArgumentApplicability {
283 let Some(parameters) = parameter_types(candidate) else {
284 return CppArgumentApplicability::Unknown;
285 };
286 if arg_types.iter().any(Option::is_none) || is_template(candidate) {
287 return CppArgumentApplicability::Unknown;
288 }
289 if parameters.len() != arg_types.len() {
290 return CppArgumentApplicability::Inapplicable;
291 }
292 let args: Vec<&CppArgType> = arg_types.iter().flatten().collect();
293 debug_assert_eq!(args.len(), arg_types.len());
294
295 let mut uncertain = false;
296 for (parameter, argument) in parameters.iter().zip(args) {
297 if cpp_type_text_pointer_depth(parameter) != argument.indirection
298 || (argument.pointee_const && !cpp_type_text_pointee_is_const(parameter))
299 {
300 return CppArgumentApplicability::Inapplicable;
301 }
302 if cpp_param_matches_arg(parameter, argument, false, resolve_type, assignable) {
303 continue;
304 }
305 if cpp_standard_conversion_applies(argument, parameter) {
309 uncertain = true;
310 continue;
311 }
312 uncertain = true;
316 }
317 if uncertain {
318 CppArgumentApplicability::Unknown
319 } else {
320 CppArgumentApplicability::Applicable
321 }
322}
323
324fn cpp_candidates_matching(
327 candidates: &[CodeUnit],
328 args: &[&CppArgType],
329 parameter_types: &dyn Fn(&CodeUnit) -> Option<Vec<String>>,
330 matches: &dyn Fn(&str, &CppArgType, bool) -> bool,
331) -> Vec<CodeUnit> {
332 candidates
333 .iter()
334 .filter(|candidate| {
335 let template_candidate = cpp_signature_is_template_candidate(candidate);
336 parameter_types(candidate).is_some_and(|params| {
337 params.len() == args.len()
338 && params
339 .iter()
340 .zip(args.iter())
341 .all(|(param, arg)| matches(param, arg, template_candidate))
342 })
343 })
344 .cloned()
345 .collect()
346}
347fn cpp_signature_is_template_candidate(candidate: &CodeUnit) -> bool {
348 candidate
349 .signature()
350 .is_some_and(|signature| signature.trim_start().starts_with('<'))
351}
352
353fn cpp_param_matches_arg(
354 param: &str,
355 arg: &CppArgType,
356 template_candidate: bool,
357 resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
358 assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
359) -> bool {
360 if cpp_type_text_pointer_depth(param) != arg.indirection {
361 return false;
362 }
363 if arg.pointee_const && !cpp_type_text_pointee_is_const(param) {
364 return false;
365 }
366 if template_candidate {
372 return true;
373 }
374 let param_name = normalize_cpp_type_name(param);
375 match (resolve_type(¶m_name), arg.unit.as_ref()) {
376 (Some(param_unit), Some(arg_unit)) => assignable(arg_unit, ¶m_unit),
377 _ => param_name == arg.name,
378 }
379}
380
381fn cpp_standard_conversion_applies(arg: &CppArgType, param: &str) -> bool {
413 if cpp_type_text_pointer_depth(param) != 0 {
414 return false;
415 }
416 let param_name = normalize_cpp_type_name(param);
417 let span_element = cpp_template_arguments(¶m_name, "std::span")
418 .and_then(|arguments| arguments.first().copied());
419 if let Some(span_element) = span_element {
420 return arg.indirection == 0
421 && cpp_contiguous_range_element(&arg.name)
422 .is_some_and(|element| cpp_span_element_accepts(span_element, element));
423 }
424 if param_name == "std::string_view" {
425 return (arg.indirection == 0 && arg.name == "std::string")
426 || (arg.indirection == 1 && arg.name == "char");
427 }
428 false
429}
430
431fn cpp_template_arguments<'a>(text: &'a str, base: &str) -> Option<Vec<&'a str>> {
438 let inner = text
439 .strip_prefix(base)?
440 .trim_start()
441 .strip_prefix('<')?
442 .trim_end()
443 .strip_suffix('>')?;
444 Some(cpp_split_top_level_commas(inner).collect())
445}
446
447fn cpp_contiguous_range_element(name: &str) -> Option<&str> {
450 ["std::vector", "std::array"]
451 .into_iter()
452 .find_map(|base| cpp_template_arguments(name, base))
453 .and_then(|arguments| arguments.first().copied())
454}
455
456fn cpp_span_element_accepts(param_element: &str, arg_element: &str) -> bool {
459 cpp_type_text_pointer_depth(param_element) == cpp_type_text_pointer_depth(arg_element)
460 && normalize_cpp_type_name(param_element) == normalize_cpp_type_name(arg_element)
461 && (cpp_type_text_pointee_is_const(param_element)
462 || !cpp_type_text_pointee_is_const(arg_element))
463}
464
465fn cpp_type_text_pointee_is_const(text: &str) -> bool {
466 let normalized = normalize_cpp_whitespace(text);
467 let base = cpp_type_text_base(&normalized).trim();
468 base.starts_with("const ") || base.ends_with(" const")
469}
470
471fn cpp_type_text_base(text: &str) -> &str {
472 text[..cpp_type_text_shape(text).0].trim()
473}
474
475pub fn cpp_split_top_level_commas(value: &str) -> impl Iterator<Item = &str> {
476 struct TopLevelCommaSplit<'a> {
477 value: &'a str,
478 start: usize,
479 angle: usize,
480 paren: usize,
481 brace: usize,
482 bracket: usize,
483 }
484
485 impl<'a> Iterator for TopLevelCommaSplit<'a> {
486 type Item = &'a str;
487
488 fn next(&mut self) -> Option<Self::Item> {
489 if self.start > self.value.len() {
490 return None;
491 }
492 for (offset, ch) in self.value[self.start..].char_indices() {
493 let absolute = self.start + offset;
494 match ch {
495 '<' => self.angle += 1,
496 '>' => self.angle = self.angle.saturating_sub(1),
497 '(' => self.paren += 1,
498 ')' => self.paren = self.paren.saturating_sub(1),
499 '{' => self.brace += 1,
500 '}' => self.brace = self.brace.saturating_sub(1),
501 '[' => self.bracket += 1,
502 ']' => self.bracket = self.bracket.saturating_sub(1),
503 ',' if self.angle == 0
504 && self.paren == 0
505 && self.brace == 0
506 && self.bracket == 0 =>
507 {
508 let item = self.value[self.start..absolute].trim();
509 self.start = absolute + ch.len_utf8();
510 return Some(item);
511 }
512 _ => {}
513 }
514 }
515 let item = self.value[self.start..].trim();
516 self.start = self.value.len() + 1;
517 Some(item)
518 }
519 }
520
521 TopLevelCommaSplit {
522 value,
523 start: 0,
524 angle: 0,
525 paren: 0,
526 brace: 0,
527 bracket: 0,
528 }
529 .filter(|item| !item.is_empty())
530}
531
532fn cpp_signature_parameter_span(signature: &str) -> Option<(usize, usize)> {
534 let open = signature.find('(')?;
535 let mut depth = 0i32;
536 for (offset, ch) in signature[open..].char_indices() {
537 match ch {
538 '(' => depth += 1,
539 ')' => {
540 depth -= 1;
541 if depth == 0 {
542 return Some((open, open + offset));
543 }
544 }
545 _ => {}
546 }
547 }
548 None
549}
550
551fn cpp_signature_parameter_text(signature: &str) -> Option<&str> {
552 let (open, close) = cpp_signature_parameter_span(signature)?;
553 Some(signature[open + 1..close].trim())
554}
555
556pub fn cpp_signature_trailing_qualifiers(signature: &str) -> &str {
565 match cpp_signature_parameter_span(signature) {
566 Some((_, close)) => signature[close + 1..].trim(),
567 None => "",
568 }
569}
570
571#[derive(Clone, Copy, Debug, PartialEq, Eq)]
572pub enum CppRefQualifier {
573 Lvalue,
574 Rvalue,
575}
576
577pub fn cpp_signature_ref_qualifier(signature: &str) -> Option<CppRefQualifier> {
584 let mut suffix = cpp_signature_trailing_qualifiers(signature);
585 loop {
586 if let Some(rest) = suffix.strip_prefix("const ") {
587 suffix = rest;
588 } else if let Some(rest) = suffix.strip_prefix("volatile ") {
589 suffix = rest;
590 } else {
591 break;
592 }
593 }
594 if suffix == "&&" || suffix.starts_with("&& ") {
595 Some(CppRefQualifier::Rvalue)
596 } else if suffix == "&" || suffix.starts_with("& ") {
597 Some(CppRefQualifier::Lvalue)
598 } else {
599 None
600 }
601}
602
603fn cpp_parameter_name_token(token: &str) -> bool {
604 let token = token.trim_start_matches('*').trim_start_matches('&').trim();
605 token
606 .chars()
607 .next()
608 .is_some_and(|ch| ch == '_' || ch.is_ascii_lowercase())
609 && token
610 .chars()
611 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
612}
613
614use crate::declarations::strip_tag_type_prefix;
615
616fn cpp_number_literal_is_float(text: &str) -> bool {
617 let text = text.trim();
618 text.contains('.') || text.contains('e') || text.contains('E') || text.ends_with(['f', 'F'])
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624 use brokk_bifrost_core::analyzer::ProjectFile;
625 use brokk_bifrost_core::analyzer::model::CodeUnitType;
626 use brokk_bifrost_test_macros::hardwon;
627
628 fn test_file() -> ProjectFile {
629 ProjectFile::new(std::env::temp_dir(), "test.cpp")
630 }
631
632 fn function(name: &str, signature: &str) -> CodeUnit {
633 CodeUnit::with_signature(
634 test_file(),
635 CodeUnitType::Function,
636 "ns",
637 name,
638 Some(signature.to_string()),
639 false,
640 )
641 }
642
643 fn class(name: &str) -> CodeUnit {
644 CodeUnit::new(test_file(), CodeUnitType::Class, "ns", name)
645 }
646
647 #[test]
648 #[hardwon("#427: overload call filtering conflated different callable candidates")]
649 fn cpp_filter_candidates_matches_named_unindexed_types() {
650 let candidates = vec![
651 function("format", "std::string format(const std::string& value)"),
652 function("format", "std::string format(int value)"),
653 ];
654 let filtered = cpp_filter_candidates_by_args(
655 candidates,
656 &[Some(CppArgType {
657 name: "std::string".to_string(),
658 unit: None,
659 indirection: 0,
660 pointee_const: false,
661 })],
662 &|_| None,
663 &|_, _| false,
664 );
665 assert_eq!(1, filtered.len());
666 assert!(filtered[0].signature().unwrap().contains("std::string&"));
667 }
668
669 #[test]
670 #[hardwon("#427: overload call filtering conflated different callable candidates")]
671 fn cpp_filter_candidates_matches_assignable_units() {
672 let arg = class("Arg");
673 let param = class("Param");
674 let filtered = cpp_filter_candidates_by_args(
675 vec![function("take", "void take(Param value)")],
676 &[Some(CppArgType {
677 name: "Arg".to_string(),
678 unit: Some(arg.clone()),
679 indirection: 0,
680 pointee_const: false,
681 })],
682 &|name| (name == "Param").then(|| param.clone()),
683 &|from, to| from == &arg && to == ¶m,
684 );
685 assert_eq!(1, filtered.len());
686 }
687
688 #[test]
689 #[hardwon("#427: overload call filtering conflated different callable candidates")]
690 fn cpp_filter_candidates_rejects_pointer_depth_mismatch() {
691 let candidates = vec![
692 function("take", "void take(int* value)"),
693 function("take", "void take(int value)"),
694 ];
695 let filtered = cpp_filter_candidates_by_args(
696 candidates,
697 &[Some(CppArgType {
698 name: "int".to_string(),
699 unit: None,
700 indirection: 0,
701 pointee_const: false,
702 })],
703 &|_| None,
704 &|_, _| false,
705 );
706 assert_eq!(1, filtered.len());
707 assert_eq!("void take(int value)", filtered[0].signature().unwrap());
708 }
709
710 #[test]
711 #[hardwon("#427: string literal types were missing from overload matching")]
712 fn cpp_filter_candidates_uses_const_string_literal_pointer_evidence() {
713 let literal = Some(CppArgType {
714 name: "char".to_string(),
715 unit: None,
716 indirection: 1,
717 pointee_const: true,
718 });
719 let direct = cpp_filter_candidates_by_args(
720 vec![
721 function("select", "int select(int value)"),
722 function("select", "int select(const char* value)"),
723 ],
724 std::slice::from_ref(&literal),
725 &|_| None,
726 &|_, _| false,
727 );
728 assert_eq!(1, direct.len());
729 assert_eq!(
730 "int select(const char* value)",
731 direct[0].signature().unwrap()
732 );
733
734 for candidates in [
735 vec![
736 function("select", "int select(int value)"),
737 function("select", "int select(char* value)"),
738 ],
739 vec![
740 function("format", "int format(int value)"),
741 function("format", "int format(std::string value)"),
742 ],
743 ] {
744 let filtered = cpp_filter_candidates_by_args(
745 candidates.clone(),
746 std::slice::from_ref(&literal),
747 &|_| None,
748 &|_, _| false,
749 );
750 assert_eq!(
751 candidates, filtered,
752 "unmodeled or invalid conversions must remain conservative"
753 );
754 }
755 }
756
757 #[test]
758 #[hardwon("#427: parameter pointer const was confused with pointee const")]
759 fn cpp_parameter_type_keeps_pointer_const_distinct_from_pointee_const() {
760 assert_eq!("char*", cpp_parameter_type_text("char * const value"));
761 assert_eq!(
762 "const char*",
763 cpp_parameter_type_text("const char * const value")
764 );
765 assert_eq!("char", normalize_cpp_type_name("char * const"));
766 }
767
768 #[test]
769 #[hardwon("#427: unknown argument types must preserve conservative overload candidates")]
770 fn cpp_filter_candidates_keeps_all_for_unknown_arguments() {
771 let candidates = vec![
772 function("format", "void format(std::string value)"),
773 function("format", "void format(int value)"),
774 ];
775 let filtered =
776 cpp_filter_candidates_by_args(candidates.clone(), &[None], &|_| None, &|_, _| false);
777 assert_eq!(candidates, filtered);
778 }
779
780 #[test]
781 #[hardwon("#427: unmatched implicit conversions must preserve conservative candidates")]
782 fn cpp_filter_candidates_keeps_all_when_no_candidate_matches() {
783 let candidates = vec![
784 function("format", "void format(std::string value)"),
785 function("format", "void format(int value)"),
786 ];
787 let filtered = cpp_filter_candidates_by_args(
788 candidates.clone(),
789 &[Some(CppArgType {
790 name: "double".to_string(),
791 unit: None,
792 indirection: 0,
793 pointee_const: false,
794 })],
795 &|_| None,
796 &|_, _| false,
797 );
798 assert_eq!(candidates, filtered);
799 }
800
801 fn call_expressions(tree: &tree_sitter::Tree) -> Vec<Node<'_>> {
803 let mut calls = Vec::new();
804 let mut stack = vec![tree.root_node()];
805 while let Some(node) = stack.pop() {
806 if node.kind() == "call_expression" {
807 calls.push(node);
808 }
809 let mut cursor = node.walk();
810 stack.extend(node.named_children(&mut cursor));
811 }
812 calls.sort_by_key(Node::start_byte);
813 calls
814 }
815
816 fn parse(source: &str) -> tree_sitter::Tree {
817 crate::diagnostics::parse_cpp_tree(source)
818 }
819
820 #[test]
824 #[hardwon("#2894: C++ call argument typing left forwarding/member-field calls ambiguous")]
825 fn a_forwarding_call_reports_the_one_argument_it_forwards() {
826 let source = r#"void f() {
827 sink(std::move(a));
828 sink(std::forward<T>(b));
829 sink(std::move(c, 1));
830 sink(std::swap(d, e));
831 sink(move(g));
832 sink(other::move(h));
833 sink(std::vector<int>(i));
834}
835"#;
836 let tree = parse(source);
837 let forwarded = call_expressions(&tree)
838 .into_iter()
839 .filter_map(|call| cpp_forwarding_call_argument(call, source))
840 .map(|argument| cpp_node_text(argument, source).to_string())
841 .collect::<Vec<_>>();
842 assert_eq!(forwarded, vec!["a".to_string(), "b".to_string()]);
843 }
844
845 fn value_arg(name: &str) -> Option<CppArgType> {
846 Some(CppArgType {
847 name: name.to_string(),
848 unit: None,
849 indirection: 0,
850 pointee_const: false,
851 })
852 }
853
854 #[test]
859 #[hardwon("#2894: owning contiguous ranges were excluded from span overloads")]
860 fn a_contiguous_range_satisfies_a_span_over_its_element() {
861 for arg in ["std::vector<uint8_t>", "std::array<uint8_t, 16>"] {
862 for param in ["std::span<const uint8_t>", "std::span<uint8_t>"] {
863 let candidates = vec![
864 function("take", &format!("void take({param} bytes)")),
865 function("take", "void take(int count)"),
866 ];
867 let filtered = cpp_filter_candidates_by_args(
868 candidates,
869 &[value_arg(arg)],
870 &|_| None,
871 &|_, _| false,
872 );
873 assert_eq!(filtered.len(), 1, "{arg} -> {param}");
874 assert!(
875 filtered[0].signature().unwrap().contains(param),
876 "{arg} -> {param}: {:?}",
877 filtered[0].signature()
878 );
879 }
880 }
881 }
882
883 #[test]
886 #[hardwon("#2894: span conversion accepted incompatible element shapes")]
887 fn a_range_over_another_element_does_not_satisfy_a_span() {
888 for (arg, param) in [
889 ("std::vector<int>", "std::span<const uint8_t>"),
890 ("std::vector<const uint8_t>", "std::span<uint8_t>"),
891 ("std::vector<uint8_t>", "std::span<const uint8_t*>"),
892 ("std::deque<uint8_t>", "std::span<const uint8_t>"),
893 ] {
894 let candidates = vec![
895 function("take", &format!("void take({param} bytes)")),
896 function("take", "void take(int count)"),
897 ];
898 let filtered = cpp_filter_candidates_by_args(
899 candidates.clone(),
900 &[value_arg(arg)],
901 &|_| None,
902 &|_, _| false,
903 );
904 assert_eq!(
905 candidates, filtered,
906 "{arg} must not satisfy {param}, so every candidate stays"
907 );
908 }
909 }
910
911 #[test]
912 #[hardwon("#2894: string and character pointers did not match string_view overloads")]
913 fn an_owned_string_and_a_character_pointer_satisfy_a_string_view() {
914 let literal = Some(CppArgType {
915 name: "char".to_string(),
916 unit: None,
917 indirection: 1,
918 pointee_const: true,
919 });
920 for arg in [value_arg("std::string"), literal] {
921 let filtered = cpp_filter_candidates_by_args(
922 vec![
923 function("label", "void label(std::string_view text)"),
924 function("label", "void label(int count)"),
925 ],
926 std::slice::from_ref(&arg),
927 &|_| None,
928 &|_, _| false,
929 );
930 assert_eq!(filtered.len(), 1, "{:?}", arg.as_ref().map(|arg| &arg.name));
931 assert!(
932 filtered[0]
933 .signature()
934 .unwrap()
935 .contains("std::string_view"),
936 "{:?}",
937 filtered[0].signature()
938 );
939 }
940 }
941
942 #[test]
946 #[hardwon("#2894: a standard conversion outranked an exact overload")]
947 fn an_exact_match_outranks_a_standard_conversion() {
948 let candidates = vec![
949 function("own", "void own(std::vector<uint8_t> bytes)"),
950 function("own", "void own(std::span<const uint8_t> bytes)"),
951 ];
952 let filtered = cpp_filter_candidates_by_args(
953 candidates,
954 &[value_arg("std::vector<uint8_t>")],
955 &|_| None,
956 &|_, _| false,
957 );
958 assert_eq!(filtered.len(), 1);
959 assert_eq!(
960 "void own(std::vector<uint8_t> bytes)",
961 filtered[0].signature().unwrap()
962 );
963 }
964
965 #[test]
966 #[hardwon("02186273bdc4: unknown template deductions discarded compatible overloads")]
967 fn cpp_filter_candidates_keeps_templates_when_only_type_shape_is_unknown() {
968 let candidates = vec![
969 function("take", "void take(Vec256<float> value)"),
970 function("take", "<typename T>(Vec256<T>)"),
971 ];
972 let filtered = cpp_filter_candidates_by_args(
973 candidates.clone(),
974 &[Some(CppArgType {
975 name: "Vec256<int>".to_string(),
976 unit: Some(class("Vec256")),
977 indirection: 0,
978 pointee_const: false,
979 })],
980 &|_| None,
981 &|_, _| false,
982 );
983 assert_eq!(filtered, candidates);
984 }
985
986 #[test]
987 #[hardwon("#2909: an lvalue receiver retained an rvalue-qualified member")]
988 fn signature_ref_qualifier_comes_from_the_ast_normalized_suffix() {
989 assert_eq!(
990 cpp_signature_ref_qualifier("() const & noexcept"),
991 Some(CppRefQualifier::Lvalue)
992 );
993 assert_eq!(
994 cpp_signature_ref_qualifier("(int) volatile && noexcept"),
995 Some(CppRefQualifier::Rvalue)
996 );
997 assert_eq!(cpp_signature_ref_qualifier("() const noexcept"), None);
998 assert_eq!(
999 cpp_signature_ref_qualifier("() noexcept(left && right)"),
1000 None
1001 );
1002 }
1003}