Skip to main content

brokk_bifrost_cpp/
call_match.rs

1//! Overload disambiguation for C++ call sites.
2//!
3//! The only C++-only module that lived outside `cpp_graph/`: given a candidate
4//! set of same-named callables and the argument list at a call site, narrow by
5//! parameter type shape. Pure text and AST work over the signatures the
6//! declaration walk already emitted.
7
8use 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
88/// The single argument a `std::move` or `std::forward` call forwards, or `None`
89/// for every other call.
90///
91/// As far as an overload's parameter types are concerned these two are the
92/// identity: the argument's type is the call's type. Without that,
93/// `Montgomery_Int(m_params, std::move(t))` had one argument of unknown type,
94/// the argument filter kept every candidate rather than guess, and the call
95/// reported *ambiguous* between the `secure_vector<word>` and
96/// `std::span<const word>` overloads (#2552).
97///
98/// Recognized structurally, from the callee's `scope` and `name` fields: a
99/// `qualified_identifier` whose scope is `std` and whose name is `move` or
100/// `forward`, with the template arguments of `std::forward<T>(t)` unwrapped
101/// wherever the grammar attached them. A local `move(x)` or
102/// another namespace's `move` is not this, and neither is a call with any
103/// argument count but one -- that is not the standard signature, so the
104/// argument type stays unknown and the filter keeps every candidate.
105pub 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    // `std::forward<T>(t)` attaches its template arguments to the name half of
124    // the qualified name, so the `name` field is a `template_function` whose own
125    // `name` is the identifier.
126    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    // Exact matches first; standard conversions decide only when nothing
215    // matches exactly. That order is C++'s own -- an identity conversion
216    // sequence beats every other one -- and it is what keeps
217    // `own(std::vector<uint8_t>)` winning over `own(std::span<const uint8_t>)`
218    // for a `std::vector<uint8_t>` argument now that the second is viable too.
219    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        // A matching function template keeps the entire arity-compatible
244        // overload set alive. The parameter metadata has no template
245        // substitution or constraint ordering, so it cannot prove that a
246        // concrete sibling wins over a macro-constrained template (#2203).
247        candidates
248    } else {
249        filtered
250    }
251}
252
253/// The candidates whose parameter list has the call's arity and whose every
254/// parameter accepts the argument in that position under `matches`.
255fn 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    // Parameter metadata records the declared spelling, but it does not carry
297    // template substitution or constraint semantics. Once pointer shape and
298    // constness agree, a function template remains a live overload candidate:
299    // a type-only comparison such as `Vec256<T>` versus `Vec256<int>` cannot
300    // prove that deduction or a macro-shaped constraint fails (#2203).
301    if template_candidate {
302        return true;
303    }
304    let param_name = normalize_cpp_type_name(param);
305    match (resolve_type(&param_name), arg.unit.as_ref()) {
306        (Some(param_unit), Some(arg_unit)) => assignable(arg_unit, &param_unit),
307        _ => param_name == arg.name,
308    }
309}
310
311/// Whether an argument of type `arg` satisfies a parameter written `param`
312/// through a standard conversion. Asked only after name equality and
313/// derived-to-base have both failed for every candidate.
314///
315/// This is a closed list, not a conversion engine. Every entry is a conversion
316/// the analyzer can state from the two spellings alone -- no user-defined
317/// conversion operator, no converting-constructor lookup, no template
318/// deduction -- and every entry is here because a corpus call site is ambiguous
319/// without it:
320///
321/// - an owning contiguous range (`std::vector<T>`, `std::array<T, N>`)
322///   satisfies `std::span<T>` and `std::span<const T>`. `return DL_Group(ber,
323///   format);` with `const std::vector<uint8_t> ber` matched no candidate at
324///   all, so the filter kept the whole overload set (#2894).
325/// - `std::string` and a `char*` or `const char*` satisfy `std::string_view`.
326///
327/// Deliberately absent:
328///
329/// - `T*` to `std::span<T>`: viable only paired with a count argument, which is
330///   arity's decision rather than a per-parameter one.
331/// - `T[N]` to `std::span<T>`: an array argument arrives here spelled as its
332///   bare element type, because [`CppArgType`] records pointer depth and an
333///   array declarator adds none. Accepting it would accept every scalar `T`.
334/// - an alias of a listed container, such as Botan's `secure_vector<T>` for
335///   `std::vector<T>`: the argument carries the written spelling, and resolving
336///   the alias here would make `Montgomery_Int(m_params, std::move(t))` satisfy
337///   both its `secure_vector<word>` and its `std::span<const word>` overload.
338/// - the argument's own top-level `const`: [`CppArgType`] does not record it,
339///   so `const std::vector<uint8_t>` and `std::vector<uint8_t>` are one type
340///   here and both satisfy `std::span<uint8_t>`. Element `const` is recorded,
341///   and is checked.
342fn 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(&param_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
361/// The template arguments of `text` when it names a specialization of `base`.
362///
363/// Bracket aware like the parameter-list reader beside it: the argument split is
364/// [`cpp_split_top_level_commas`], so `std::array<std::pair<int, int>, 4>` reads
365/// as two arguments rather than three, and `std::vector<int>::iterator` is not a
366/// specialization of `std::vector` at all.
367fn 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
377/// The element type of an owning contiguous range: the `T` of `std::vector<T>`
378/// or of `std::array<T, N>`.
379fn 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
386/// Whether a `std::span` over `param_element` accepts a range over
387/// `arg_element`. A span may add `const` to its element type, never drop it.
388fn 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
462/// The byte offsets of a signature's outermost parameter-list parentheses.
463fn 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
486/// What a signature carries after its parameter list: the trailing
487/// cv-/ref-qualifiers and `noexcept` that the signature identity records
488/// (#1827).
489///
490/// Two member declarations with the same parameter types but different
491/// trailing qualifiers are distinct declarations, so a caller deciding whether
492/// one declaration hides another has to compare this alongside the parameter
493/// types.
494pub 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 == &param,
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    /// Every `call_expression` in `source`, in source order.
700    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    /// #2552 shape 4. `std::move` and `std::forward` forward one argument, and
723    /// its type is the call's type. Nothing else is that, including the same
724    /// names outside `std` and a call with the wrong argument count.
725    #[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    /// #2894 gap 2. `DL_Group(ber, format)` with a `std::vector<uint8_t> ber`
756    /// matched neither two-parameter constructor, so the filter kept both.
757    /// A contiguous owning range now satisfies a span over the same element,
758    /// including a span that adds `const`.
759    #[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    /// The near misses the issue asked for: a different element type, and an
784    /// element that would lose its `const`.
785    #[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    /// A standard conversion never outranks an exact match: the overload set
841    /// that has both keeps resolving to the exact one, as it did before the
842    /// conversion table existed.
843    #[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}