Skip to main content

comindly_core/lang/
rust.rs

1//! Rust syntax adapter (tree-sitter-rust). Modules follow files; methods attach via `impl`.
2
3use tree_sitter::Node;
4
5use super::{field_text, line, named_children, text, LangSpec, RawContract, Target};
6use crate::facts::{Args, Import, ImportItem, RefKind, TemplatePart, TypeSource, ValueExpr};
7use crate::model::SymbolKind;
8
9pub struct Rust;
10
11/// Generic wrappers whose first type argument is the value's real type.
12const WRAPPERS: &[&str] = &[
13    "Box", "Arc", "Rc", "Option", "Result", "RefCell", "Mutex", "RwLock", "Cow",
14];
15
16const VERBS: &[&str] = &["get", "post", "put", "patch", "delete", "head"];
17
18impl LangSpec for Rust {
19    fn receiver(&self) -> &'static str {
20        "self"
21    }
22
23    fn contracts(&self, node: Node, src: &[u8]) -> Vec<RawContract> {
24        match node.kind() {
25            // actix / rocket: `#[get("/x")] async fn h()`.
26            "function_item" => attribute_route(node, src).into_iter().collect(),
27            "call_expression" => {
28                let Some(f) = node.child_by_field_name("function") else {
29                    return Vec::new();
30                };
31                let Some(list) = node.child_by_field_name("arguments") else {
32                    return Vec::new();
33                };
34                let args = named_children(list);
35                // axum: `.route("/x", get(h).post(h2))`.
36                if f.kind() == "field_expression" && field_text(f, "field", src) == Some("route") {
37                    if let [p, chain] = args.as_slice() {
38                        if let Some(ValueExpr::Lit(path)) = value(*p, src) {
39                            let mut out = Vec::new();
40                            method_router(*chain, src, &mut out);
41                            return out
42                                .into_iter()
43                                .map(|(verb, handler)| RawContract::Handle {
44                                    receiver: Vec::new(),
45                                    method: verb.to_uppercase(),
46                                    path: ValueExpr::Lit(path.clone()),
47                                    handler,
48                                })
49                                .collect();
50                        }
51                    }
52                    return Vec::new();
53                }
54                // reqwest: `client.get(url)`, `reqwest::get(url)`.
55                let Some(mut p) = path(f, src) else {
56                    return Vec::new();
57                };
58                let Some(method) = p.pop() else {
59                    return Vec::new();
60                };
61                if !VERBS.contains(&method.as_str()) || args.is_empty() {
62                    return Vec::new();
63                }
64                vec![RawContract::Op {
65                    receiver: p,
66                    method,
67                    args: Args {
68                        positional: args.iter().map(|n| value(*n, src)).collect(),
69                        ..Default::default()
70                    },
71                }]
72            }
73            _ => Vec::new(),
74        }
75    }
76
77    fn value(&self, node: Node, src: &[u8]) -> Option<(String, ValueExpr)> {
78        if !matches!(node.kind(), "const_item" | "static_item") {
79            return None;
80        }
81        Some((
82            field_text(node, "name", src)?.to_string(),
83            value(node.child_by_field_name("value")?, src)?,
84        ))
85    }
86
87    fn def(&self, node: Node, src: &[u8], _in_class: bool) -> Option<(SymbolKind, String)> {
88        let kind = match node.kind() {
89            "function_item" if in_impl_or_trait(node) => SymbolKind::Method,
90            "function_item" => SymbolKind::Function,
91            "function_signature_item" => SymbolKind::Method,
92            "struct_item" | "union_item" => SymbolKind::Struct,
93            "enum_item" => SymbolKind::Enum,
94            "enum_variant" => SymbolKind::Constant,
95            "trait_item" => SymbolKind::Trait,
96            "type_item" => SymbolKind::TypeAlias,
97            // Inline `mod name { … }`; `mod name;` only declares a file module.
98            "mod_item" if node.child_by_field_name("body").is_some() => SymbolKind::Module,
99            _ => return None,
100        };
101        Some((kind, field_text(node, "name", src)?.to_string()))
102    }
103
104    fn owner(&self, def: Node, src: &[u8]) -> Option<String> {
105        if !matches!(def.kind(), "function_item" | "function_signature_item") {
106            return None;
107        }
108        let imp = def.parent()?.parent()?;
109        if imp.kind() != "impl_item" {
110            return None;
111        }
112        type_path(imp.child_by_field_name("type")?, src)?.pop()
113    }
114
115    fn owned_bases(&self, node: Node, src: &[u8]) -> Option<(String, RefKind, Vec<String>)> {
116        if node.kind() != "impl_item" {
117            return None;
118        }
119        let ty = type_path(node.child_by_field_name("type")?, src)?.pop()?;
120        let tr = type_path(node.child_by_field_name("trait")?, src)?;
121        Some((ty, RefKind::Implements, tr))
122    }
123
124    fn variable(&self, node: Node, src: &[u8]) -> Option<String> {
125        matches!(node.kind(), "const_item" | "static_item")
126            .then(|| field_text(node, "name", src).map(str::to_string))?
127    }
128
129    fn import(&self, node: Node, src: &[u8]) -> Option<Vec<Import>> {
130        if node.kind() != "use_declaration" {
131            return None;
132        }
133        let mut flat = Vec::new();
134        flatten_use(node.child_by_field_name("argument")?, &[], src, &mut flat);
135        Some(
136            flat.into_iter()
137                .map(|(module, item)| Import {
138                    module: module.join("::"),
139                    level: 0,
140                    items: vec![item],
141                    line: line(node),
142                    file: None,
143                })
144                .collect(),
145        )
146    }
147
148    fn reference(&self, node: Node, src: &[u8]) -> Option<(RefKind, Vec<String>)> {
149        match node.kind() {
150            "call_expression" => Some((
151                RefKind::Call,
152                path(node.child_by_field_name("function")?, src)?,
153            )),
154            "struct_expression" => Some((
155                RefKind::New,
156                type_path(node.child_by_field_name("name")?, src)?,
157            )),
158            _ => None,
159        }
160    }
161
162    fn assignment(&self, node: Node, src: &[u8]) -> Option<(Target, TypeSource)> {
163        match node.kind() {
164            "let_declaration" => {
165                let pat = node.child_by_field_name("pattern")?;
166                if pat.kind() != "identifier" {
167                    return None;
168                }
169                let target = Target::Var(text(pat, src).to_string());
170                if let Some(t) = node
171                    .child_by_field_name("type")
172                    .and_then(|t| type_path(t, src))
173                {
174                    return Some((target, TypeSource::Annotation(t)));
175                }
176                Some((target, value_type(node.child_by_field_name("value")?, src)?))
177            }
178            "field_declaration" => Some((
179                Target::Attr(field_text(node, "name", src)?.to_string()),
180                TypeSource::Annotation(type_path(node.child_by_field_name("type")?, src)?),
181            )),
182            _ => None,
183        }
184    }
185
186    fn bases(&self, def: Node, src: &[u8]) -> Vec<(RefKind, Vec<String>)> {
187        // Supertraits: `trait A: B + C`.
188        def.child_by_field_name("bounds")
189            .filter(|_| def.kind() == "trait_item")
190            .map(|b| {
191                named_children(b)
192                    .into_iter()
193                    .filter_map(|t| type_path(t, src))
194                    .map(|p| (RefKind::Inherits, p))
195                    .collect()
196            })
197            .unwrap_or_default()
198    }
199
200    fn returns(&self, def: Node, src: &[u8]) -> Option<Vec<String>> {
201        type_path(def.child_by_field_name("return_type")?, src)
202    }
203
204    fn params(&self, def: Node, src: &[u8]) -> Vec<(String, Vec<String>, bool)> {
205        let Some(ps) = def.child_by_field_name("parameters") else {
206            return Vec::new();
207        };
208        named_children(ps)
209            .into_iter()
210            .filter(|p| p.kind() == "parameter")
211            .filter_map(|p| {
212                let pat = p.child_by_field_name("pattern")?;
213                (pat.kind() == "identifier").then_some(())?;
214                Some((
215                    text(pat, src).to_string(),
216                    type_path(p.child_by_field_name("type")?, src)?,
217                    false,
218                ))
219            })
220            .collect()
221    }
222}
223
224fn in_impl_or_trait(node: Node) -> bool {
225    node.parent()
226        .and_then(|p| p.parent())
227        .is_some_and(|g| matches!(g.kind(), "impl_item" | "trait_item"))
228}
229
230/// Flatten a `use` tree into (module path, item) pairs.
231fn flatten_use(
232    node: Node,
233    prefix: &[String],
234    src: &[u8],
235    out: &mut Vec<(Vec<String>, ImportItem)>,
236) {
237    let with = |segs: Vec<String>| -> Vec<String> {
238        let mut p = prefix.to_vec();
239        p.extend(segs);
240        p
241    };
242    match node.kind() {
243        "identifier" | "crate" | "super" => {
244            let name = text(node, src).to_string();
245            if prefix.is_empty() {
246                out.push((vec![name.clone()], ImportItem::Module { alias: Some(name) }));
247            } else {
248                out.push((prefix.to_vec(), ImportItem::Name { name, alias: None }));
249            }
250        }
251        // `use a::b::{self, …}` binds the module `b`.
252        "self" => out.push((
253            prefix.to_vec(),
254            ImportItem::Module {
255                alias: prefix.last().cloned(),
256            },
257        )),
258        "scoped_identifier" => {
259            if let Some(mut segs) = segments(node, src) {
260                let name = segs.pop().unwrap_or_default();
261                if name == "self" {
262                    let alias = segs.last().cloned();
263                    out.push((with(segs), ImportItem::Module { alias }));
264                } else {
265                    out.push((with(segs), ImportItem::Name { name, alias: None }));
266                }
267            }
268        }
269        "use_as_clause" => {
270            let alias = field_text(node, "alias", src).map(str::to_string);
271            let Some(mut segs) = node
272                .child_by_field_name("path")
273                .and_then(|p| segments(p, src))
274            else {
275                return;
276            };
277            if segs.len() == 1 && prefix.is_empty() {
278                out.push((segs, ImportItem::Module { alias }));
279            } else {
280                let name = segs.pop().unwrap_or_default();
281                out.push((with(segs), ImportItem::Name { name, alias }));
282            }
283        }
284        "use_wildcard" => {
285            if let Some(segs) = named_children(node)
286                .into_iter()
287                .next()
288                .and_then(|p| segments(p, src))
289            {
290                out.push((with(segs), ImportItem::All));
291            }
292        }
293        "scoped_use_list" => {
294            let base = node
295                .child_by_field_name("path")
296                .and_then(|p| segments(p, src))
297                .unwrap_or_default();
298            if let Some(list) = node.child_by_field_name("list") {
299                let p = with(base);
300                for c in named_children(list) {
301                    flatten_use(c, &p, src, out);
302                }
303            }
304        }
305        "use_list" => {
306            for c in named_children(node) {
307                flatten_use(c, prefix, src, out);
308            }
309        }
310        _ => {}
311    }
312}
313
314/// `a::b::c` (incl. `crate`, `self`, `super`) as segments.
315fn segments(node: Node, src: &[u8]) -> Option<Vec<String>> {
316    match node.kind() {
317        "identifier" | "crate" | "self" | "super" | "type_identifier" => {
318            Some(vec![text(node, src).to_string()])
319        }
320        "scoped_identifier" | "scoped_type_identifier" => {
321            let mut p = match node.child_by_field_name("path") {
322                Some(p) => segments(p, src)?,
323                None => Vec::new(),
324            };
325            p.push(field_text(node, "name", src)?.to_string());
326            Some(p)
327        }
328        _ => None,
329    }
330}
331
332/// Expression path; a leading module `self::` becomes `<self>` (vs. the `self` receiver).
333fn attribute_route(func: Node, src: &[u8]) -> Option<RawContract> {
334    let mut prev = func.prev_named_sibling();
335    while let Some(a) = prev.filter(|p| matches!(p.kind(), "attribute_item" | "line_comment")) {
336        prev = a.prev_named_sibling();
337        let Some(attr) = named_children(a)
338            .into_iter()
339            .find(|c| c.kind() == "attribute")
340        else {
341            continue;
342        };
343        let kids = named_children(attr);
344        let verb = kids.first().and_then(|n| path(*n, src))?.pop()?;
345        if !VERBS.contains(&verb.as_str()) {
346            continue;
347        }
348        let lit = kids
349            .iter()
350            .find(|k| k.kind() == "token_tree")
351            .and_then(|t| {
352                named_children(*t)
353                    .into_iter()
354                    .find(|c| c.kind() == "string_literal")
355            })
356            .and_then(|s| value(s, src))?;
357        return Some(RawContract::Route {
358            handler: field_text(func, "name", src)?.to_string(),
359            receiver: Vec::new(),
360            method: verb.to_uppercase(),
361            path: lit,
362            client: false,
363        });
364    }
365    None
366}
367
368/// `get(h).post(h2)` → [(get, h), (post, h2)].
369fn method_router(node: Node, src: &[u8], out: &mut Vec<(String, Vec<String>)>) {
370    if node.kind() != "call_expression" {
371        return;
372    }
373    let (Some(f), Some(list)) = (
374        node.child_by_field_name("function"),
375        node.child_by_field_name("arguments"),
376    ) else {
377        return;
378    };
379    let (verb, inner) = match f.kind() {
380        "field_expression" => (
381            field_text(f, "field", src).unwrap_or_default().to_string(),
382            f.child_by_field_name("value"),
383        ),
384        _ => (
385            path(f, src).and_then(|mut p| p.pop()).unwrap_or_default(),
386            None,
387        ),
388    };
389    if let Some(i) = inner {
390        method_router(i, src, out);
391    }
392    if VERBS.contains(&verb.as_str()) {
393        let handler = named_children(list)
394            .first()
395            .filter(|h| h.kind() != "closure_expression")
396            .and_then(|h| path(*h, src))
397            .unwrap_or_default();
398        out.push((verb, handler));
399    }
400}
401
402/// Literals, names, and `format!` templates with inline or positional arguments.
403fn value(node: Node, src: &[u8]) -> Option<ValueExpr> {
404    match node.kind() {
405        "string_literal" | "raw_string_literal" => Some(ValueExpr::Lit(
406            named_children(node)
407                .iter()
408                .filter(|c| c.kind() == "string_content")
409                .map(|c| text(*c, src))
410                .collect(),
411        )),
412        "identifier" | "scoped_identifier" => path(node, src).map(ValueExpr::Name),
413        "reference_expression" => value(node.child_by_field_name("value")?, src),
414        "macro_invocation" if field_text(node, "macro", src) == Some("format") => {
415            let tt = named_children(node)
416                .into_iter()
417                .find(|c| c.kind() == "token_tree")?;
418            let kids = named_children(tt);
419            let fmt = value(*kids.first().filter(|k| k.kind() == "string_literal")?, src)?;
420            let ValueExpr::Lit(fmt) = fmt else {
421                return None;
422            };
423            // One slot per argument, so a non-identifier keeps its position.
424            let mut positional = kids[1..]
425                .iter()
426                .map(|k| (k.kind() == "identifier").then(|| text(*k, src).to_string()));
427            let mut parts = Vec::new();
428            let mut rest = fmt.as_str();
429            while let Some(open) = rest.find('{') {
430                parts.push(TemplatePart::Lit(rest[..open].to_string()));
431                let close = rest[open..].find('}').map_or(rest.len(), |c| open + c);
432                let name = rest[open + 1..close].split(':').next().unwrap_or("");
433                let expr = if name.is_empty() {
434                    positional.next().flatten().map(|n| vec![n])
435                } else {
436                    Some(vec![name.to_string()])
437                };
438                parts.push(TemplatePart::Expr(expr));
439                rest = rest.get(close + 1..).unwrap_or("");
440            }
441            parts.push(TemplatePart::Lit(rest.to_string()));
442            Some(ValueExpr::Template(parts))
443        }
444        _ => None,
445    }
446}
447
448fn path(node: Node, src: &[u8]) -> Option<Vec<String>> {
449    match node.kind() {
450        "identifier" | "self" | "crate" | "super" => Some(vec![text(node, src).to_string()]),
451        "scoped_identifier" => {
452            let mut p = segments(node, src)?;
453            if p.first().is_some_and(|s| s == "self") {
454                p[0] = "<self>".into();
455            }
456            Some(p)
457        }
458        "field_expression" => {
459            let mut p = path(node.child_by_field_name("value")?, src)?;
460            p.push(field_text(node, "field", src)?.to_string());
461            Some(p)
462        }
463        "generic_function" => path(node.child_by_field_name("function")?, src),
464        _ => None,
465    }
466}
467
468fn type_path(node: Node, src: &[u8]) -> Option<Vec<String>> {
469    match node.kind() {
470        "type_identifier" | "identifier" => Some(vec![text(node, src).to_string()]),
471        "scoped_type_identifier" | "scoped_identifier" => segments(node, src),
472        "reference_type" => type_path(node.child_by_field_name("type")?, src),
473        "generic_type" | "generic_type_with_turbofish" => {
474            let base = type_path(node.child_by_field_name("type")?, src)?;
475            if base.last().is_some_and(|b| WRAPPERS.contains(&b.as_str())) {
476                let args = node.child_by_field_name("type_arguments")?;
477                return named_children(args)
478                    .into_iter()
479                    .find_map(|a| type_path(a, src));
480            }
481            Some(base)
482        }
483        _ => None,
484    }
485}
486
487/// Type of a `let` initializer: a call (its return type) or a struct literal.
488fn value_type(v: Node, src: &[u8]) -> Option<TypeSource> {
489    match v.kind() {
490        "call_expression" => Some(TypeSource::Construct(path(
491            v.child_by_field_name("function")?,
492            src,
493        )?)),
494        "struct_expression" => Some(TypeSource::Construct(type_path(
495            v.child_by_field_name("name")?,
496            src,
497        )?)),
498        "try_expression" | "await_expression" | "reference_expression" => {
499            value_type(named_children(v).into_iter().next()?, src)
500        }
501        "identifier" => Some(TypeSource::Alias {
502            name: text(v, src).to_string(),
503            scope: String::new(),
504        }),
505        _ => None,
506    }
507}