1use tree_sitter::{Language, Parser, Tree};
2
3use crate::core::code_graph::CodeGraph;
4use crate::core::moniker::Moniker;
5
6use crate::lang::canonical_walker::CanonicalWalker;
7
8pub mod build;
9mod canonicalize;
10mod kinds;
11mod strategy;
12
13use std::collections::HashMap;
14
15use canonicalize::compute_module_moniker;
16use strategy::{Strategy, collect_callable_table, collect_local_mods};
17
18pub fn parse(source: &str) -> Tree {
19 let mut parser = Parser::new();
20 let language: Language = tree_sitter_rust::LANGUAGE.into();
21 parser
22 .set_language(&language)
23 .expect("failed to load tree-sitter Rust grammar");
24 parser
25 .parse(source, None)
26 .expect("tree-sitter parse returned None on a non-cancelled call")
27}
28
29#[derive(Clone, Debug, Default)]
30pub struct Presets {}
31
32pub fn extract(
33 uri: &str,
34 source: &str,
35 anchor: &Moniker,
36 deep: bool,
37 _presets: &Presets,
38) -> CodeGraph {
39 let module = compute_module_moniker(anchor, uri);
40 let (def_cap, ref_cap) = CodeGraph::capacity_for_source(source.len());
41 let mut graph = CodeGraph::with_capacity(module.clone(), kinds::MODULE, def_cap, ref_cap);
42 let tree = parse(source);
43 let local_mods = collect_local_mods(tree.root_node(), source.as_bytes());
44 let mut callable_table: HashMap<(Moniker, Vec<u8>), Vec<u8>> = HashMap::new();
45 collect_callable_table(
46 tree.root_node(),
47 source.as_bytes(),
48 &module,
49 &mut callable_table,
50 );
51 let strat = Strategy {
52 module: module.clone(),
53 source_bytes: source.as_bytes(),
54 deep,
55 local_mods,
56 local_scope: std::cell::RefCell::new(Vec::new()),
57 type_params: std::cell::RefCell::new(Vec::new()),
58 callable_table,
59 in_trait_impl: std::cell::Cell::new(false),
60 imported_modules: std::cell::RefCell::new(std::collections::HashSet::new()),
61 };
62 let walker = CanonicalWalker::new(&strat, source.as_bytes());
63 walker.walk(tree.root_node(), &module, &mut graph);
64 graph
65}
66
67pub struct Lang;
68
69impl crate::lang::LangExtractor for Lang {
70 type Presets = Presets;
71 const LANG_TAG: &'static str = "rs";
72 const ALLOWED_KINDS: &'static [&'static str] = &[
73 "struct", "enum", "trait", "impl", "fn", "method", "const", "static", "type",
74 ];
75 const ALLOWED_VISIBILITIES: &'static [&'static str] = &["public", "private", "module"];
76
77 fn extract(
78 uri: &str,
79 source: &str,
80 anchor: &Moniker,
81 deep: bool,
82 presets: &Self::Presets,
83 ) -> CodeGraph {
84 extract(uri, source, anchor, deep, presets)
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::core::moniker::{Moniker, MonikerBuilder};
92 use crate::lang::assert_conformance;
93
94 fn extract(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
95 let g = super::extract(uri, source, anchor, deep, &Presets::default());
96 assert_conformance::<super::Lang>(&g, anchor);
97 g
98 }
99
100 fn make_anchor() -> Moniker {
101 MonikerBuilder::new().project(b"code-moniker").build()
102 }
103
104 #[test]
105 fn parse_empty_returns_source_file() {
106 let tree = parse("");
107 assert_eq!(tree.root_node().kind(), "source_file");
108 }
109
110 #[test]
111 fn extract_empty_yields_module_only_graph() {
112 let anchor = make_anchor();
113 let g = extract("src/lib.rs", "", &anchor, false);
114 assert_eq!(g.def_count(), 1);
115 assert_eq!(g.ref_count(), 0);
116
117 let expected = MonikerBuilder::new()
118 .project(b"code-moniker")
119 .segment(b"lang", b"rs")
120 .segment(b"dir", b"src")
121 .segment(b"module", b"lib")
122 .build();
123 assert_eq!(g.root(), &expected);
124 }
125
126 #[test]
127 fn extract_type_alias_emits_type_alias_def() {
128 let g = extract("util.rs", "pub type Id = u64;", &make_anchor(), false);
129 let id = MonikerBuilder::new()
130 .project(b"code-moniker")
131 .segment(b"lang", b"rs")
132 .segment(b"module", b"util")
133 .segment(b"type", b"Id")
134 .build();
135 assert!(g.contains(&id));
136 }
137
138 #[test]
139 fn extract_impl_trait_for_external_type_keeps_methods_and_ref() {
140 let src = r#"
141 use alloc::collections::VecDeque;
142 pub trait Buf { fn remaining(&self) -> usize; }
143 impl Buf for VecDeque<u8> {
144 fn remaining(&self) -> usize { 0 }
145 fn chunk(&self) -> &[u8] { &[] }
146 }
147 "#;
148 let g = extract("util.rs", src, &make_anchor(), false);
149 let vec_deque = MonikerBuilder::new()
150 .project(b"code-moniker")
151 .segment(b"lang", b"rs")
152 .segment(b"module", b"util")
153 .segment(b"struct", b"VecDeque")
154 .build();
155 let remaining = MonikerBuilder::new()
156 .project(b"code-moniker")
157 .segment(b"lang", b"rs")
158 .segment(b"module", b"util")
159 .segment(b"struct", b"VecDeque")
160 .segment(b"method", b"remaining()")
161 .build();
162 assert!(
163 g.contains(&vec_deque),
164 "VecDeque must be synthesized as a placeholder struct so its methods can land. defs: {:?}",
165 g.def_monikers()
166 );
167 assert!(
168 g.contains(&remaining),
169 "method on impl-for-external-type must be captured. defs: {:?}",
170 g.def_monikers()
171 );
172 assert!(
173 g.refs().any(|r| r.kind == b"implements".to_vec()
174 && r.target.as_view().segments().last().unwrap().name == b"Buf"),
175 "impl-for-external must still emit the implements ref"
176 );
177 }
178
179 #[test]
180 fn extract_use_bare_ident_is_external() {
181 let g = extract("util.rs", "use foo;", &make_anchor(), false);
182 assert_eq!(g.ref_count(), 1);
183 let r = g.refs().next().unwrap();
184 assert_eq!(r.kind, b"imports_symbol".to_vec());
185 let target = MonikerBuilder::new()
186 .project(b"code-moniker")
187 .segment(b"external_pkg", b"foo")
188 .build();
189 assert_eq!(r.target, target);
190 }
191
192 #[test]
193 fn extract_use_crate_prefix_resolves_project_local() {
194 let g = extract(
195 "util.rs",
196 "use crate::core::moniker::Moniker;",
197 &make_anchor(),
198 false,
199 );
200 let r = g.refs().next().unwrap();
201 let target = MonikerBuilder::new()
202 .project(b"code-moniker")
203 .segment(b"lang", b"rs")
204 .segment(b"dir", b"core")
205 .segment(b"module", b"moniker")
206 .segment(b"path", b"Moniker")
207 .build();
208 assert_eq!(r.target, target);
209 }
210
211 #[test]
212 fn extract_use_super_walks_up_one_segment() {
213 let anchor = MonikerBuilder::new()
214 .project(b"code-moniker")
215 .segment(b"path", b"src")
216 .segment(b"path", b"lang")
217 .build();
218 let g = extract("rs/walker.rs", "use super::kinds;", &anchor, false);
219 let r = g.refs().next().unwrap();
220 let target = MonikerBuilder::new()
221 .project(b"code-moniker")
222 .segment(b"path", b"src")
223 .segment(b"path", b"lang")
224 .segment(b"lang", b"rs")
225 .segment(b"dir", b"rs")
226 .segment(b"path", b"kinds")
227 .build();
228 assert_eq!(r.target, target);
229 }
230
231 #[test]
232 fn extract_use_local_mod_resolves_as_self() {
233 let src = r#"
234 mod canonicalize;
235 use canonicalize::compute_module_moniker;
236 "#;
237 let g = extract("util.rs", src, &make_anchor(), false);
238 let r = g.refs().next().unwrap();
239 let target = MonikerBuilder::new()
240 .project(b"code-moniker")
241 .segment(b"lang", b"rs")
242 .segment(b"module", b"util")
243 .segment(b"module", b"canonicalize")
244 .segment(b"path", b"compute_module_moniker")
245 .build();
246 assert_eq!(
247 r.target, target,
248 "bare path matching a local mod must resolve project-local"
249 );
250 }
251
252 #[test]
253 fn extract_use_self_keeps_module_prefix() {
254 let g = extract("util.rs", "use self::kinds::PATH;", &make_anchor(), false);
255 let r = g.refs().next().unwrap();
256 let target = MonikerBuilder::new()
257 .project(b"code-moniker")
258 .segment(b"lang", b"rs")
259 .segment(b"module", b"util")
260 .segment(b"module", b"kinds")
261 .segment(b"path", b"PATH")
262 .build();
263 assert_eq!(r.target, target);
264 }
265
266 #[test]
267 fn extract_use_list_emits_one_ref_per_leaf() {
268 let g = extract(
269 "util.rs",
270 "use std::collections::{HashMap, HashSet};",
271 &make_anchor(),
272 false,
273 );
274 let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
275 assert_eq!(imports_symbol.len(), 2);
276 let targets: Vec<_> = imports_symbol.iter().map(|r| r.target.clone()).collect();
277 let hashmap = MonikerBuilder::new()
278 .project(b"code-moniker")
279 .segment(b"external_pkg", b"std")
280 .segment(b"path", b"collections")
281 .segment(b"path", b"HashMap")
282 .build();
283 let hashset = MonikerBuilder::new()
284 .project(b"code-moniker")
285 .segment(b"external_pkg", b"std")
286 .segment(b"path", b"collections")
287 .segment(b"path", b"HashSet")
288 .build();
289 assert!(targets.contains(&hashmap));
290 assert!(targets.contains(&hashset));
291 }
292
293 #[test]
294 fn extract_use_wildcard_splits_scoped_path() {
295 let g = extract("util.rs", "use pgrx::prelude::*;", &make_anchor(), false);
296 let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
297 assert_eq!(imports_symbol.len(), 1);
298 let target = MonikerBuilder::new()
299 .project(b"code-moniker")
300 .segment(b"external_pkg", b"pgrx")
301 .segment(b"path", b"prelude")
302 .build();
303 assert_eq!(
304 imports_symbol[0].target, target,
305 "wildcard parent path must split on :: AND mark crate root as external"
306 );
307 }
308
309 #[test]
310 fn extract_use_alias_drops_alias_keeps_path() {
311 let g = extract(
312 "util.rs",
313 "use std::io::Result as IoResult;",
314 &make_anchor(),
315 false,
316 );
317 let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
318 assert_eq!(imports_symbol.len(), 1);
319 let r = &imports_symbol[0];
320 let target = MonikerBuilder::new()
321 .project(b"code-moniker")
322 .segment(b"external_pkg", b"std")
323 .segment(b"path", b"io")
324 .segment(b"path", b"Result")
325 .build();
326 assert_eq!(r.target, target);
327 }
328
329 #[test]
330 fn extract_shallow_skips_param_and_local() {
331 let src = "pub fn add(a: i32, b: i32) -> i32 { let sum = a + b; sum }";
332 let g = extract("util.rs", src, &make_anchor(), false);
333 assert!(
334 g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
335 "shallow extraction must not produce param/local defs"
336 );
337 }
338
339 #[test]
340 fn extract_deep_emits_params_under_function() {
341 let src = "pub fn add(a: i32, b: i32) -> i32 { a + b }";
342 let g = extract("util.rs", src, &make_anchor(), true);
343 let add = MonikerBuilder::new()
344 .project(b"code-moniker")
345 .segment(b"lang", b"rs")
346 .segment(b"module", b"util")
347 .segment(b"fn", b"add(a:i32,b:i32)")
348 .build();
349 let pa = MonikerBuilder::new()
350 .project(b"code-moniker")
351 .segment(b"lang", b"rs")
352 .segment(b"module", b"util")
353 .segment(b"fn", b"add(a:i32,b:i32)")
354 .segment(b"param", b"a")
355 .build();
356 let pb = MonikerBuilder::new()
357 .project(b"code-moniker")
358 .segment(b"lang", b"rs")
359 .segment(b"module", b"util")
360 .segment(b"fn", b"add(a:i32,b:i32)")
361 .segment(b"param", b"b")
362 .build();
363 assert!(g.contains(&add));
364 assert!(
365 g.contains(&pa),
366 "missing param:a, defs: {:?}",
367 g.def_monikers()
368 );
369 assert!(g.contains(&pb));
370 }
371
372 #[test]
373 fn extract_deep_self_parameter_named_self() {
374 let src = "pub struct Foo; impl Foo { fn bar(&self, x: i32) {} }";
375 let g = extract("util.rs", src, &make_anchor(), true);
376 let bar_self = MonikerBuilder::new()
377 .project(b"code-moniker")
378 .segment(b"lang", b"rs")
379 .segment(b"module", b"util")
380 .segment(b"struct", b"Foo")
381 .segment(b"method", b"bar(x:i32)")
382 .segment(b"param", b"self")
383 .build();
384 let bar_x = MonikerBuilder::new()
385 .project(b"code-moniker")
386 .segment(b"lang", b"rs")
387 .segment(b"module", b"util")
388 .segment(b"struct", b"Foo")
389 .segment(b"method", b"bar(x:i32)")
390 .segment(b"param", b"x")
391 .build();
392 assert!(g.contains(&bar_self));
393 assert!(g.contains(&bar_x));
394 }
395
396 #[test]
397 fn extract_deep_emits_locals_under_function() {
398 let src = r#"pub fn run() {
399 let x = 1;
400 let y = 2;
401 }"#;
402 let g = extract("util.rs", src, &make_anchor(), true);
403 let lx = MonikerBuilder::new()
404 .project(b"code-moniker")
405 .segment(b"lang", b"rs")
406 .segment(b"module", b"util")
407 .segment(b"fn", b"run()")
408 .segment(b"local", b"x")
409 .build();
410 let ly = MonikerBuilder::new()
411 .project(b"code-moniker")
412 .segment(b"lang", b"rs")
413 .segment(b"module", b"util")
414 .segment(b"fn", b"run()")
415 .segment(b"local", b"y")
416 .build();
417 assert!(g.contains(&lx));
418 assert!(g.contains(&ly));
419 }
420
421 #[test]
422 fn extract_deep_locals_in_nested_block_attach_to_function() {
423 let src = r#"pub fn run(flag: bool) {
424 if flag { let inner = 1; }
425 }"#;
426 let g = extract("util.rs", src, &make_anchor(), true);
427 let inner = MonikerBuilder::new()
428 .project(b"code-moniker")
429 .segment(b"lang", b"rs")
430 .segment(b"module", b"util")
431 .segment(b"fn", b"run(flag:bool)")
432 .segment(b"local", b"inner")
433 .build();
434 assert!(
435 g.contains(&inner),
436 "local inside `if` block should attach to the function, not the block; defs: {:?}",
437 g.def_monikers()
438 );
439 }
440
441 #[test]
442 fn extract_deep_named_closure_emits_function_def() {
443 let src = "pub fn run() { let f = |x| x + 1; }";
444 let g = extract("util.rs", src, &make_anchor(), true);
445 let f = MonikerBuilder::new()
446 .project(b"code-moniker")
447 .segment(b"lang", b"rs")
448 .segment(b"module", b"util")
449 .segment(b"fn", b"run()")
450 .segment(b"fn", b"f(x)")
451 .build();
452 assert!(
453 g.contains(&f),
454 "expected {f:?}, defs: {:?}",
455 g.def_monikers()
456 );
457 }
458
459 #[test]
460 fn extract_deep_skips_underscore_pattern() {
461 let src = "pub fn run(_: i32) { let _ = 1; }";
462 let g = extract("util.rs", src, &make_anchor(), true);
463 assert!(
464 g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
465 "`_` patterns must not produce defs; got: {:?}",
466 g.def_monikers()
467 );
468 }
469
470 #[test]
471 fn extract_deep_comment_inside_match_arm_emits_def() {
472 let src = r#"pub fn run() {
473 match Some(1) {
474 Some(_) => {}
475 // inside-arm
476 None => {}
477 }
478 }"#;
479 let g = extract("util.rs", src, &make_anchor(), true);
480 assert!(
481 g.defs().any(|d| d.kind == b"comment"),
482 "comment between match arms must emit a comment def; defs: {:?}",
483 g.def_monikers()
484 );
485 }
486
487 #[test]
488 fn extract_deep_comment_inside_let_value_expression_emits_def() {
489 let src = r#"pub fn run() {
490 let _value = if true {
491 1
492 } else {
493 match Some(2) {
494 Some(_) => 3,
495 // hidden-in-let-value
496 None => 4,
497 }
498 };
499 }"#;
500 let g = extract("util.rs", src, &make_anchor(), true);
501 assert!(
502 g.defs().any(|d| d.kind == b"comment"),
503 "comment nested in the value of a let must emit a comment def; defs: {:?}",
504 g.def_monikers()
505 );
506 }
507
508 #[test]
509 fn extract_deep_comment_inside_call_closure_emits_def() {
510 let src = r#"pub fn run() {
511 let _ = (0..1).map(|x| {
512 // hidden-in-closure-arg
513 x + 1
514 });
515 }"#;
516 let g = extract("util.rs", src, &make_anchor(), true);
517 assert!(
518 g.defs().any(|d| d.kind == b"comment"),
519 "comment inside a closure passed as a call argument must emit a comment def; defs: {:?}",
520 g.def_monikers()
521 );
522 }
523
524 #[test]
525 fn extract_deep_local_inside_let_value_emits_def() {
526 let src = r#"pub fn run() {
527 let _v = if true {
528 let inner = 7;
529 inner
530 } else {
531 0
532 };
533 }"#;
534 let g = extract("util.rs", src, &make_anchor(), true);
535 let inner = MonikerBuilder::new()
536 .project(b"code-moniker")
537 .segment(b"lang", b"rs")
538 .segment(b"module", b"util")
539 .segment(b"fn", b"run()")
540 .segment(b"local", b"inner")
541 .build();
542 assert!(
543 g.contains(&inner),
544 "local inside a let-value expression must attach to the function; defs: {:?}",
545 g.def_monikers()
546 );
547 }
548
549 #[test]
550 fn extract_nested_self_call_emits_two_method_call_refs() {
551 let src = r#"
552pub struct W;
553impl W {
554 fn outer(&self) { self.foo(self.bar()); }
555 fn foo(&self, _: u8) {}
556 fn bar(&self) -> u8 { 0 }
557}
558"#;
559 let g = extract("util.rs", src, &make_anchor(), true);
560 let n = g.refs().filter(|r| r.kind == b"method_call").count();
561 assert_eq!(
562 n,
563 2,
564 "nested self.foo(self.bar()) must emit two method_call refs; refs: {:?}",
565 g.refs().collect::<Vec<_>>()
566 );
567 }
568
569 #[test]
570 fn extract_use_single_segment_skips_imports_module() {
571 let src = "use foo;";
572 let g = extract("lib.rs", src, &make_anchor(), false);
573 let n = g.refs().filter(|r| r.kind == b"imports_module").count();
574 assert_eq!(n, 0, "single-segment use has no parent module to point at");
575 }
576
577 #[test]
578 fn extract_grouped_use_emits_single_imports_module_per_parent() {
579 let src = "use std::io::{self, Read, Write};";
580 let g = extract("lib.rs", src, &make_anchor(), false);
581 let ims: Vec<_> = g.refs().filter(|r| r.kind == b"imports_module").collect();
582 assert_eq!(
583 ims.len(),
584 1,
585 "grouped use must emit exactly one imports_module per parent module; refs: {:?}",
586 ims
587 );
588 }
589
590 #[test]
591 fn extract_nested_grouped_use_emits_one_imports_module_per_distinct_parent() {
592 let src = "use std::{io::{Read, Write}, fmt};";
593 let g = extract("lib.rs", src, &make_anchor(), false);
594 let all: Vec<_> = g.refs().filter(|r| r.kind == b"imports_module").collect();
595 let unique: std::collections::HashSet<_> = all.iter().map(|r| &r.target).collect();
596 assert_eq!(
597 all.len(),
598 unique.len(),
599 "nested grouped use must not duplicate imports_module refs for the same parent",
600 );
601 }
602
603 #[test]
604 fn extract_free_call_to_same_file_def_is_resolved() {
605 let src = "pub fn run() { foo(); }\npub fn foo() {}";
606 let g = extract("util.rs", src, &make_anchor(), true);
607 let r = g
608 .refs()
609 .find(|r| r.kind == b"calls")
610 .expect("missing calls ref");
611 assert_eq!(
612 r.confidence,
613 b"resolved",
614 "same-file free fn call must be resolved; got {:?}",
615 std::str::from_utf8(&r.confidence)
616 );
617 }
618
619 #[test]
620 fn extract_free_call_to_unknown_name_stays_unresolved() {
621 let src = "pub fn run() { foo(); }";
622 let g = extract("util.rs", src, &make_anchor(), true);
623 let r = g
624 .refs()
625 .find(|r| r.kind == b"calls")
626 .expect("missing calls ref");
627 assert_eq!(
628 r.confidence,
629 b"unresolved",
630 "call to unknown name stays unresolved; got {:?}",
631 std::str::from_utf8(&r.confidence)
632 );
633 }
634
635 #[test]
636 fn extract_self_method_call_to_same_impl_is_resolved() {
637 let src = r#"
638pub struct W;
639impl W {
640 fn dispatch(&self) { self.walk(); }
641 fn walk(&self) {}
642}
643"#;
644 let g = extract("util.rs", src, &make_anchor(), true);
645 let r = g
646 .refs()
647 .find(|r| r.kind == b"method_call")
648 .expect("missing method_call ref");
649 assert_eq!(
650 r.confidence,
651 b"resolved",
652 "self method call to same impl must be resolved; got {:?}",
653 std::str::from_utf8(&r.confidence)
654 );
655 }
656
657 #[test]
658 fn extract_path_qualified_call_emits_calls_ref() {
659 let src = "pub fn run() { ::foo::bar::baz(); }";
660 let g = extract("util.rs", src, &make_anchor(), true);
661 let n = g.refs().filter(|r| r.kind == b"calls").count();
662 assert!(
663 n >= 1,
664 "path-qualified call must emit calls ref; refs: {:?}",
665 g.refs().collect::<Vec<_>>()
666 );
667 }
668
669 #[test]
670 fn extract_let_type_emits_uses_type_ref() {
671 let src = "pub fn run() { let x: SomeType = todo!(); }";
672 let g = extract("util.rs", src, &make_anchor(), true);
673 let n = g.refs().filter(|r| r.kind == b"uses_type").count();
674 assert!(
675 n >= 1,
676 "typed let binding must emit uses_type; refs: {:?}",
677 g.refs().collect::<Vec<_>>()
678 );
679 }
680
681 #[test]
682 fn extract_method_chain_emits_method_call_per_link() {
683 let src = r#"
684pub struct W;
685impl W {
686 fn outer(&self) { self.foo().bar(); }
687 fn foo(&self) -> Self { W }
688 fn bar(&self) {}
689}
690"#;
691 let g = extract("util.rs", src, &make_anchor(), true);
692 let n = g.refs().filter(|r| r.kind == b"method_call").count();
693 assert_eq!(
694 n,
695 2,
696 "method chain self.foo().bar() must emit one method_call per link; refs: {:?}",
697 g.refs().collect::<Vec<_>>()
698 );
699 }
700
701 #[test]
702 fn extract_enum_variant_construction_emits_instantiates() {
703 let src = r#"
704pub fn run() { let _ = Color::Red(1); }
705"#;
706 let g = extract("util.rs", src, &make_anchor(), true);
707 let n = g
708 .refs()
709 .filter(|r| {
710 r.kind == b"instantiates"
711 && r.target
712 .as_view()
713 .segments()
714 .last()
715 .is_some_and(|s| s.kind == b"enum" && s.name == b"Color")
716 })
717 .count();
718 assert_eq!(
719 n,
720 1,
721 "Type::Variant(args) must emit instantiates → enum:Type; refs: {:?}",
722 g.refs().collect::<Vec<_>>()
723 );
724 }
725
726 #[test]
727 fn extract_tuple_struct_construction_emits_instantiates() {
728 let src = "pub fn run() { let _ = Foo(1, 2); }";
729 let g = extract("util.rs", src, &make_anchor(), true);
730 let n = g
731 .refs()
732 .filter(|r| {
733 r.kind == b"instantiates"
734 && r.target
735 .as_view()
736 .segments()
737 .last()
738 .is_some_and(|s| s.kind == b"struct" && s.name == b"Foo")
739 })
740 .count();
741 assert_eq!(
742 n,
743 1,
744 "CamelCase identifier call Foo(...) must emit instantiates → struct:Foo; refs: {:?}",
745 g.refs().collect::<Vec<_>>()
746 );
747 let mistaken = g
748 .refs()
749 .filter(|r| r.kind == b"calls")
750 .any(|r| r.target.as_view().segments().last().unwrap().name == b"Foo(2)");
751 assert!(
752 !mistaken,
753 "Foo(...) must NOT emit calls → fn:Foo; refs: {:?}",
754 g.refs().collect::<Vec<_>>()
755 );
756 }
757
758 #[test]
759 fn extract_primitive_types_emit_no_uses_type_ref() {
760 let src = "pub fn run(x: i32, y: bool, z: String) -> u8 { let _: f64 = 0.0; 0 }";
761 let g = extract("util.rs", src, &make_anchor(), true);
762 let primitives: &[&[u8]] = &[b"i32", b"u8", b"bool", b"f64", b"String", b"str"];
763 let leaked: Vec<_> = g
764 .refs()
765 .filter(|r| r.kind == b"uses_type")
766 .filter(|r| {
767 let name = r.target.as_view().segments().last().unwrap().name;
768 primitives.contains(&name)
769 })
770 .collect();
771 assert!(
772 leaked.is_empty(),
773 "primitive types must NOT emit uses_type; leaked: {:?}",
774 leaked
775 );
776 }
777
778 #[test]
779 fn extract_generic_type_param_emits_no_uses_type_ref() {
780 let src = "pub fn run<T>(x: T) -> T { x }";
781 let g = extract("util.rs", src, &make_anchor(), true);
782 let leaked = g
783 .refs()
784 .filter(|r| r.kind == b"uses_type")
785 .any(|r| r.target.as_view().segments().last().unwrap().name == b"T");
786 assert!(
787 !leaked,
788 "generic type param T must NOT emit uses_type; refs: {:?}",
789 g.refs().collect::<Vec<_>>()
790 );
791 }
792
793 #[test]
794 fn extract_closure_bound_call_targets_local_def() {
795 let src = "pub fn run() { let f = |x| x + 1; f(2); }";
796 let g = extract("util.rs", src, &make_anchor(), true);
797 let local_call = g
798 .refs()
799 .filter(|r| r.kind == b"calls")
800 .find(|r| r.target.as_view().segments().last().unwrap().name == b"f");
801 assert!(
802 local_call.is_some(),
803 "call to closure-bound name `f` must target the local closure def; refs: {:?}",
804 g.refs().collect::<Vec<_>>()
805 );
806 let r = local_call.unwrap();
807 assert_eq!(
808 r.confidence,
809 b"local",
810 "closure-bound call confidence must be `local`, got {:?}",
811 std::str::from_utf8(&r.confidence)
812 );
813 }
814
815 #[test]
816 fn extract_scoped_variant_in_value_position_emits_reads() {
817 let src = "pub fn run() { let _ = Color::Red; }";
818 let g = extract("util.rs", src, &make_anchor(), true);
819 let n = g
820 .refs()
821 .filter(|r| r.kind == b"reads")
822 .any(|r| r.target.as_view().segments().last().unwrap().name == b"Red");
823 assert!(
824 n,
825 "Color::Red in value position must emit reads → variant; refs: {:?}",
826 g.refs().collect::<Vec<_>>()
827 );
828 }
829
830 #[test]
831 fn extract_local_var_reference_emits_reads_ref() {
832 let src = "pub fn run() { let x = 1; foo(x); }";
833 let g = extract("util.rs", src, &make_anchor(), true);
834 let reads_x = g
835 .refs()
836 .filter(|r| r.kind == b"reads")
837 .any(|r| r.target.as_view().segments().last().unwrap().name == b"x");
838 assert!(
839 reads_x,
840 "local variable read `x` must emit reads → local:x; refs: {:?}",
841 g.refs().collect::<Vec<_>>()
842 );
843 }
844}