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
186#[cfg(test)]
187fn cpp_filter_candidates_by_args(
188    candidates: Vec<CodeUnit>,
189    arg_types: &[Option<CppArgType>],
190    resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
191    assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
192) -> Vec<CodeUnit> {
193    cpp_filter_candidates_by_args_with_parameter_types(
194        candidates,
195        arg_types,
196        &|candidate| cpp_signature_param_types(candidate.signature().unwrap_or_default()),
197        &cpp_signature_is_template_candidate,
198        resolve_type,
199        assignable,
200    )
201}
202
203pub fn cpp_filter_candidates_by_args_with_parameter_types(
204    candidates: Vec<CodeUnit>,
205    arg_types: &[Option<CppArgType>],
206    parameter_types: &dyn Fn(&CodeUnit) -> Option<Vec<String>>,
207    is_template: &dyn Fn(&CodeUnit) -> bool,
208    resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
209    assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
210) -> Vec<CodeUnit> {
211    if candidates.len() <= 1 || arg_types.iter().any(Option::is_none) {
212        return candidates;
213    }
214    let args: Vec<&CppArgType> = arg_types.iter().flatten().collect();
215    debug_assert_eq!(args.len(), arg_types.len());
216
217    // Exact matches first; standard conversions decide only when nothing
218    // matches exactly. That order is C++'s own -- an identity conversion
219    // sequence beats every other one -- and it is what keeps
220    // `own(std::vector<uint8_t>)` winning over `own(std::span<const uint8_t>)`
221    // for a `std::vector<uint8_t>` argument now that the second is viable too.
222    let exact = cpp_candidates_matching(
223        &candidates,
224        &args,
225        parameter_types,
226        &|param, arg, template_candidate| {
227            cpp_param_matches_arg(param, arg, template_candidate, resolve_type, assignable)
228        },
229    );
230    let filtered = if exact.is_empty() {
231        cpp_candidates_matching(
232            &candidates,
233            &args,
234            parameter_types,
235            &|param, arg, template_candidate| {
236                cpp_param_matches_arg(param, arg, template_candidate, resolve_type, assignable)
237                    || cpp_standard_conversion_applies(arg, param)
238            },
239        )
240    } else {
241        exact
242    };
243    if filtered.is_empty() {
244        candidates
245    } else if filtered.iter().any(is_template) {
246        // A matching function template keeps the entire arity-compatible
247        // overload set alive. The parameter metadata has no template
248        // substitution or constraint ordering, so it cannot prove that a
249        // concrete sibling wins over a macro-constrained template (#2203).
250        candidates
251    } else {
252        filtered
253    }
254}
255
256/// The candidates whose parameter list has the call's arity and whose every
257/// parameter accepts the argument in that position under `matches`.
258fn cpp_candidates_matching(
259    candidates: &[CodeUnit],
260    args: &[&CppArgType],
261    parameter_types: &dyn Fn(&CodeUnit) -> Option<Vec<String>>,
262    matches: &dyn Fn(&str, &CppArgType, bool) -> bool,
263) -> Vec<CodeUnit> {
264    candidates
265        .iter()
266        .filter(|candidate| {
267            let template_candidate = cpp_signature_is_template_candidate(candidate);
268            parameter_types(candidate).is_some_and(|params| {
269                params.len() == args.len()
270                    && params
271                        .iter()
272                        .zip(args.iter())
273                        .all(|(param, arg)| matches(param, arg, template_candidate))
274            })
275        })
276        .cloned()
277        .collect()
278}
279fn cpp_signature_is_template_candidate(candidate: &CodeUnit) -> bool {
280    candidate
281        .signature()
282        .is_some_and(|signature| signature.trim_start().starts_with('<'))
283}
284
285fn cpp_param_matches_arg(
286    param: &str,
287    arg: &CppArgType,
288    template_candidate: bool,
289    resolve_type: &dyn Fn(&str) -> Option<CodeUnit>,
290    assignable: &dyn Fn(&CodeUnit, &CodeUnit) -> bool,
291) -> bool {
292    if cpp_type_text_pointer_depth(param) != arg.indirection {
293        return false;
294    }
295    if arg.pointee_const && !cpp_type_text_pointee_is_const(param) {
296        return false;
297    }
298    // Parameter metadata records the declared spelling, but it does not carry
299    // template substitution or constraint semantics. Once pointer shape and
300    // constness agree, a function template remains a live overload candidate:
301    // a type-only comparison such as `Vec256<T>` versus `Vec256<int>` cannot
302    // prove that deduction or a macro-shaped constraint fails (#2203).
303    if template_candidate {
304        return true;
305    }
306    let param_name = normalize_cpp_type_name(param);
307    match (resolve_type(&param_name), arg.unit.as_ref()) {
308        (Some(param_unit), Some(arg_unit)) => assignable(arg_unit, &param_unit),
309        _ => param_name == arg.name,
310    }
311}
312
313/// Whether an argument of type `arg` satisfies a parameter written `param`
314/// through a standard conversion. Asked only after name equality and
315/// derived-to-base have both failed for every candidate.
316///
317/// This is a closed list, not a conversion engine. Every entry is a conversion
318/// the analyzer can state from the two spellings alone -- no user-defined
319/// conversion operator, no converting-constructor lookup, no template
320/// deduction -- and every entry is here because a corpus call site is ambiguous
321/// without it:
322///
323/// - an owning contiguous range (`std::vector<T>`, `std::array<T, N>`)
324///   satisfies `std::span<T>` and `std::span<const T>`. `return DL_Group(ber,
325///   format);` with `const std::vector<uint8_t> ber` matched no candidate at
326///   all, so the filter kept the whole overload set (#2894).
327/// - `std::string` and a `char*` or `const char*` satisfy `std::string_view`.
328///
329/// Deliberately absent:
330///
331/// - `T*` to `std::span<T>`: viable only paired with a count argument, which is
332///   arity's decision rather than a per-parameter one.
333/// - `T[N]` to `std::span<T>`: an array argument arrives here spelled as its
334///   bare element type, because [`CppArgType`] records pointer depth and an
335///   array declarator adds none. Accepting it would accept every scalar `T`.
336/// - an alias of a listed container, such as Botan's `secure_vector<T>` for
337///   `std::vector<T>`: the argument carries the written spelling, and resolving
338///   the alias here would make `Montgomery_Int(m_params, std::move(t))` satisfy
339///   both its `secure_vector<word>` and its `std::span<const word>` overload.
340/// - the argument's own top-level `const`: [`CppArgType`] does not record it,
341///   so `const std::vector<uint8_t>` and `std::vector<uint8_t>` are one type
342///   here and both satisfy `std::span<uint8_t>`. Element `const` is recorded,
343///   and is checked.
344fn cpp_standard_conversion_applies(arg: &CppArgType, param: &str) -> bool {
345    if cpp_type_text_pointer_depth(param) != 0 {
346        return false;
347    }
348    let param_name = normalize_cpp_type_name(param);
349    let span_element = cpp_template_arguments(&param_name, "std::span")
350        .and_then(|arguments| arguments.first().copied());
351    if let Some(span_element) = span_element {
352        return arg.indirection == 0
353            && cpp_contiguous_range_element(&arg.name)
354                .is_some_and(|element| cpp_span_element_accepts(span_element, element));
355    }
356    if param_name == "std::string_view" {
357        return (arg.indirection == 0 && arg.name == "std::string")
358            || (arg.indirection == 1 && arg.name == "char");
359    }
360    false
361}
362
363/// The template arguments of `text` when it names a specialization of `base`.
364///
365/// Bracket aware like the parameter-list reader beside it: the argument split is
366/// [`cpp_split_top_level_commas`], so `std::array<std::pair<int, int>, 4>` reads
367/// as two arguments rather than three, and `std::vector<int>::iterator` is not a
368/// specialization of `std::vector` at all.
369fn cpp_template_arguments<'a>(text: &'a str, base: &str) -> Option<Vec<&'a str>> {
370    let inner = text
371        .strip_prefix(base)?
372        .trim_start()
373        .strip_prefix('<')?
374        .trim_end()
375        .strip_suffix('>')?;
376    Some(cpp_split_top_level_commas(inner).collect())
377}
378
379/// The element type of an owning contiguous range: the `T` of `std::vector<T>`
380/// or of `std::array<T, N>`.
381fn cpp_contiguous_range_element(name: &str) -> Option<&str> {
382    ["std::vector", "std::array"]
383        .into_iter()
384        .find_map(|base| cpp_template_arguments(name, base))
385        .and_then(|arguments| arguments.first().copied())
386}
387
388/// Whether a `std::span` over `param_element` accepts a range over
389/// `arg_element`. A span may add `const` to its element type, never drop it.
390fn cpp_span_element_accepts(param_element: &str, arg_element: &str) -> bool {
391    cpp_type_text_pointer_depth(param_element) == cpp_type_text_pointer_depth(arg_element)
392        && normalize_cpp_type_name(param_element) == normalize_cpp_type_name(arg_element)
393        && (cpp_type_text_pointee_is_const(param_element)
394            || !cpp_type_text_pointee_is_const(arg_element))
395}
396
397fn cpp_type_text_pointee_is_const(text: &str) -> bool {
398    let normalized = normalize_cpp_whitespace(text);
399    let base = cpp_type_text_base(&normalized).trim();
400    base.starts_with("const ") || base.ends_with(" const")
401}
402
403fn cpp_type_text_base(text: &str) -> &str {
404    text[..cpp_type_text_shape(text).0].trim()
405}
406
407pub fn cpp_split_top_level_commas(value: &str) -> impl Iterator<Item = &str> {
408    struct TopLevelCommaSplit<'a> {
409        value: &'a str,
410        start: usize,
411        angle: usize,
412        paren: usize,
413        brace: usize,
414        bracket: usize,
415    }
416
417    impl<'a> Iterator for TopLevelCommaSplit<'a> {
418        type Item = &'a str;
419
420        fn next(&mut self) -> Option<Self::Item> {
421            if self.start > self.value.len() {
422                return None;
423            }
424            for (offset, ch) in self.value[self.start..].char_indices() {
425                let absolute = self.start + offset;
426                match ch {
427                    '<' => self.angle += 1,
428                    '>' => self.angle = self.angle.saturating_sub(1),
429                    '(' => self.paren += 1,
430                    ')' => self.paren = self.paren.saturating_sub(1),
431                    '{' => self.brace += 1,
432                    '}' => self.brace = self.brace.saturating_sub(1),
433                    '[' => self.bracket += 1,
434                    ']' => self.bracket = self.bracket.saturating_sub(1),
435                    ',' if self.angle == 0
436                        && self.paren == 0
437                        && self.brace == 0
438                        && self.bracket == 0 =>
439                    {
440                        let item = self.value[self.start..absolute].trim();
441                        self.start = absolute + ch.len_utf8();
442                        return Some(item);
443                    }
444                    _ => {}
445                }
446            }
447            let item = self.value[self.start..].trim();
448            self.start = self.value.len() + 1;
449            Some(item)
450        }
451    }
452
453    TopLevelCommaSplit {
454        value,
455        start: 0,
456        angle: 0,
457        paren: 0,
458        brace: 0,
459        bracket: 0,
460    }
461    .filter(|item| !item.is_empty())
462}
463
464/// The byte offsets of a signature's outermost parameter-list parentheses.
465fn cpp_signature_parameter_span(signature: &str) -> Option<(usize, usize)> {
466    let open = signature.find('(')?;
467    let mut depth = 0i32;
468    for (offset, ch) in signature[open..].char_indices() {
469        match ch {
470            '(' => depth += 1,
471            ')' => {
472                depth -= 1;
473                if depth == 0 {
474                    return Some((open, open + offset));
475                }
476            }
477            _ => {}
478        }
479    }
480    None
481}
482
483fn cpp_signature_parameter_text(signature: &str) -> Option<&str> {
484    let (open, close) = cpp_signature_parameter_span(signature)?;
485    Some(signature[open + 1..close].trim())
486}
487
488/// What a signature carries after its parameter list: the trailing
489/// cv-/ref-qualifiers and `noexcept` that the signature identity records
490/// (#1827).
491///
492/// Two member declarations with the same parameter types but different
493/// trailing qualifiers are distinct declarations, so a caller deciding whether
494/// one declaration hides another has to compare this alongside the parameter
495/// types.
496pub fn cpp_signature_trailing_qualifiers(signature: &str) -> &str {
497    match cpp_signature_parameter_span(signature) {
498        Some((_, close)) => signature[close + 1..].trim(),
499        None => "",
500    }
501}
502
503#[derive(Clone, Copy, Debug, PartialEq, Eq)]
504pub enum CppRefQualifier {
505    Lvalue,
506    Rvalue,
507}
508
509/// The member ref-qualifier carried by an indexed identity signature.
510///
511/// Declaration extraction emits cv-qualifiers before the ref-qualifier, in
512/// the same order as the C++ declarator nodes. Reading that normalized product
513/// keeps call applicability on the declaration AST's answer without reparsing
514/// source text at every call site.
515pub fn cpp_signature_ref_qualifier(signature: &str) -> Option<CppRefQualifier> {
516    let mut suffix = cpp_signature_trailing_qualifiers(signature);
517    loop {
518        if let Some(rest) = suffix.strip_prefix("const ") {
519            suffix = rest;
520        } else if let Some(rest) = suffix.strip_prefix("volatile ") {
521            suffix = rest;
522        } else {
523            break;
524        }
525    }
526    if suffix == "&&" || suffix.starts_with("&& ") {
527        Some(CppRefQualifier::Rvalue)
528    } else if suffix == "&" || suffix.starts_with("& ") {
529        Some(CppRefQualifier::Lvalue)
530    } else {
531        None
532    }
533}
534
535fn cpp_parameter_name_token(token: &str) -> bool {
536    let token = token.trim_start_matches('*').trim_start_matches('&').trim();
537    token
538        .chars()
539        .next()
540        .is_some_and(|ch| ch == '_' || ch.is_ascii_lowercase())
541        && token
542            .chars()
543            .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
544}
545
546fn strip_tag_type_prefix(value: &str) -> &str {
547    let value = value.trim_start_matches("const ");
548    value
549        .strip_prefix("struct ")
550        .or_else(|| value.strip_prefix("class "))
551        .or_else(|| value.strip_prefix("enum "))
552        .unwrap_or(value)
553        .trim()
554}
555
556fn cpp_number_literal_is_float(text: &str) -> bool {
557    let text = text.trim();
558    text.contains('.') || text.contains('e') || text.contains('E') || text.ends_with(['f', 'F'])
559}
560
561#[cfg(test)]
562mod tests {
563    use super::*;
564    use brokk_bifrost_core::analyzer::ProjectFile;
565    use brokk_bifrost_core::analyzer::model::CodeUnitType;
566
567    fn test_file() -> ProjectFile {
568        ProjectFile::new(std::env::temp_dir(), "test.cpp")
569    }
570
571    fn function(name: &str, signature: &str) -> CodeUnit {
572        CodeUnit::with_signature(
573            test_file(),
574            CodeUnitType::Function,
575            "ns",
576            name,
577            Some(signature.to_string()),
578            false,
579        )
580    }
581
582    fn class(name: &str) -> CodeUnit {
583        CodeUnit::new(test_file(), CodeUnitType::Class, "ns", name)
584    }
585
586    #[test]
587    fn cpp_filter_candidates_matches_named_unindexed_types() {
588        let candidates = vec![
589            function("format", "std::string format(const std::string& value)"),
590            function("format", "std::string format(int value)"),
591        ];
592        let filtered = cpp_filter_candidates_by_args(
593            candidates,
594            &[Some(CppArgType {
595                name: "std::string".to_string(),
596                unit: None,
597                indirection: 0,
598                pointee_const: false,
599            })],
600            &|_| None,
601            &|_, _| false,
602        );
603        assert_eq!(1, filtered.len());
604        assert!(filtered[0].signature().unwrap().contains("std::string&"));
605    }
606
607    #[test]
608    fn cpp_filter_candidates_matches_assignable_units() {
609        let arg = class("Arg");
610        let param = class("Param");
611        let filtered = cpp_filter_candidates_by_args(
612            vec![function("take", "void take(Param value)")],
613            &[Some(CppArgType {
614                name: "Arg".to_string(),
615                unit: Some(arg.clone()),
616                indirection: 0,
617                pointee_const: false,
618            })],
619            &|name| (name == "Param").then(|| param.clone()),
620            &|from, to| from == &arg && to == &param,
621        );
622        assert_eq!(1, filtered.len());
623    }
624
625    #[test]
626    fn cpp_filter_candidates_rejects_pointer_depth_mismatch() {
627        let candidates = vec![
628            function("take", "void take(int* value)"),
629            function("take", "void take(int value)"),
630        ];
631        let filtered = cpp_filter_candidates_by_args(
632            candidates,
633            &[Some(CppArgType {
634                name: "int".to_string(),
635                unit: None,
636                indirection: 0,
637                pointee_const: false,
638            })],
639            &|_| None,
640            &|_, _| false,
641        );
642        assert_eq!(1, filtered.len());
643        assert_eq!("void take(int value)", filtered[0].signature().unwrap());
644    }
645
646    #[test]
647    fn cpp_filter_candidates_uses_const_string_literal_pointer_evidence() {
648        let literal = Some(CppArgType {
649            name: "char".to_string(),
650            unit: None,
651            indirection: 1,
652            pointee_const: true,
653        });
654        let direct = cpp_filter_candidates_by_args(
655            vec![
656                function("select", "int select(int value)"),
657                function("select", "int select(const char* value)"),
658            ],
659            std::slice::from_ref(&literal),
660            &|_| None,
661            &|_, _| false,
662        );
663        assert_eq!(1, direct.len());
664        assert_eq!(
665            "int select(const char* value)",
666            direct[0].signature().unwrap()
667        );
668
669        for candidates in [
670            vec![
671                function("select", "int select(int value)"),
672                function("select", "int select(char* value)"),
673            ],
674            vec![
675                function("format", "int format(int value)"),
676                function("format", "int format(std::string value)"),
677            ],
678        ] {
679            let filtered = cpp_filter_candidates_by_args(
680                candidates.clone(),
681                std::slice::from_ref(&literal),
682                &|_| None,
683                &|_, _| false,
684            );
685            assert_eq!(
686                candidates, filtered,
687                "unmodeled or invalid conversions must remain conservative"
688            );
689        }
690    }
691
692    #[test]
693    fn cpp_parameter_type_keeps_pointer_const_distinct_from_pointee_const() {
694        assert_eq!("char*", cpp_parameter_type_text("char * const value"));
695        assert_eq!(
696            "const char*",
697            cpp_parameter_type_text("const char * const value")
698        );
699        assert_eq!("char", normalize_cpp_type_name("char * const"));
700    }
701
702    #[test]
703    fn cpp_filter_candidates_keeps_all_for_unknown_arguments() {
704        let candidates = vec![
705            function("format", "void format(std::string value)"),
706            function("format", "void format(int value)"),
707        ];
708        let filtered =
709            cpp_filter_candidates_by_args(candidates.clone(), &[None], &|_| None, &|_, _| false);
710        assert_eq!(candidates, filtered);
711    }
712
713    #[test]
714    fn cpp_filter_candidates_keeps_all_when_no_candidate_matches() {
715        let candidates = vec![
716            function("format", "void format(std::string value)"),
717            function("format", "void format(int value)"),
718        ];
719        let filtered = cpp_filter_candidates_by_args(
720            candidates.clone(),
721            &[Some(CppArgType {
722                name: "double".to_string(),
723                unit: None,
724                indirection: 0,
725                pointee_const: false,
726            })],
727            &|_| None,
728            &|_, _| false,
729        );
730        assert_eq!(candidates, filtered);
731    }
732
733    /// Every `call_expression` in `source`, in source order.
734    fn call_expressions(tree: &tree_sitter::Tree) -> Vec<Node<'_>> {
735        let mut calls = Vec::new();
736        let mut stack = vec![tree.root_node()];
737        while let Some(node) = stack.pop() {
738            if node.kind() == "call_expression" {
739                calls.push(node);
740            }
741            let mut cursor = node.walk();
742            stack.extend(node.named_children(&mut cursor));
743        }
744        calls.sort_by_key(Node::start_byte);
745        calls
746    }
747
748    fn parse(source: &str) -> tree_sitter::Tree {
749        let mut parser = tree_sitter::Parser::new();
750        parser
751            .set_language(&tree_sitter_cpp::LANGUAGE.into())
752            .expect("the C++ grammar loads");
753        parser.parse(source, None).expect("the fixture parses")
754    }
755
756    /// #2552 shape 4. `std::move` and `std::forward` forward one argument, and
757    /// its type is the call's type. Nothing else is that, including the same
758    /// names outside `std` and a call with the wrong argument count.
759    #[test]
760    fn a_forwarding_call_reports_the_one_argument_it_forwards() {
761        let source = r#"void f() {
762   sink(std::move(a));
763   sink(std::forward<T>(b));
764   sink(std::move(c, 1));
765   sink(std::swap(d, e));
766   sink(move(g));
767   sink(other::move(h));
768   sink(std::vector<int>(i));
769}
770"#;
771        let tree = parse(source);
772        let forwarded = call_expressions(&tree)
773            .into_iter()
774            .filter_map(|call| cpp_forwarding_call_argument(call, source))
775            .map(|argument| cpp_node_text(argument, source).to_string())
776            .collect::<Vec<_>>();
777        assert_eq!(forwarded, vec!["a".to_string(), "b".to_string()]);
778    }
779
780    fn value_arg(name: &str) -> Option<CppArgType> {
781        Some(CppArgType {
782            name: name.to_string(),
783            unit: None,
784            indirection: 0,
785            pointee_const: false,
786        })
787    }
788
789    /// #2894 gap 2. `DL_Group(ber, format)` with a `std::vector<uint8_t> ber`
790    /// matched neither two-parameter constructor, so the filter kept both.
791    /// A contiguous owning range now satisfies a span over the same element,
792    /// including a span that adds `const`.
793    #[test]
794    fn a_contiguous_range_satisfies_a_span_over_its_element() {
795        for arg in ["std::vector<uint8_t>", "std::array<uint8_t, 16>"] {
796            for param in ["std::span<const uint8_t>", "std::span<uint8_t>"] {
797                let candidates = vec![
798                    function("take", &format!("void take({param} bytes)")),
799                    function("take", "void take(int count)"),
800                ];
801                let filtered = cpp_filter_candidates_by_args(
802                    candidates,
803                    &[value_arg(arg)],
804                    &|_| None,
805                    &|_, _| false,
806                );
807                assert_eq!(filtered.len(), 1, "{arg} -> {param}");
808                assert!(
809                    filtered[0].signature().unwrap().contains(param),
810                    "{arg} -> {param}: {:?}",
811                    filtered[0].signature()
812                );
813            }
814        }
815    }
816
817    /// The near misses the issue asked for: a different element type, and an
818    /// element that would lose its `const`.
819    #[test]
820    fn a_range_over_another_element_does_not_satisfy_a_span() {
821        for (arg, param) in [
822            ("std::vector<int>", "std::span<const uint8_t>"),
823            ("std::vector<const uint8_t>", "std::span<uint8_t>"),
824            ("std::vector<uint8_t>", "std::span<const uint8_t*>"),
825            ("std::deque<uint8_t>", "std::span<const uint8_t>"),
826        ] {
827            let candidates = vec![
828                function("take", &format!("void take({param} bytes)")),
829                function("take", "void take(int count)"),
830            ];
831            let filtered = cpp_filter_candidates_by_args(
832                candidates.clone(),
833                &[value_arg(arg)],
834                &|_| None,
835                &|_, _| false,
836            );
837            assert_eq!(
838                candidates, filtered,
839                "{arg} must not satisfy {param}, so every candidate stays"
840            );
841        }
842    }
843
844    #[test]
845    fn an_owned_string_and_a_character_pointer_satisfy_a_string_view() {
846        let literal = Some(CppArgType {
847            name: "char".to_string(),
848            unit: None,
849            indirection: 1,
850            pointee_const: true,
851        });
852        for arg in [value_arg("std::string"), literal] {
853            let filtered = cpp_filter_candidates_by_args(
854                vec![
855                    function("label", "void label(std::string_view text)"),
856                    function("label", "void label(int count)"),
857                ],
858                std::slice::from_ref(&arg),
859                &|_| None,
860                &|_, _| false,
861            );
862            assert_eq!(filtered.len(), 1, "{:?}", arg.as_ref().map(|arg| &arg.name));
863            assert!(
864                filtered[0]
865                    .signature()
866                    .unwrap()
867                    .contains("std::string_view"),
868                "{:?}",
869                filtered[0].signature()
870            );
871        }
872    }
873
874    /// A standard conversion never outranks an exact match: the overload set
875    /// that has both keeps resolving to the exact one, as it did before the
876    /// conversion table existed.
877    #[test]
878    fn an_exact_match_outranks_a_standard_conversion() {
879        let candidates = vec![
880            function("own", "void own(std::vector<uint8_t> bytes)"),
881            function("own", "void own(std::span<const uint8_t> bytes)"),
882        ];
883        let filtered = cpp_filter_candidates_by_args(
884            candidates,
885            &[value_arg("std::vector<uint8_t>")],
886            &|_| None,
887            &|_, _| false,
888        );
889        assert_eq!(filtered.len(), 1);
890        assert_eq!(
891            "void own(std::vector<uint8_t> bytes)",
892            filtered[0].signature().unwrap()
893        );
894    }
895
896    #[test]
897    fn cpp_filter_candidates_keeps_templates_when_only_type_shape_is_unknown() {
898        let candidates = vec![
899            function("take", "void take(Vec256<float> value)"),
900            function("take", "<typename T>(Vec256<T>)"),
901        ];
902        let filtered = cpp_filter_candidates_by_args(
903            candidates.clone(),
904            &[Some(CppArgType {
905                name: "Vec256<int>".to_string(),
906                unit: Some(class("Vec256")),
907                indirection: 0,
908                pointee_const: false,
909            })],
910            &|_| None,
911            &|_, _| false,
912        );
913        assert_eq!(filtered, candidates);
914    }
915
916    #[test]
917    fn signature_ref_qualifier_comes_from_the_ast_normalized_suffix() {
918        assert_eq!(
919            cpp_signature_ref_qualifier("() const & noexcept"),
920            Some(CppRefQualifier::Lvalue)
921        );
922        assert_eq!(
923            cpp_signature_ref_qualifier("(int) volatile && noexcept"),
924            Some(CppRefQualifier::Rvalue)
925        );
926        assert_eq!(cpp_signature_ref_qualifier("() const noexcept"), None);
927        assert_eq!(
928            cpp_signature_ref_qualifier("() noexcept(left && right)"),
929            None
930        );
931    }
932}