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	};
60	let walker = CanonicalWalker::new(&strat, source.as_bytes());
61	walker.walk(tree.root_node(), &module, &mut graph);
62	graph
63}
64
65pub struct Lang;
66
67impl crate::lang::LangExtractor for Lang {
68	type Presets = Presets;
69	const LANG_TAG: &'static str = "rs";
70	const ALLOWED_KINDS: &'static [&'static str] = &[
71		"struct", "enum", "trait", "impl", "fn", "method", "const", "static", "type",
72	];
73	const ALLOWED_VISIBILITIES: &'static [&'static str] = &["public", "private", "module"];
74
75	fn extract(
76		uri: &str,
77		source: &str,
78		anchor: &Moniker,
79		deep: bool,
80		presets: &Self::Presets,
81	) -> CodeGraph {
82		extract(uri, source, anchor, deep, presets)
83	}
84}
85
86#[cfg(test)]
87mod tests {
88	use super::*;
89	use crate::core::moniker::{Moniker, MonikerBuilder};
90	use crate::lang::assert_conformance;
91
92	fn extract(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
93		let g = super::extract(uri, source, anchor, deep, &Presets::default());
94		assert_conformance::<super::Lang>(&g, anchor);
95		g
96	}
97
98	fn make_anchor() -> Moniker {
99		MonikerBuilder::new().project(b"code-moniker").build()
100	}
101
102	fn has_parent_segment(m: &Moniker, kind: &[u8], name: &[u8]) -> bool {
103		let segments: Vec<_> = m.as_view().segments().collect();
104		segments
105			.get(segments.len().saturating_sub(2))
106			.is_some_and(|seg| seg.kind == kind && seg.name == name)
107	}
108
109	#[test]
110	fn parse_empty_returns_source_file() {
111		let tree = parse("");
112		assert_eq!(tree.root_node().kind(), "source_file");
113	}
114
115	#[test]
116	fn extract_emits_comment_def_per_comment_node() {
117		let src = "// a\n/// b\nstruct Foo;\n";
118		let g = extract("src/lib.rs", src, &make_anchor(), false);
119		let n = g.defs().filter(|d| d.kind == b"comment").count();
120		assert_eq!(n, 2);
121	}
122
123	#[test]
124	fn extract_emits_comments_inside_type_bodies() {
125		let src = r#"
126struct Foo {
127    // field comment
128    value: i32,
129}
130
131trait Bar {
132    // trait comment
133    fn bar(&self);
134}
135
136enum Baz {
137    // enum comment
138    A,
139}
140"#;
141		let g = extract("src/lib.rs", src, &make_anchor(), true);
142		let comments: Vec<_> = g.defs().filter(|d| d.kind == b"comment").collect();
143		assert_eq!(comments.len(), 3);
144		assert!(
145			comments
146				.iter()
147				.any(|d| has_parent_segment(&d.moniker, b"struct", b"Foo"))
148		);
149		assert!(
150			comments
151				.iter()
152				.any(|d| has_parent_segment(&d.moniker, b"trait", b"Bar"))
153		);
154		assert!(
155			comments
156				.iter()
157				.any(|d| has_parent_segment(&d.moniker, b"enum", b"Baz"))
158		);
159	}
160
161	#[test]
162	fn extract_empty_yields_module_only_graph() {
163		let anchor = make_anchor();
164		let g = extract("src/lib.rs", "", &anchor, false);
165		assert_eq!(g.def_count(), 1);
166		assert_eq!(g.ref_count(), 0);
167
168		let expected = MonikerBuilder::new()
169			.project(b"code-moniker")
170			.segment(b"lang", b"rs")
171			.segment(b"dir", b"src")
172			.segment(b"module", b"lib")
173			.build();
174		assert_eq!(g.root(), &expected);
175	}
176
177	#[test]
178	fn extract_struct_emits_class_def() {
179		let g = extract(
180			"util.rs",
181			"pub struct Foo { x: i32 }",
182			&make_anchor(),
183			false,
184		);
185		let foo = MonikerBuilder::new()
186			.project(b"code-moniker")
187			.segment(b"lang", b"rs")
188			.segment(b"module", b"util")
189			.segment(b"struct", b"Foo")
190			.build();
191		assert!(g.contains(&foo));
192	}
193
194	#[test]
195	fn extract_enum_emits_enum_def() {
196		let g = extract(
197			"util.rs",
198			"pub enum Color { Red, Green }",
199			&make_anchor(),
200			false,
201		);
202		let color = MonikerBuilder::new()
203			.project(b"code-moniker")
204			.segment(b"lang", b"rs")
205			.segment(b"module", b"util")
206			.segment(b"enum", b"Color")
207			.build();
208		assert!(g.contains(&color));
209	}
210
211	#[test]
212	fn extract_trait_emits_interface_def() {
213		let g = extract(
214			"util.rs",
215			"pub trait Greet { fn hi(&self); }",
216			&make_anchor(),
217			false,
218		);
219		let greet = MonikerBuilder::new()
220			.project(b"code-moniker")
221			.segment(b"lang", b"rs")
222			.segment(b"module", b"util")
223			.segment(b"trait", b"Greet")
224			.build();
225		assert!(g.contains(&greet));
226	}
227
228	#[test]
229	fn extract_type_alias_emits_type_alias_def() {
230		let g = extract("util.rs", "pub type Id = u64;", &make_anchor(), false);
231		let id = MonikerBuilder::new()
232			.project(b"code-moniker")
233			.segment(b"lang", b"rs")
234			.segment(b"module", b"util")
235			.segment(b"type", b"Id")
236			.build();
237		assert!(g.contains(&id));
238	}
239
240	#[test]
241	fn extract_top_level_fn_emits_function_def() {
242		let g = extract(
243			"util.rs",
244			"pub fn add(a: i32, b: i32) -> i32 { a + b }",
245			&make_anchor(),
246			false,
247		);
248		let add = MonikerBuilder::new()
249			.project(b"code-moniker")
250			.segment(b"lang", b"rs")
251			.segment(b"module", b"util")
252			.segment(b"fn", b"add(a:i32,b:i32)")
253			.build();
254		assert!(
255			g.contains(&add),
256			"expected {add:?}, defs: {:?}",
257			g.def_monikers()
258		);
259	}
260
261	#[test]
262	fn extract_fn_no_args_uses_arity_zero_form() {
263		let g = extract("util.rs", "pub fn boot() {}", &make_anchor(), false);
264		let boot = MonikerBuilder::new()
265			.project(b"code-moniker")
266			.segment(b"lang", b"rs")
267			.segment(b"module", b"util")
268			.segment(b"fn", b"boot()")
269			.build();
270		assert!(g.contains(&boot));
271	}
272
273	#[test]
274	fn extract_impl_block_reparents_methods_to_type() {
275		let src = r#"
276            pub struct Foo;
277            impl Foo {
278                pub fn bar(&self) -> i32 { 0 }
279            }
280        "#;
281		let g = extract("util.rs", src, &make_anchor(), false);
282		let foo = MonikerBuilder::new()
283			.project(b"code-moniker")
284			.segment(b"lang", b"rs")
285			.segment(b"module", b"util")
286			.segment(b"struct", b"Foo")
287			.build();
288		let bar = MonikerBuilder::new()
289			.project(b"code-moniker")
290			.segment(b"lang", b"rs")
291			.segment(b"module", b"util")
292			.segment(b"struct", b"Foo")
293			.segment(b"method", b"bar()")
294			.build();
295		assert!(g.contains(&foo));
296		assert!(
297			g.contains(&bar),
298			"expected {bar:?}, defs: {:?}",
299			g.def_monikers()
300		);
301	}
302
303	#[test]
304	fn extract_impl_trait_for_emits_implements_ref() {
305		let src = r#"
306            pub trait Greet { fn hi(&self); }
307            pub struct Foo;
308            impl Greet for Foo {
309                fn hi(&self) {}
310            }
311        "#;
312		let g = extract("util.rs", src, &make_anchor(), false);
313		let foo = MonikerBuilder::new()
314			.project(b"code-moniker")
315			.segment(b"lang", b"rs")
316			.segment(b"module", b"util")
317			.segment(b"struct", b"Foo")
318			.build();
319		let greet = MonikerBuilder::new()
320			.project(b"code-moniker")
321			.segment(b"lang", b"rs")
322			.segment(b"module", b"util")
323			.segment(b"trait", b"Greet")
324			.build();
325		let r = g
326			.refs()
327			.find(|r| r.kind == b"implements".to_vec())
328			.expect("implements ref");
329		assert_eq!(g.defs().nth(r.source).unwrap().moniker, foo);
330		assert_eq!(r.target, greet);
331	}
332
333	#[test]
334	fn extract_impl_trait_for_external_type_keeps_methods_and_ref() {
335		let src = r#"
336            use alloc::collections::VecDeque;
337            pub trait Buf { fn remaining(&self) -> usize; }
338            impl Buf for VecDeque<u8> {
339                fn remaining(&self) -> usize { 0 }
340                fn chunk(&self) -> &[u8] { &[] }
341            }
342        "#;
343		let g = extract("util.rs", src, &make_anchor(), false);
344		let vec_deque = MonikerBuilder::new()
345			.project(b"code-moniker")
346			.segment(b"lang", b"rs")
347			.segment(b"module", b"util")
348			.segment(b"struct", b"VecDeque")
349			.build();
350		let remaining = MonikerBuilder::new()
351			.project(b"code-moniker")
352			.segment(b"lang", b"rs")
353			.segment(b"module", b"util")
354			.segment(b"struct", b"VecDeque")
355			.segment(b"method", b"remaining()")
356			.build();
357		assert!(
358			g.contains(&vec_deque),
359			"VecDeque must be synthesized as a placeholder struct so its methods can land. defs: {:?}",
360			g.def_monikers()
361		);
362		assert!(
363			g.contains(&remaining),
364			"method on impl-for-external-type must be captured. defs: {:?}",
365			g.def_monikers()
366		);
367		assert!(
368			g.refs().any(|r| r.kind == b"implements".to_vec()
369				&& r.target.as_view().segments().last().unwrap().name == b"Buf"),
370			"impl-for-external must still emit the implements ref"
371		);
372	}
373
374	#[test]
375	fn extract_use_bare_ident_is_external() {
376		let g = extract("util.rs", "use foo;", &make_anchor(), false);
377		assert_eq!(g.ref_count(), 1);
378		let r = g.refs().next().unwrap();
379		assert_eq!(r.kind, b"imports_symbol".to_vec());
380		let target = MonikerBuilder::new()
381			.project(b"code-moniker")
382			.segment(b"external_pkg", b"foo")
383			.build();
384		assert_eq!(r.target, target);
385	}
386
387	#[test]
388	fn extract_use_external_crate_marks_external_pkg() {
389		let g = extract(
390			"util.rs",
391			"use std::collections::HashMap;",
392			&make_anchor(),
393			false,
394		);
395		let r = g.refs().next().unwrap();
396		let target = MonikerBuilder::new()
397			.project(b"code-moniker")
398			.segment(b"external_pkg", b"std")
399			.segment(b"path", b"collections")
400			.segment(b"path", b"HashMap")
401			.build();
402		assert_eq!(r.target, target);
403	}
404
405	#[test]
406	fn extract_use_crate_prefix_resolves_project_local() {
407		let g = extract(
408			"util.rs",
409			"use crate::core::moniker::Moniker;",
410			&make_anchor(),
411			false,
412		);
413		let r = g.refs().next().unwrap();
414		let target = MonikerBuilder::new()
415			.project(b"code-moniker")
416			.segment(b"lang", b"rs")
417			.segment(b"dir", b"core")
418			.segment(b"module", b"moniker")
419			.segment(b"path", b"Moniker")
420			.build();
421		assert_eq!(r.target, target);
422	}
423
424	#[test]
425	fn extract_use_super_walks_up_one_segment() {
426		let anchor = MonikerBuilder::new()
427			.project(b"code-moniker")
428			.segment(b"path", b"src")
429			.segment(b"path", b"lang")
430			.build();
431		let g = extract("rs/walker.rs", "use super::kinds;", &anchor, false);
432		let r = g.refs().next().unwrap();
433		let target = MonikerBuilder::new()
434			.project(b"code-moniker")
435			.segment(b"path", b"src")
436			.segment(b"path", b"lang")
437			.segment(b"lang", b"rs")
438			.segment(b"dir", b"rs")
439			.segment(b"path", b"kinds")
440			.build();
441		assert_eq!(r.target, target);
442	}
443
444	#[test]
445	fn extract_use_local_mod_resolves_as_self() {
446		let src = r#"
447            mod canonicalize;
448            use canonicalize::compute_module_moniker;
449        "#;
450		let g = extract("util.rs", src, &make_anchor(), false);
451		let r = g.refs().next().unwrap();
452		let target = MonikerBuilder::new()
453			.project(b"code-moniker")
454			.segment(b"lang", b"rs")
455			.segment(b"module", b"util")
456			.segment(b"module", b"canonicalize")
457			.segment(b"path", b"compute_module_moniker")
458			.build();
459		assert_eq!(
460			r.target, target,
461			"bare path matching a local mod must resolve project-local"
462		);
463	}
464
465	#[test]
466	fn extract_use_unknown_first_segment_stays_external() {
467		let g = extract("util.rs", "use foo::bar;", &make_anchor(), false);
468		let target = MonikerBuilder::new()
469			.project(b"code-moniker")
470			.segment(b"external_pkg", b"foo")
471			.segment(b"path", b"bar")
472			.build();
473		assert_eq!(g.refs().next().unwrap().target, target);
474	}
475
476	#[test]
477	fn extract_use_self_keeps_module_prefix() {
478		let g = extract("util.rs", "use self::kinds::PATH;", &make_anchor(), false);
479		let r = g.refs().next().unwrap();
480		let target = MonikerBuilder::new()
481			.project(b"code-moniker")
482			.segment(b"lang", b"rs")
483			.segment(b"module", b"util")
484			.segment(b"module", b"kinds")
485			.segment(b"path", b"PATH")
486			.build();
487		assert_eq!(r.target, target);
488	}
489
490	#[test]
491	fn extract_use_list_emits_one_ref_per_leaf() {
492		let g = extract(
493			"util.rs",
494			"use std::collections::{HashMap, HashSet};",
495			&make_anchor(),
496			false,
497		);
498		let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
499		assert_eq!(imports_symbol.len(), 2);
500		let targets: Vec<_> = imports_symbol.iter().map(|r| r.target.clone()).collect();
501		let hashmap = MonikerBuilder::new()
502			.project(b"code-moniker")
503			.segment(b"external_pkg", b"std")
504			.segment(b"path", b"collections")
505			.segment(b"path", b"HashMap")
506			.build();
507		let hashset = MonikerBuilder::new()
508			.project(b"code-moniker")
509			.segment(b"external_pkg", b"std")
510			.segment(b"path", b"collections")
511			.segment(b"path", b"HashSet")
512			.build();
513		assert!(targets.contains(&hashmap));
514		assert!(targets.contains(&hashset));
515	}
516
517	#[test]
518	fn extract_use_wildcard_splits_scoped_path() {
519		let g = extract("util.rs", "use pgrx::prelude::*;", &make_anchor(), false);
520		let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
521		assert_eq!(imports_symbol.len(), 1);
522		let target = MonikerBuilder::new()
523			.project(b"code-moniker")
524			.segment(b"external_pkg", b"pgrx")
525			.segment(b"path", b"prelude")
526			.build();
527		assert_eq!(
528			imports_symbol[0].target, target,
529			"wildcard parent path must split on :: AND mark crate root as external"
530		);
531	}
532
533	#[test]
534	fn extract_use_alias_drops_alias_keeps_path() {
535		let g = extract(
536			"util.rs",
537			"use std::io::Result as IoResult;",
538			&make_anchor(),
539			false,
540		);
541		let imports_symbol: Vec<_> = g.refs().filter(|r| r.kind == b"imports_symbol").collect();
542		assert_eq!(imports_symbol.len(), 1);
543		let r = &imports_symbol[0];
544		let target = MonikerBuilder::new()
545			.project(b"code-moniker")
546			.segment(b"external_pkg", b"std")
547			.segment(b"path", b"io")
548			.segment(b"path", b"Result")
549			.build();
550		assert_eq!(r.target, target);
551	}
552
553	#[test]
554	fn extract_shallow_skips_param_and_local() {
555		let src = "pub fn add(a: i32, b: i32) -> i32 { let sum = a + b; sum }";
556		let g = extract("util.rs", src, &make_anchor(), false);
557		assert!(
558			g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
559			"shallow extraction must not produce param/local defs"
560		);
561	}
562
563	#[test]
564	fn extract_deep_emits_params_under_function() {
565		let src = "pub fn add(a: i32, b: i32) -> i32 { a + b }";
566		let g = extract("util.rs", src, &make_anchor(), true);
567		let add = MonikerBuilder::new()
568			.project(b"code-moniker")
569			.segment(b"lang", b"rs")
570			.segment(b"module", b"util")
571			.segment(b"fn", b"add(a:i32,b:i32)")
572			.build();
573		let pa = MonikerBuilder::new()
574			.project(b"code-moniker")
575			.segment(b"lang", b"rs")
576			.segment(b"module", b"util")
577			.segment(b"fn", b"add(a:i32,b:i32)")
578			.segment(b"param", b"a")
579			.build();
580		let pb = MonikerBuilder::new()
581			.project(b"code-moniker")
582			.segment(b"lang", b"rs")
583			.segment(b"module", b"util")
584			.segment(b"fn", b"add(a:i32,b:i32)")
585			.segment(b"param", b"b")
586			.build();
587		assert!(g.contains(&add));
588		assert!(
589			g.contains(&pa),
590			"missing param:a, defs: {:?}",
591			g.def_monikers()
592		);
593		assert!(g.contains(&pb));
594	}
595
596	#[test]
597	fn extract_deep_self_parameter_named_self() {
598		let src = "pub struct Foo; impl Foo { fn bar(&self, x: i32) {} }";
599		let g = extract("util.rs", src, &make_anchor(), true);
600		let bar_self = MonikerBuilder::new()
601			.project(b"code-moniker")
602			.segment(b"lang", b"rs")
603			.segment(b"module", b"util")
604			.segment(b"struct", b"Foo")
605			.segment(b"method", b"bar(x:i32)")
606			.segment(b"param", b"self")
607			.build();
608		let bar_x = MonikerBuilder::new()
609			.project(b"code-moniker")
610			.segment(b"lang", b"rs")
611			.segment(b"module", b"util")
612			.segment(b"struct", b"Foo")
613			.segment(b"method", b"bar(x:i32)")
614			.segment(b"param", b"x")
615			.build();
616		assert!(g.contains(&bar_self));
617		assert!(g.contains(&bar_x));
618	}
619
620	#[test]
621	fn extract_deep_emits_locals_under_function() {
622		let src = r#"pub fn run() {
623            let x = 1;
624            let y = 2;
625        }"#;
626		let g = extract("util.rs", src, &make_anchor(), true);
627		let lx = MonikerBuilder::new()
628			.project(b"code-moniker")
629			.segment(b"lang", b"rs")
630			.segment(b"module", b"util")
631			.segment(b"fn", b"run()")
632			.segment(b"local", b"x")
633			.build();
634		let ly = MonikerBuilder::new()
635			.project(b"code-moniker")
636			.segment(b"lang", b"rs")
637			.segment(b"module", b"util")
638			.segment(b"fn", b"run()")
639			.segment(b"local", b"y")
640			.build();
641		assert!(g.contains(&lx));
642		assert!(g.contains(&ly));
643	}
644
645	#[test]
646	fn extract_deep_locals_in_nested_block_attach_to_function() {
647		let src = r#"pub fn run(flag: bool) {
648            if flag { let inner = 1; }
649        }"#;
650		let g = extract("util.rs", src, &make_anchor(), true);
651		let inner = MonikerBuilder::new()
652			.project(b"code-moniker")
653			.segment(b"lang", b"rs")
654			.segment(b"module", b"util")
655			.segment(b"fn", b"run(flag:bool)")
656			.segment(b"local", b"inner")
657			.build();
658		assert!(
659			g.contains(&inner),
660			"local inside `if` block should attach to the function, not the block; defs: {:?}",
661			g.def_monikers()
662		);
663	}
664
665	#[test]
666	fn extract_deep_named_closure_emits_function_def() {
667		let src = "pub fn run() { let f = |x| x + 1; }";
668		let g = extract("util.rs", src, &make_anchor(), true);
669		let f = MonikerBuilder::new()
670			.project(b"code-moniker")
671			.segment(b"lang", b"rs")
672			.segment(b"module", b"util")
673			.segment(b"fn", b"run()")
674			.segment(b"fn", b"f(x)")
675			.build();
676		assert!(
677			g.contains(&f),
678			"expected {f:?}, defs: {:?}",
679			g.def_monikers()
680		);
681	}
682
683	#[test]
684	fn extract_deep_skips_underscore_pattern() {
685		let src = "pub fn run(_: i32) { let _ = 1; }";
686		let g = extract("util.rs", src, &make_anchor(), true);
687		assert!(
688			g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
689			"`_` patterns must not produce defs; got: {:?}",
690			g.def_monikers()
691		);
692	}
693
694	#[test]
695	fn extract_deep_comment_inside_match_arm_emits_def() {
696		let src = r#"pub fn run() {
697            match Some(1) {
698                Some(_) => {}
699                // inside-arm
700                None => {}
701            }
702        }"#;
703		let g = extract("util.rs", src, &make_anchor(), true);
704		assert!(
705			g.defs().any(|d| d.kind == b"comment"),
706			"comment between match arms must emit a comment def; defs: {:?}",
707			g.def_monikers()
708		);
709	}
710
711	#[test]
712	fn extract_deep_comment_inside_let_value_expression_emits_def() {
713		let src = r#"pub fn run() {
714            let _value = if true {
715                1
716            } else {
717                match Some(2) {
718                    Some(_) => 3,
719                    // hidden-in-let-value
720                    None => 4,
721                }
722            };
723        }"#;
724		let g = extract("util.rs", src, &make_anchor(), true);
725		assert!(
726			g.defs().any(|d| d.kind == b"comment"),
727			"comment nested in the value of a let must emit a comment def; defs: {:?}",
728			g.def_monikers()
729		);
730	}
731
732	#[test]
733	fn extract_deep_comment_inside_call_closure_emits_def() {
734		let src = r#"pub fn run() {
735            let _ = (0..1).map(|x| {
736                // hidden-in-closure-arg
737                x + 1
738            });
739        }"#;
740		let g = extract("util.rs", src, &make_anchor(), true);
741		assert!(
742			g.defs().any(|d| d.kind == b"comment"),
743			"comment inside a closure passed as a call argument must emit a comment def; defs: {:?}",
744			g.def_monikers()
745		);
746	}
747
748	#[test]
749	fn extract_deep_local_inside_let_value_emits_def() {
750		let src = r#"pub fn run() {
751            let _v = if true {
752                let inner = 7;
753                inner
754            } else {
755                0
756            };
757        }"#;
758		let g = extract("util.rs", src, &make_anchor(), true);
759		let inner = MonikerBuilder::new()
760			.project(b"code-moniker")
761			.segment(b"lang", b"rs")
762			.segment(b"module", b"util")
763			.segment(b"fn", b"run()")
764			.segment(b"local", b"inner")
765			.build();
766		assert!(
767			g.contains(&inner),
768			"local inside a let-value expression must attach to the function; defs: {:?}",
769			g.def_monikers()
770		);
771	}
772
773	#[test]
774	fn extract_self_dot_method_emits_method_call_ref() {
775		let src = r#"
776pub struct W;
777impl W {
778    fn dispatch(&self) { self.walk(); }
779    fn walk(&self) {}
780}
781"#;
782		let g = extract("util.rs", src, &make_anchor(), true);
783		let refs: Vec<_> = g.refs().filter(|r| r.kind == b"method_call").collect();
784		assert_eq!(
785			refs.len(),
786			1,
787			"expected one method_call ref; refs: {:?}",
788			refs
789		);
790		let target = &refs[0].target;
791		let last = target.as_view().segments().last().unwrap();
792		assert_eq!(last.kind, b"method");
793		let bare = crate::core::moniker::query::bare_callable_name(last.name);
794		assert_eq!(
795			bare,
796			b"walk",
797			"method_call target must point at `walk`; got name={:?}",
798			std::str::from_utf8(last.name)
799		);
800		let source_def = g.def_at(refs[0].source);
801		let source_last = source_def.moniker.as_view().segments().last().unwrap();
802		let source_bare = crate::core::moniker::query::bare_callable_name(source_last.name);
803		assert_eq!(
804			source_bare,
805			b"dispatch",
806			"method_call source must be `dispatch`; got name={:?}",
807			std::str::from_utf8(source_last.name)
808		);
809	}
810
811	#[test]
812	fn extract_non_self_method_call_emits_method_call_ref() {
813		let src = r#"
814pub struct W;
815impl W {
816    fn run(&self, other: W) { other.walk(); }
817    fn walk(&self) {}
818}
819"#;
820		let g = extract("util.rs", src, &make_anchor(), true);
821		let n = g.refs().filter(|r| r.kind == b"method_call").count();
822		assert!(
823			n >= 1,
824			"non-self receiver must emit method_call with arity-only target; refs: {:?}",
825			g.refs().collect::<Vec<_>>()
826		);
827	}
828
829	#[test]
830	fn extract_nested_self_call_emits_two_method_call_refs() {
831		let src = r#"
832pub struct W;
833impl W {
834    fn outer(&self) { self.foo(self.bar()); }
835    fn foo(&self, _: u8) {}
836    fn bar(&self) -> u8 { 0 }
837}
838"#;
839		let g = extract("util.rs", src, &make_anchor(), true);
840		let n = g.refs().filter(|r| r.kind == b"method_call").count();
841		assert_eq!(
842			n,
843			2,
844			"nested self.foo(self.bar()) must emit two method_call refs; refs: {:?}",
845			g.refs().collect::<Vec<_>>()
846		);
847	}
848
849	#[test]
850	fn extract_use_emits_imports_module_to_parent() {
851		let src = "use crate::foo::bar::Baz;";
852		let g = extract("lib.rs", src, &make_anchor(), false);
853		let ims: Vec<_> = g.refs().filter(|r| r.kind == b"imports_module").collect();
854		assert!(
855			!ims.is_empty(),
856			"use must emit imports_module; refs: {:?}",
857			g.refs().collect::<Vec<_>>()
858		);
859		let last = ims[0].target.as_view().segments().last().unwrap();
860		assert_ne!(
861			last.kind,
862			b"path",
863			"imports_module target must point at a module, not at the leaf path:Baz; last={:?}",
864			std::str::from_utf8(last.kind)
865		);
866	}
867
868	#[test]
869	fn extract_use_external_emits_imports_module() {
870		let src = "use std::collections::HashMap;";
871		let g = extract("lib.rs", src, &make_anchor(), false);
872		let n = g.refs().filter(|r| r.kind == b"imports_module").count();
873		assert!(
874			n >= 1,
875			"extern use must emit imports_module; refs: {:?}",
876			g.refs().collect::<Vec<_>>()
877		);
878	}
879
880	#[test]
881	fn extract_use_single_segment_skips_imports_module() {
882		let src = "use foo;";
883		let g = extract("lib.rs", src, &make_anchor(), false);
884		let n = g.refs().filter(|r| r.kind == b"imports_module").count();
885		assert_eq!(n, 0, "single-segment use has no parent module to point at");
886	}
887
888	#[test]
889	fn extract_free_function_call_emits_calls_ref() {
890		let src = "pub fn run() { foo(); }";
891		let g = extract("util.rs", src, &make_anchor(), true);
892		let n = g.refs().filter(|r| r.kind == b"calls").count();
893		assert!(
894			n >= 1,
895			"free fn call must emit calls ref; refs: {:?}",
896			g.refs().collect::<Vec<_>>()
897		);
898	}
899
900	#[test]
901	fn extract_path_qualified_call_emits_calls_ref() {
902		let src = "pub fn run() { ::foo::bar::baz(); }";
903		let g = extract("util.rs", src, &make_anchor(), true);
904		let n = g.refs().filter(|r| r.kind == b"calls").count();
905		assert!(
906			n >= 1,
907			"path-qualified call must emit calls ref; refs: {:?}",
908			g.refs().collect::<Vec<_>>()
909		);
910	}
911
912	#[test]
913	fn extract_param_type_emits_uses_type_ref() {
914		let src = "pub fn run(x: SomeType) {}";
915		let g = extract("util.rs", src, &make_anchor(), false);
916		let n = g.refs().filter(|r| r.kind == b"uses_type").count();
917		assert!(
918			n >= 1,
919			"param type annotation must emit uses_type; refs: {:?}",
920			g.refs().collect::<Vec<_>>()
921		);
922	}
923
924	#[test]
925	fn extract_return_type_emits_uses_type_ref() {
926		let src = "pub fn run() -> SomeType { todo!() }";
927		let g = extract("util.rs", src, &make_anchor(), false);
928		let n = g.refs().filter(|r| r.kind == b"uses_type").count();
929		assert!(
930			n >= 1,
931			"return type must emit uses_type; refs: {:?}",
932			g.refs().collect::<Vec<_>>()
933		);
934	}
935
936	#[test]
937	fn extract_let_type_emits_uses_type_ref() {
938		let src = "pub fn run() { let x: SomeType = todo!(); }";
939		let g = extract("util.rs", src, &make_anchor(), true);
940		let n = g.refs().filter(|r| r.kind == b"uses_type").count();
941		assert!(
942			n >= 1,
943			"typed let binding must emit uses_type; refs: {:?}",
944			g.refs().collect::<Vec<_>>()
945		);
946	}
947
948	#[test]
949	fn extract_struct_field_type_emits_uses_type_ref() {
950		let src = "pub struct Foo { pub value: SomeType }";
951		let g = extract("util.rs", src, &make_anchor(), false);
952		let n = g.refs().filter(|r| r.kind == b"uses_type").count();
953		assert!(
954			n >= 1,
955			"struct field type must emit uses_type; refs: {:?}",
956			g.refs().collect::<Vec<_>>()
957		);
958	}
959
960	#[test]
961	fn extract_struct_literal_emits_instantiates_ref() {
962		let src = "pub fn run() { let _ = Foo { x: 1 }; }";
963		let g = extract("util.rs", src, &make_anchor(), true);
964		let n = g.refs().filter(|r| r.kind == b"instantiates").count();
965		assert!(
966			n >= 1,
967			"struct literal must emit instantiates; refs: {:?}",
968			g.refs().collect::<Vec<_>>()
969		);
970	}
971
972	#[test]
973	fn extract_path_constructor_emits_instantiates_ref() {
974		let src = "pub fn run() { let _ = Foo::new(); }";
975		let g = extract("util.rs", src, &make_anchor(), true);
976		let n = g.refs().filter(|r| r.kind == b"instantiates").count();
977		assert!(
978			n >= 1,
979			"Foo::new() must emit instantiates; refs: {:?}",
980			g.refs().collect::<Vec<_>>()
981		);
982	}
983
984	#[test]
985	fn extract_comprehensive_fixture_covers_all_expected_ref_kinds() {
986		let src = r#"
987use std::collections::HashMap;
988use crate::foo::Bar;
989
990pub trait Greet { fn hi(&self); }
991
992pub struct Service { backing: HashMap<String, Bar> }
993
994impl Greet for Service {
995    fn hi(&self) {
996        let _ = Service { backing: HashMap::new() };
997        let other: Service = Service::new();
998        other.hi();
999        self.hi();
1000        helper();
1001    }
1002}
1003
1004pub fn helper() {}
1005"#;
1006		let g = extract("util.rs", src, &make_anchor(), true);
1007		let kinds: std::collections::HashSet<Vec<u8>> = g.refs().map(|r| r.kind.clone()).collect();
1008		let expected: &[&[u8]] = &[
1009			b"imports_module",
1010			b"imports_symbol",
1011			b"calls",
1012			b"method_call",
1013			b"uses_type",
1014			b"instantiates",
1015			b"implements",
1016		];
1017		let missing: Vec<&str> = expected
1018			.iter()
1019			.filter(|k| !kinds.contains(*k as &[u8]))
1020			.map(|k| std::str::from_utf8(k).unwrap())
1021			.collect();
1022		assert!(
1023			missing.is_empty(),
1024			"missing ref kinds in comprehensive fixture: {:?}; got: {:?}",
1025			missing,
1026			kinds
1027				.iter()
1028				.map(|k| std::str::from_utf8(k).unwrap_or("?"))
1029				.collect::<Vec<_>>()
1030		);
1031	}
1032
1033	#[test]
1034	fn extract_method_chain_emits_method_call_per_link() {
1035		let src = r#"
1036pub struct W;
1037impl W {
1038    fn outer(&self) { self.foo().bar(); }
1039    fn foo(&self) -> Self { W }
1040    fn bar(&self) {}
1041}
1042"#;
1043		let g = extract("util.rs", src, &make_anchor(), true);
1044		let n = g.refs().filter(|r| r.kind == b"method_call").count();
1045		assert_eq!(
1046			n,
1047			2,
1048			"method chain self.foo().bar() must emit one method_call per link; refs: {:?}",
1049			g.refs().collect::<Vec<_>>()
1050		);
1051	}
1052
1053	#[test]
1054	fn extract_enum_variant_construction_emits_instantiates() {
1055		let src = r#"
1056pub fn run() { let _ = Color::Red(1); }
1057"#;
1058		let g = extract("util.rs", src, &make_anchor(), true);
1059		let n = g
1060			.refs()
1061			.filter(|r| {
1062				r.kind == b"instantiates"
1063					&& r.target
1064						.as_view()
1065						.segments()
1066						.last()
1067						.is_some_and(|s| s.kind == b"enum" && s.name == b"Color")
1068			})
1069			.count();
1070		assert_eq!(
1071			n,
1072			1,
1073			"Type::Variant(args) must emit instantiates → enum:Type; refs: {:?}",
1074			g.refs().collect::<Vec<_>>()
1075		);
1076	}
1077
1078	#[test]
1079	fn extract_tuple_struct_construction_emits_instantiates() {
1080		let src = "pub fn run() { let _ = Foo(1, 2); }";
1081		let g = extract("util.rs", src, &make_anchor(), true);
1082		let n = g
1083			.refs()
1084			.filter(|r| {
1085				r.kind == b"instantiates"
1086					&& r.target
1087						.as_view()
1088						.segments()
1089						.last()
1090						.is_some_and(|s| s.kind == b"struct" && s.name == b"Foo")
1091			})
1092			.count();
1093		assert_eq!(
1094			n,
1095			1,
1096			"CamelCase identifier call Foo(...) must emit instantiates → struct:Foo; refs: {:?}",
1097			g.refs().collect::<Vec<_>>()
1098		);
1099		let mistaken = g
1100			.refs()
1101			.filter(|r| r.kind == b"calls")
1102			.any(|r| r.target.as_view().segments().last().unwrap().name == b"Foo(2)");
1103		assert!(
1104			!mistaken,
1105			"Foo(...) must NOT emit calls → fn:Foo; refs: {:?}",
1106			g.refs().collect::<Vec<_>>()
1107		);
1108	}
1109
1110	#[test]
1111	fn extract_macro_invocation_emits_calls_ref() {
1112		let src = "pub fn run() { vec![1, 2]; format!(\"{}\", 1); }";
1113		let g = extract("util.rs", src, &make_anchor(), true);
1114		let names: Vec<_> = g
1115			.refs()
1116			.filter(|r| r.kind == b"calls")
1117			.map(|r| {
1118				r.target
1119					.as_view()
1120					.segments()
1121					.last()
1122					.map(|s| s.name.to_vec())
1123					.unwrap_or_default()
1124			})
1125			.collect();
1126		assert!(
1127			names.iter().any(|n| n.starts_with(b"vec")),
1128			"vec! must emit calls; refs: {:?}",
1129			g.refs().collect::<Vec<_>>()
1130		);
1131		assert!(
1132			names.iter().any(|n| n.starts_with(b"format")),
1133			"format! must emit calls; refs: {:?}",
1134			g.refs().collect::<Vec<_>>()
1135		);
1136	}
1137
1138	#[test]
1139	fn extract_primitive_types_emit_no_uses_type_ref() {
1140		let src = "pub fn run(x: i32, y: bool, z: String) -> u8 { let _: f64 = 0.0; 0 }";
1141		let g = extract("util.rs", src, &make_anchor(), true);
1142		let primitives: &[&[u8]] = &[b"i32", b"u8", b"bool", b"f64", b"String", b"str"];
1143		let leaked: Vec<_> = g
1144			.refs()
1145			.filter(|r| r.kind == b"uses_type")
1146			.filter(|r| {
1147				let name = r.target.as_view().segments().last().unwrap().name;
1148				primitives.contains(&name)
1149			})
1150			.collect();
1151		assert!(
1152			leaked.is_empty(),
1153			"primitive types must NOT emit uses_type; leaked: {:?}",
1154			leaked
1155		);
1156	}
1157
1158	#[test]
1159	fn extract_generic_type_param_emits_no_uses_type_ref() {
1160		let src = "pub fn run<T>(x: T) -> T { x }";
1161		let g = extract("util.rs", src, &make_anchor(), true);
1162		let leaked = g
1163			.refs()
1164			.filter(|r| r.kind == b"uses_type")
1165			.any(|r| r.target.as_view().segments().last().unwrap().name == b"T");
1166		assert!(
1167			!leaked,
1168			"generic type param T must NOT emit uses_type; refs: {:?}",
1169			g.refs().collect::<Vec<_>>()
1170		);
1171	}
1172
1173	#[test]
1174	fn extract_closure_bound_call_targets_local_def() {
1175		let src = "pub fn run() { let f = |x| x + 1; f(2); }";
1176		let g = extract("util.rs", src, &make_anchor(), true);
1177		let local_call = g
1178			.refs()
1179			.filter(|r| r.kind == b"calls")
1180			.find(|r| r.target.as_view().segments().last().unwrap().name == b"f");
1181		assert!(
1182			local_call.is_some(),
1183			"call to closure-bound name `f` must target the local closure def; refs: {:?}",
1184			g.refs().collect::<Vec<_>>()
1185		);
1186		let r = local_call.unwrap();
1187		assert_eq!(
1188			r.confidence,
1189			b"local",
1190			"closure-bound call confidence must be `local`, got {:?}",
1191			std::str::from_utf8(&r.confidence)
1192		);
1193	}
1194
1195	#[test]
1196	fn extract_scoped_variant_in_value_position_emits_reads() {
1197		let src = "pub fn run() { let _ = Color::Red; }";
1198		let g = extract("util.rs", src, &make_anchor(), true);
1199		let n = g
1200			.refs()
1201			.filter(|r| r.kind == b"reads")
1202			.any(|r| r.target.as_view().segments().last().unwrap().name == b"Red");
1203		assert!(
1204			n,
1205			"Color::Red in value position must emit reads → variant; refs: {:?}",
1206			g.refs().collect::<Vec<_>>()
1207		);
1208	}
1209
1210	#[test]
1211	fn extract_trait_supertype_emits_extends_ref() {
1212		let src = "pub trait Foo: Bar + Baz {}";
1213		let g = extract("util.rs", src, &make_anchor(), false);
1214		let extends: Vec<_> = g.refs().filter(|r| r.kind == b"extends").collect();
1215		assert_eq!(
1216			extends.len(),
1217			2,
1218			"trait Foo: Bar + Baz must emit two extends refs; refs: {:?}",
1219			g.refs().collect::<Vec<_>>()
1220		);
1221	}
1222
1223	#[test]
1224	fn extract_derive_attribute_emits_annotates_ref() {
1225		let src = "#[derive(Clone, Debug)] pub struct Foo;";
1226		let g = extract("util.rs", src, &make_anchor(), false);
1227		let n = g.refs().filter(|r| r.kind == b"annotates").count();
1228		assert!(
1229			n >= 2,
1230			"#[derive(Clone, Debug)] must emit at least 2 annotates refs (one per trait); refs: {:?}",
1231			g.refs().collect::<Vec<_>>()
1232		);
1233	}
1234
1235	#[test]
1236	fn extract_local_var_reference_emits_reads_ref() {
1237		let src = "pub fn run() { let x = 1; foo(x); }";
1238		let g = extract("util.rs", src, &make_anchor(), true);
1239		let reads_x = g
1240			.refs()
1241			.filter(|r| r.kind == b"reads")
1242			.any(|r| r.target.as_view().segments().last().unwrap().name == b"x");
1243		assert!(
1244			reads_x,
1245			"local variable read `x` must emit reads → local:x; refs: {:?}",
1246			g.refs().collect::<Vec<_>>()
1247		);
1248	}
1249}