llmcc_rust/descriptor/
call.rs

1use std::mem;
2
3use llmcc_core::context::CompileUnit;
4use llmcc_core::ir::{HirId, HirNode};
5use tree_sitter::Node;
6
7use super::function::{parse_type_expr, TypeExpr};
8
9/// Description of a function-style call expression discovered in the source.
10#[derive(Debug, Clone)]
11pub struct CallDescriptor {
12    /// HIR identifier for the call expression.
13    pub hir_id: HirId,
14    /// Best-effort classification of the call target.
15    pub target: CallTarget,
16    /// Raw argument snippets in call order.
17    pub arguments: Vec<CallArgument>,
18    /// Fully-qualified name of the function/method that owns this call, if any.
19    pub enclosing_function: Option<String>,
20}
21
22/// Information about the call target expression.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum CallTarget {
25    /// A path-based call such as `foo::bar()`.
26    Path {
27        segments: Vec<String>,
28        generics: Vec<TypeExpr>,
29    },
30    /// A method-style call `receiver.method()`.
31    Method {
32        receiver: String,
33        method: String,
34        generics: Vec<TypeExpr>,
35    },
36    /// A chained sequence like `obj.f1().f2::<T>()` packed as a single call.
37    Chain {
38        base: String,
39        segments: Vec<ChainSegment>,
40    },
41    /// Anything we could not recognise (stored verbatim).
42    Unknown(String),
43}
44
45/// Component of a chained call.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct ChainSegment {
48    pub method: String,
49    pub generics: Vec<TypeExpr>,
50    pub arguments: Vec<CallArgument>,
51}
52
53/// Lightweight view of call arguments.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct CallArgument {
56    pub text: String,
57}
58
59impl CallDescriptor {
60    pub fn from_call<'tcx>(
61        unit: CompileUnit<'tcx>,
62        node: &HirNode<'tcx>,
63        enclosing_function: Option<String>,
64    ) -> Self {
65        let ts_node = node.inner_ts_node();
66        let function_node = ts_node.child_by_field_name("function");
67        let call_generics = ts_node
68            .child_by_field_name("type_arguments")
69            .map(|n| parse_type_arguments(unit, n))
70            .unwrap_or_default();
71
72        let target = function_node
73            .and_then(|func| parse_chain(unit, func, call_generics.clone()))
74            .or_else(|| parse_chain(unit, ts_node, call_generics.clone()))
75            .map(|(base, segments)| CallTarget::Chain { base, segments })
76            .unwrap_or_else(|| match function_node {
77                Some(func) => parse_call_target(unit, func, call_generics.clone()),
78                None => CallTarget::Unknown(clean(&node_text(unit, ts_node))),
79            });
80
81        let arguments = ts_node
82            .child_by_field_name("arguments")
83            .map(|args| parse_arguments(unit, args))
84            .unwrap_or_default();
85
86        CallDescriptor {
87            hir_id: node.hir_id(),
88            target,
89            arguments,
90            enclosing_function,
91        }
92    }
93}
94
95fn parse_arguments<'tcx>(unit: CompileUnit<'tcx>, args_node: Node<'tcx>) -> Vec<CallArgument> {
96    let mut cursor = args_node.walk();
97    args_node
98        .named_children(&mut cursor)
99        .map(|arg| CallArgument {
100            text: clean(&node_text(unit, arg)),
101        })
102        .collect()
103}
104
105fn parse_call_target<'tcx>(
106    unit: CompileUnit<'tcx>,
107    node: Node<'tcx>,
108    call_generics: Vec<TypeExpr>,
109) -> CallTarget {
110    match node.kind() {
111        "identifier" | "scoped_identifier" | "type_identifier" => {
112            let segments: Vec<String> = clean(&node_text(unit, node))
113                .split("::")
114                .map(|s| s.to_string())
115                .collect();
116            CallTarget::Path {
117                segments,
118                generics: call_generics,
119            }
120        }
121        "generic_type" => {
122            let base = node.child_by_field_name("type").unwrap_or(node);
123            let mut segments: Vec<String> = clean(&node_text(unit, base))
124                .split("::")
125                .map(|s| s.to_string())
126                .collect();
127            if segments.is_empty() {
128                segments.push(clean(&node_text(unit, base)));
129            }
130            let generics = node
131                .child_by_field_name("type_arguments")
132                .map(|args| parse_type_arguments(unit, args))
133                .unwrap_or(call_generics);
134            CallTarget::Path { segments, generics }
135        }
136        "generic_function" => {
137            let generics = node
138                .child_by_field_name("type_arguments")
139                .map(|args| parse_type_arguments(unit, args))
140                .unwrap_or_default();
141            let inner = node
142                .child_by_field_name("function")
143                .unwrap_or(node.child(0).unwrap_or(node));
144            let mut target = parse_call_target(unit, inner, call_generics);
145            match &mut target {
146                CallTarget::Path { generics: g, .. } => *g = generics,
147                CallTarget::Method { generics: g, .. } => *g = generics,
148                _ => {}
149            }
150            target
151        }
152        "field_expression" => {
153            let receiver = node
154                .child_by_field_name("value")
155                .map(|n| clean(&node_text(unit, n)))
156                .unwrap_or_else(|| clean(&node_text(unit, node)));
157            let method = node
158                .child_by_field_name("field")
159                .map(|n| clean(&node_text(unit, n)))
160                .unwrap_or_default();
161            let generics = node
162                .child_by_field_name("type_arguments")
163                .map(|n| parse_type_arguments(unit, n))
164                .unwrap_or(call_generics);
165            CallTarget::Method {
166                receiver,
167                method,
168                generics,
169            }
170        }
171        _ => {
172            let text = clean(&node_text(unit, node));
173            if let Some((receiver, method)) = parse_method_from_text(&text) {
174                CallTarget::Method {
175                    receiver,
176                    method,
177                    generics: call_generics,
178                }
179            } else {
180                CallTarget::Unknown(text)
181            }
182        }
183    }
184}
185
186fn parse_chain<'tcx>(
187    unit: CompileUnit<'tcx>,
188    mut node: Node<'tcx>,
189    call_generics: Vec<TypeExpr>,
190) -> Option<(String, Vec<ChainSegment>)> {
191    let mut segments = Vec::new();
192    let mut pending_generics = call_generics;
193    let mut pending_arguments = Vec::new();
194
195    loop {
196        match node.kind() {
197            "call_expression" => {
198                pending_generics = node
199                    .child_by_field_name("type_arguments")
200                    .map(|n| parse_type_arguments(unit, n))
201                    .unwrap_or_default();
202                pending_arguments = node
203                    .child_by_field_name("arguments")
204                    .map(|args| parse_arguments(unit, args))
205                    .unwrap_or_default();
206                let function_node = node.child_by_field_name("function")?;
207                node = function_node;
208            }
209            "generic_function" => {
210                pending_generics = node
211                    .child_by_field_name("type_arguments")
212                    .map(|n| parse_type_arguments(unit, n))
213                    .unwrap_or_default();
214                let inner_function = node.child_by_field_name("function")?;
215                node = inner_function;
216            }
217            "field_expression" => {
218                let method = node
219                    .child_by_field_name("field")
220                    .map(|n| clean(&node_text(unit, n)))
221                    .unwrap_or_default();
222                let generics = mem::take(&mut pending_generics);
223                let arguments = mem::take(&mut pending_arguments);
224                segments.push(ChainSegment {
225                    method,
226                    generics,
227                    arguments,
228                });
229                let argument_node = node.child_by_field_name("value")?;
230                node = argument_node;
231            }
232            _ => {
233                if segments.len() < 2 {
234                    return None;
235                }
236                let base = clean(&node_text(unit, node));
237                segments.reverse();
238                return Some((base, segments));
239            }
240        }
241    }
242}
243
244fn parse_type_arguments<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> Vec<TypeExpr> {
245    let mut args = Vec::new();
246    let mut cursor = node.walk();
247    for child in node.named_children(&mut cursor) {
248        match child.kind() {
249            "type_argument" => {
250                if let Some(inner) = child.child_by_field_name("type") {
251                    args.push(parse_type_expr(unit, inner));
252                }
253            }
254            kind if is_type_node(kind) => {
255                args.push(parse_type_expr(unit, child));
256            }
257            _ => {}
258        }
259    }
260    if args.is_empty() {
261        let mut cursor = node.walk();
262        for child in node.children(&mut cursor) {
263            if is_type_node(child.kind()) {
264                args.push(parse_type_expr(unit, child));
265            }
266        }
267    }
268    args
269}
270
271fn is_type_node(kind: &str) -> bool {
272    matches!(
273        kind,
274        "type_identifier"
275            | "scoped_type_identifier"
276            | "generic_type"
277            | "reference_type"
278            | "tuple_type"
279            | "primitive_type"
280            | "impl_trait_type"
281    )
282}
283
284fn node_text<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> String {
285    unit.file().get_text(node.start_byte(), node.end_byte())
286}
287
288fn clean(text: &str) -> String {
289    let mut out = String::new();
290    let mut last_was_ws = false;
291    for ch in text.chars() {
292        if ch.is_whitespace() {
293            if !last_was_ws && !out.is_empty() {
294                out.push(' ');
295            }
296            last_was_ws = true;
297        } else {
298            out.push(ch);
299            last_was_ws = false;
300        }
301    }
302    out.trim().to_string()
303}
304
305fn parse_method_from_text(text: &str) -> Option<(String, String)> {
306    let idx = text.rfind('.')?;
307    let (receiver, method_part) = text.split_at(idx);
308    Some((
309        receiver.trim().to_string(),
310        method_part.trim_start_matches('.').to_string(),
311    ))
312}