Skip to main content

code_moniker_core/lang/rs/
mod.rs

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}