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