Skip to main content

code_moniker_core/lang/python/
mod.rs

1use tree_sitter::{Language, Parser, Tree};
2
3use crate::core::code_graph::CodeGraph;
4use crate::core::moniker::Moniker;
5use crate::core::shape::Shape;
6
7use crate::lang::KindSpec;
8
9pub mod build;
10mod canonicalize;
11mod kinds;
12mod sdk_pipeline;
13
14#[derive(Clone, Debug, Default)]
15pub struct Presets {}
16
17pub fn parse(source: &str) -> Tree {
18	let mut parser = Parser::new();
19	let language: Language = tree_sitter_python::LANGUAGE.into();
20	parser.set_language(&language).unwrap_or_else(|err| {
21		panic!("failed to load tree-sitter Python grammar: {err}");
22	});
23	parser.parse(source, None).unwrap_or_else(|| {
24		panic!("tree-sitter parse returned None on a non-cancelled call");
25	})
26}
27
28pub fn extract(
29	uri: &str,
30	source: &str,
31	anchor: &Moniker,
32	deep: bool,
33	presets: &Presets,
34) -> CodeGraph {
35	sdk_pipeline::extract(uri, source, anchor, deep, presets)
36}
37
38pub struct Lang;
39
40const DEF_KINDS: &[&str] = &[
41	"class",
42	"type",
43	"function",
44	"method",
45	"async_function",
46	"path",
47];
48
49const DEF_KIND_SPECS: &[KindSpec] = &[
50	KindSpec::new("class", Shape::Type, 20, "class"),
51	KindSpec::new("type", Shape::Type, 21, "type"),
52	KindSpec::new("function", Shape::Callable, 40, "function"),
53	KindSpec::new("async_function", Shape::Callable, 41, "async_function"),
54	KindSpec::new("method", Shape::Callable, 42, "method"),
55	KindSpec::new("path", Shape::Value, 63, "path"),
56];
57
58impl crate::lang::LangExtractor for Lang {
59	type Presets = Presets;
60	const LANG_TAG: &'static str = "python";
61	const ALLOWED_KINDS: &'static [&'static str] = DEF_KINDS;
62	const KIND_SPECS: &'static [KindSpec] = DEF_KIND_SPECS;
63	const ALLOWED_VISIBILITIES: &'static [&'static str] = &["public", "private", "module"];
64
65	fn extract(
66		uri: &str,
67		source: &str,
68		anchor: &Moniker,
69		deep: bool,
70		presets: &Self::Presets,
71	) -> CodeGraph {
72		extract(uri, source, anchor, deep, presets)
73	}
74}
75
76#[cfg(test)]
77mod tests {
78	use super::*;
79	use crate::core::moniker::MonikerBuilder;
80	use crate::lang::assert_conformance;
81
82	fn make_anchor() -> Moniker {
83		MonikerBuilder::new().project(b"app").build()
84	}
85
86	fn extract_default(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
87		let g = extract(uri, source, anchor, deep, &Presets::default());
88		assert_conformance::<super::Lang>(&g, anchor);
89		g
90	}
91
92	#[test]
93	fn parse_empty_returns_module() {
94		let tree = parse("");
95		assert_eq!(tree.root_node().kind(), "module");
96	}
97
98	#[test]
99	fn extract_module_uses_path_segments() {
100		let g = extract_default("acme/util/text.py", "", &make_anchor(), false);
101		let expected = MonikerBuilder::new()
102			.project(b"app")
103			.segment(b"lang", b"python")
104			.segment(b"package", b"acme")
105			.segment(b"package", b"util")
106			.segment(b"module", b"text")
107			.build();
108		assert_eq!(g.root(), &expected);
109	}
110
111	#[test]
112	fn extract_module_root_is_filename_only() {
113		let g = extract_default("foo.py", "", &make_anchor(), false);
114		let expected = MonikerBuilder::new()
115			.project(b"app")
116			.segment(b"lang", b"python")
117			.segment(b"module", b"foo")
118			.build();
119		assert_eq!(g.root(), &expected);
120	}
121
122	#[test]
123	fn extract_function_with_typed_params_emits_full_signature() {
124		let src = "def make(x: int, y: str) -> int:\n    return x\n";
125		let g = extract_default("m.py", src, &make_anchor(), false);
126		let f = g
127			.defs()
128			.find(|d| d.kind == b"function")
129			.expect("function def");
130		let last = f.moniker.as_view().segments().last().unwrap();
131		assert_eq!(last.kind, b"function");
132		assert_eq!(last.name, b"make(x:int,y:str)");
133		assert_eq!(f.signature, b"x:int,y:str".to_vec());
134	}
135
136	#[test]
137	fn extract_function_with_untyped_params_uses_name_only_slots() {
138		let src = "def f(a, b=1):\n    return a\n";
139		let g = extract_default("m.py", src, &make_anchor(), false);
140		let f = g
141			.defs()
142			.find(|d| d.kind == b"function")
143			.expect("function def");
144		let last = f.moniker.as_view().segments().last().unwrap();
145		assert_eq!(last.name, b"f(a,b)");
146		assert_eq!(f.signature, b"a,b".to_vec());
147	}
148
149	#[test]
150	fn extract_classmethod_excludes_cls_from_signature() {
151		let src = "class Foo:\n    @classmethod\n    def make(cls, x: int) -> 'Foo':\n        return cls()\n";
152		let g = extract_default("foo.py", src, &make_anchor(), false);
153		let m = g.defs().find(|d| d.kind == b"method").expect("method def");
154		assert_eq!(
155			m.moniker.as_view().segments().last().unwrap().name,
156			b"make(x:int)"
157		);
158	}
159
160	#[test]
161	fn extract_double_underscore_visibility_is_private() {
162		let src = "class Foo:\n    def __secret(self):\n        pass\n";
163		let g = extract_default("foo.py", src, &make_anchor(), false);
164		let m = g.defs().find(|d| d.kind == b"method").expect("method def");
165		assert_eq!(m.visibility, b"private".to_vec());
166	}
167
168	#[test]
169	fn extract_single_underscore_visibility_is_module() {
170		let src = "def _internal():\n    pass\n";
171		let g = extract_default("m.py", src, &make_anchor(), false);
172		let f = g
173			.defs()
174			.find(|d| d.kind == b"function")
175			.expect("function def");
176		assert_eq!(f.visibility, b"module".to_vec());
177	}
178
179	#[test]
180	fn extract_import_module_emits_imports_module() {
181		let src = "import os\nimport acme.util as u\n";
182		let g = extract_default("m.py", src, &make_anchor(), false);
183		let kinds: Vec<&[u8]> = g.refs().map(|r| r.kind.as_ref()).collect();
184		assert_eq!(kinds.iter().filter(|k| **k == b"imports_module").count(), 2);
185	}
186
187	#[test]
188	fn extract_stdlib_import_marks_external() {
189		let g = extract_default("m.py", "import json\n", &make_anchor(), false);
190		let r = g
191			.refs()
192			.find(|r| r.kind == b"imports_module")
193			.expect("imports_module");
194		assert_eq!(r.confidence, b"external".to_vec());
195	}
196
197	#[test]
198	fn extract_project_import_marks_imported() {
199		let g = extract_default("m.py", "import acme.util\n", &make_anchor(), false);
200		let r = g
201			.refs()
202			.find(|r| r.kind == b"imports_module")
203			.expect("imports_module");
204		assert_eq!(r.confidence, b"imported".to_vec());
205	}
206
207	#[test]
208	fn extract_from_import_emits_one_imports_symbol_per_name() {
209		let src = "from acme.util import a, b as c\n";
210		let g = extract_default("m.py", src, &make_anchor(), false);
211		let names: Vec<&[u8]> = g
212			.refs()
213			.filter(|r| r.kind == b"imports_symbol")
214			.map(|r| r.target.as_view().segments().last().unwrap().name)
215			.collect();
216		assert_eq!(names, vec![&b"a"[..], &b"b"[..]]);
217		let segs: Vec<_> = g
218			.refs()
219			.find(|r| r.kind == b"imports_symbol")
220			.unwrap()
221			.target
222			.as_view()
223			.segments()
224			.collect();
225		let kinds: Vec<&[u8]> = segs.iter().map(|s| s.kind).collect();
226		assert_eq!(
227			kinds,
228			vec![&b"lang"[..], &b"package"[..], &b"module"[..], &b"path"[..]]
229		);
230		let aliased = g
231			.refs()
232			.find(|r| r.kind == b"imports_symbol" && r.alias == b"c")
233			.expect("aliased import");
234		assert_eq!(aliased.alias, b"c".to_vec());
235	}
236
237	#[test]
238	fn extract_future_annotations_does_not_emit_a_runtime_read() {
239		let g = extract_default(
240			"m.py",
241			"from __future__ import annotations\n",
242			&make_anchor(),
243			false,
244		);
245		assert!(!g.refs().any(|reference| {
246			reference.kind == b"reads"
247				&& reference.target.as_view().segments().last().unwrap().name == b"annotations"
248		}));
249	}
250
251	#[test]
252	fn extract_runtime_module_globals_are_external() {
253		let g = extract_default(
254			"m.py",
255			"if __name__ == '__main__':\n    pass\n",
256			&make_anchor(),
257			false,
258		);
259		let reference = g
260			.refs()
261			.find(|reference| reference.kind == b"reads")
262			.expect("reads __name__");
263		assert_eq!(reference.confidence, b"external".to_vec());
264		assert_eq!(
265			reference.target.as_view().segments().next().unwrap().name,
266			b"python"
267		);
268		assert_eq!(
269			reference.target.as_view().segments().nth(1).unwrap().name,
270			b"runtime"
271		);
272	}
273
274	#[test]
275	fn extract_wildcard_import_preserves_the_star_binding() {
276		let g = extract_default(
277			"acme/facade.py",
278			"from .implementation import *\n",
279			&make_anchor(),
280			false,
281		);
282		let reference = g
283			.refs()
284			.find(|reference| reference.kind == b"imports_module")
285			.expect("wildcard module import");
286
287		assert_eq!(reference.alias, b"*".to_vec());
288	}
289
290	#[test]
291	fn extract_static_all_emits_named_export_bindings() {
292		let src = "__all__ = [\n    \"PublicClient\",\n    # Kept public for compatibility.\n    'helper',\n]\nclass PublicClient:\n    pass\ndef helper():\n    pass\n";
293		let g = extract_default("acme/implementation.py", src, &make_anchor(), false);
294		let exports = g
295			.refs()
296			.filter(|reference| reference.kind == b"reexports" && !reference.alias.is_empty())
297			.map(|reference| reference.alias.clone())
298			.collect::<Vec<_>>();
299
300		assert_eq!(exports, vec![b"PublicClient".to_vec(), b"helper".to_vec()]);
301	}
302
303	#[test]
304	fn extract_all_assignments_preserve_empty_dynamic_and_extend_state() {
305		let src = "__all__ = []\n__all__ += ['Later']\n__all__ = build_exports()\n";
306		let g = extract_default("acme/facade.py", src, &make_anchor(), false);
307		let directives = g
308			.refs()
309			.filter(|reference| reference.kind == b"reexports" && reference.alias.is_empty())
310			.map(|reference| reference.receiver_hint.clone())
311			.collect::<Vec<_>>();
312
313		assert_eq!(
314			directives,
315			vec![
316				b"python_all_replace".to_vec(),
317				b"python_all_extend".to_vec(),
318				b"python_all_dynamic".to_vec(),
319			]
320		);
321	}
322
323	#[test]
324	fn extract_conditional_import_marks_runtime_binding() {
325		let src = "if enabled:\n    from acme.client import Client\n";
326		let g = extract_default("acme/facade.py", src, &make_anchor(), false);
327		let reference = g
328			.refs()
329			.find(|reference| reference.kind == b"imports_symbol")
330			.expect("conditional import");
331
332		assert_eq!(
333			reference.receiver_hint,
334			b"python_conditional_import".to_vec()
335		);
336	}
337
338	#[test]
339	fn extract_calls_through_conditional_imports_as_runtime_bindings() {
340		let src = "def build(enabled):\n    if enabled:\n        from acme.client import Client\n    return Client()\n";
341		let g = extract_default("acme/facade.py", src, &make_anchor(), false);
342		let reference = g
343			.refs()
344			.find(|reference| reference.kind == b"calls")
345			.expect("call through conditional import");
346
347		assert_eq!(
348			reference.receiver_hint,
349			b"python_conditional_import".to_vec()
350		);
351	}
352
353	#[test]
354	fn extract_function_imports_do_not_leak_into_sibling_scopes() {
355		let src = "def configure():\n    from acme.client import Client\n    return Client()\n\ndef build():\n    return Client()\n";
356		let g = extract_default("acme/facade.py", src, &make_anchor(), false);
357		let calls = g
358			.refs()
359			.filter(|reference| reference.kind == b"calls")
360			.map(|reference| {
361				let source = g
362					.def_at(reference.source)
363					.moniker
364					.as_view()
365					.segments()
366					.last()
367					.expect("call source")
368					.name
369					.to_vec();
370				(source, reference.confidence.to_vec())
371			})
372			.collect::<Vec<_>>();
373
374		assert!(
375			calls.contains(&(b"configure()".to_vec(), b"imported".to_vec())),
376			"{calls:?}"
377		);
378		assert!(
379			calls.contains(&(b"build()".to_vec(), b"name_match".to_vec())),
380			"{calls:?}"
381		);
382	}
383
384	#[test]
385	fn extract_local_import_shadows_conditional_module_binding() {
386		let src = "try:\n    from acme.a import Client\nexcept ImportError:\n    from acme.b import Client\n\ndef build():\n    from acme.c import Client\n    return Client()\n";
387		let g = extract_default("acme/facade.py", src, &make_anchor(), false);
388		let call = g
389			.refs()
390			.find(|reference| reference.kind == b"calls")
391			.expect("locally shadowed call");
392
393		assert_eq!(call.receiver_hint, b"");
394		assert_eq!(call.confidence, b"imported");
395		assert!(
396			call.target
397				.as_view()
398				.segments()
399				.any(|segment| { segment.kind == b"module" && segment.name == b"c" })
400		);
401	}
402
403	#[test]
404	fn extract_conditional_all_as_dynamic() {
405		let src = "if enabled:\n    __all__ = ['Client']\nelse:\n    __all__ = ['Fallback']\n";
406		let g = extract_default("acme/facade.py", src, &make_anchor(), false);
407		let directives = g
408			.refs()
409			.filter(|reference| reference.kind == b"reexports")
410			.map(|reference| (reference.alias.to_vec(), reference.receiver_hint.to_vec()))
411			.collect::<Vec<_>>();
412
413		assert_eq!(
414			directives,
415			vec![
416				(Vec::new(), b"python_all_dynamic".to_vec()),
417				(Vec::new(), b"python_all_dynamic".to_vec()),
418			]
419		);
420	}
421
422	#[test]
423	fn extract_relative_import_resolves_against_importer() {
424		let src = "from .util import helper\n";
425		let g = extract_default("acme/m.py", src, &make_anchor(), false);
426		let r = g
427			.refs()
428			.find(|r| r.kind == b"imports_symbol")
429			.expect("imports_symbol");
430		let segs: Vec<_> = r.target.as_view().segments().collect();
431		let kinds: Vec<&[u8]> = segs.iter().map(|s| s.kind).collect();
432		let names: Vec<&[u8]> = segs.iter().map(|s| s.name).collect();
433		assert_eq!(
434			kinds,
435			vec![&b"lang"[..], &b"package"[..], &b"module"[..], &b"path"[..]]
436		);
437		assert_eq!(
438			names,
439			vec![&b"python"[..], &b"acme"[..], &b"util"[..], &b"helper"[..]]
440		);
441	}
442
443	#[test]
444	fn extract_relative_import_underflow_falls_back_to_external_pkg() {
445		let src = "from ...foo import bar\n";
446		let g = extract_default("m.py", src, &make_anchor(), false);
447		let r = g
448			.refs()
449			.find(|r| r.kind == b"imports_symbol")
450			.expect("imports_symbol");
451		let segs: Vec<_> = r.target.as_view().segments().collect();
452		assert_eq!(segs[0].kind, b"external_pkg");
453		assert_eq!(segs[0].name, b"...");
454	}
455
456	#[test]
457	fn extract_decorator_emits_annotates() {
458		let src = "import functools\n@functools.wraps(fn)\ndef g():\n    pass\n";
459		let g = extract_default("m.py", src, &make_anchor(), false);
460		let ann = g
461			.refs()
462			.find(|r| r.kind == b"annotates")
463			.expect("annotates ref");
464		assert_eq!(
465			ann.target.as_view().segments().last().unwrap().name,
466			b"wraps"
467		);
468	}
469
470	#[test]
471	fn extract_param_read_marks_confidence_local() {
472		let src = "def f(x):\n    return x\n";
473		let g = extract_default("m.py", src, &make_anchor(), true);
474		let r = g
475			.refs()
476			.find(|r| {
477				r.kind == b"reads" && r.target.as_view().segments().last().unwrap().name == b"x"
478			})
479			.expect("reads x");
480		assert_eq!(r.confidence, b"local".to_vec());
481	}
482
483	#[test]
484	fn extract_deep_emits_param_def() {
485		let src = "def f(x, y: int):\n    return x\n";
486		let g = extract_default("m.py", src, &make_anchor(), true);
487		let params: Vec<&[u8]> = g
488			.defs()
489			.filter(|d| d.kind == b"param")
490			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
491			.collect();
492		assert!(params.contains(&&b"x"[..]));
493		assert!(params.contains(&&b"y"[..]));
494	}
495
496	#[test]
497	fn extract_async_function_tracks_local_assignments() {
498		let src = "async def f():\n    x = 1\n    return x\n";
499		let g = extract_default("m.py", src, &make_anchor(), true);
500		let r = g
501			.refs()
502			.find(|r| {
503				r.kind == b"reads" && r.target.as_view().segments().last().unwrap().name == b"x"
504			})
505			.expect("reads x");
506		assert_eq!(r.confidence, b"local".to_vec());
507	}
508
509	#[test]
510	fn extract_imported_identifier_read_targets_import_moniker() {
511		let src = "import asyncio\nasync def f():\n    return asyncio\n";
512		let g = extract_default("m.py", src, &make_anchor(), false);
513		let r = g
514			.refs()
515			.find(|r| r.kind == b"reads" && r.confidence == b"external")
516			.expect("external read");
517		let segs: Vec<_> = r.target.as_view().segments().collect();
518		assert_eq!(segs[0].kind, b"sdk");
519		assert_eq!(segs[0].name, b"python");
520		assert_eq!(segs[1].name, b"asyncio");
521	}
522
523	#[test]
524	fn extract_unknown_identifier_read_marks_incomplete_resolution() {
525		let src = "def f():\n    return missing_name\n";
526		let g = extract_default("m.py", src, &make_anchor(), false);
527		let r = g
528			.refs()
529			.find(|r| {
530				r.kind == b"reads"
531					&& r.target.as_view().segments().last().unwrap().name == b"missing_name"
532			})
533			.expect("reads missing_name");
534		assert_eq!(r.confidence, b"unresolved".to_vec());
535	}
536
537	#[test]
538	fn extract_same_module_callable_read_keeps_exact_resolution() {
539		let src = "def f():\n    return callback\n\ndef callback():\n    pass\n";
540		let g = extract_default("m.py", src, &make_anchor(), false);
541		let r = g
542			.refs()
543			.find(|r| {
544				r.kind == b"reads"
545					&& r.target.as_view().segments().last().unwrap().name == b"callback()"
546			})
547			.expect("reads callback");
548		assert_eq!(r.confidence, b"resolved".to_vec());
549	}
550
551	#[test]
552	fn extract_module_callable_read_respects_local_shadowing() {
553		let src = "def callback():\n    pass\n\ndef f(callback):\n    return callback\n";
554		let g = extract_default("m.py", src, &make_anchor(), true);
555		let r = g
556			.refs()
557			.find(|r| {
558				r.kind == b"reads"
559					&& r.target.as_view().segments().last().unwrap().name == b"callback"
560			})
561			.expect("reads local callback");
562		assert_eq!(r.confidence, b"local".to_vec());
563		assert_eq!(r.target.as_view().segments().last().unwrap().kind, b"local");
564	}
565
566	#[test]
567	fn extract_unknown_receiver_method_call_marks_incomplete_resolution() {
568		let src = "def f(value):\n    return value.normalize()\n";
569		let g = extract_default("m.py", src, &make_anchor(), false);
570		let r = g
571			.refs()
572			.find(|r| {
573				r.kind == b"method_call"
574					&& r.target.as_view().segments().last().unwrap().name == b"normalize"
575			})
576			.expect("method_call normalize");
577		assert_eq!(r.confidence, b"unresolved".to_vec());
578	}
579
580	#[test]
581	fn extract_self_member_call_resolves_via_typed_constructor_param() {
582		let src = "class Store:\n    def reserve(self) -> None:\n        pass\n\nclass Worker:\n    def __init__(self, store: Store) -> None:\n        self._store = store\n\n    def run(self) -> None:\n        self._store.reserve()\n";
583		let g = extract_default("m.py", src, &make_anchor(), false);
584		let r = g
585			.refs()
586			.find(|r| {
587				r.kind == b"method_call"
588					&& r.target.as_view().segments().last().unwrap().name == b"reserve()"
589			})
590			.expect("method_call Store.reserve");
591		assert_eq!(r.confidence, b"resolved".to_vec());
592		let parent = r.target.parent().expect("method parent");
593		assert_eq!(parent.as_view().segments().last().unwrap().name, b"Store");
594	}
595
596	#[test]
597	fn extract_self_member_call_uses_constructor_type_independent_of_method_order() {
598		let src = "class Store:\n    def reserve(self) -> None:\n        pass\n\nclass Other:\n    def reserve(self) -> None:\n        pass\n\nclass Worker:\n    def before_init(self) -> None:\n        self._store.reserve()\n\n    def __init__(self, store: Store) -> None:\n        self._store = store\n\n    def retarget(self, other: Other) -> None:\n        self._store = other\n\n    def after_retarget(self) -> None:\n        self._store.reserve()\n";
599		let g = extract_default("m.py", src, &make_anchor(), false);
600		let targets: Vec<Moniker> = g
601			.refs()
602			.filter(|r| {
603				r.kind == b"method_call"
604					&& r.target.as_view().segments().last().unwrap().name == b"reserve()"
605			})
606			.map(|r| r.target.clone())
607			.collect();
608		assert_eq!(targets.len(), 2);
609		for target in targets {
610			let parent = target.parent().expect("method parent");
611			assert_eq!(parent.as_view().segments().last().unwrap().name, b"Store");
612		}
613	}
614
615	#[test]
616	fn extract_self_callable_attr_call_targets_attribute_type_alias() {
617		let src = "from collections.abc import Callable\nfrom typing import TypeAlias\n\nCallback: TypeAlias = Callable[[], None]\n\nclass Worker:\n    def __init__(self, cb: Callback) -> None:\n        self._cb = cb\n\n    def run(self) -> None:\n        self._cb()\n";
618		let g = extract_default("m.py", src, &make_anchor(), false);
619		let r = g
620			.refs()
621			.find(|r| {
622				r.kind == b"calls"
623					&& r.target.as_view().segments().last().unwrap().name == b"Callback"
624			})
625			.expect("calls Callback type alias");
626		assert_eq!(r.confidence, b"resolved".to_vec());
627		assert!(!g.refs().any(|r| {
628			r.kind == b"method_call" && r.target.as_view().segments().last().unwrap().name == b"_cb"
629		}));
630	}
631
632	#[test]
633	fn extract_type_alias_emits_type_def_and_rhs_uses_type() {
634		let src = "from typing import TypeAlias\n\nclass User:\n    pass\n\nUserMap: TypeAlias = dict[str, User]\ntype UserResult = User | None\n";
635		let g = extract_default("m.py", src, &make_anchor(), false);
636		let type_names: Vec<&[u8]> = g
637			.defs()
638			.filter(|d| d.kind == b"type")
639			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
640			.collect();
641		assert!(type_names.contains(&&b"UserMap"[..]));
642		assert!(type_names.contains(&&b"UserResult"[..]));
643		assert!(g.refs().any(|r| {
644			r.kind == b"uses_type"
645				&& r.target.as_view().segments().last().unwrap().name == b"User"
646				&& r.confidence == b"resolved"
647		}));
648		assert!(!g.refs().any(|r| {
649			r.kind == b"uses_type"
650				&& matches!(
651					r.target.as_view().segments().last().unwrap().name,
652					b"dict" | b"str" | b"None"
653				)
654		}));
655	}
656
657	#[test]
658	fn extract_local_class_call_emits_instantiates() {
659		let src = "class User:\n    pass\n\ndef make() -> User:\n    return User()\n";
660		let g = extract_default("m.py", src, &make_anchor(), false);
661		let r = g
662			.refs()
663			.find(|r| r.kind == b"instantiates")
664			.expect("instantiates User");
665		assert_eq!(r.confidence, b"resolved".to_vec());
666		assert_eq!(r.target.as_view().segments().last().unwrap().name, b"User");
667	}
668
669	#[test]
670	fn extract_callable_return_annotation_emits_returns_type() {
671		let src = "class User:\n    pass\n\ndef make() -> User:\n    return User()\n";
672		let g = extract_default("m.py", src, &make_anchor(), false);
673		let r = g
674			.refs()
675			.find(|r| r.kind == b"returns_type")
676			.expect("returns_type User");
677		assert_eq!(r.confidence, b"resolved".to_vec());
678		assert_eq!(r.target.as_view().segments().last().unwrap().kind, b"class");
679		assert_eq!(r.target.as_view().segments().last().unwrap().name, b"User");
680	}
681
682	#[test]
683	fn extract_local_assignment_uses_annotated_factory_return_type() {
684		let src = "class User:\n    def label(self) -> str:\n        return 'user'\n\ndef make() -> User:\n    return User()\n\ndef render():\n    value = make()\n    return value.label()\n";
685		let g = extract_default("m.py", src, &make_anchor(), false);
686		let r = g
687			.refs()
688			.find(|r| {
689				r.kind == b"method_call"
690					&& r.target.as_view().segments().last().unwrap().name == b"label()"
691			})
692			.expect("method_call User.label");
693		assert_eq!(r.confidence, b"resolved".to_vec());
694		assert_eq!(
695			r.target
696				.parent()
697				.expect("method parent")
698				.as_view()
699				.segments()
700				.last()
701				.unwrap()
702				.name,
703			b"User"
704		);
705	}
706
707	#[test]
708	fn extract_shadowed_factory_does_not_leak_module_return_type() {
709		let src = "class User:\n    def label(self) -> str:\n        return 'user'\n\ndef make() -> User:\n    return User()\n\ndef render(make):\n    value = make()\n    return value.label()\n";
710		let g = extract_default("m.py", src, &make_anchor(), false);
711		let r = g
712			.refs()
713			.find(|r| {
714				r.kind == b"method_call"
715					&& r.target.as_view().segments().last().unwrap().name == b"label"
716			})
717			.expect("method_call label");
718		assert_eq!(r.confidence, b"unresolved".to_vec());
719	}
720
721	#[test]
722	fn extract_later_assignment_still_shadows_module_factory() {
723		let src = "class User:\n    def label(self) -> str:\n        return 'user'\n\ndef make() -> User:\n    return User()\n\ndef render():\n    value = make()\n    make = lambda: None\n    return value.label()\n";
724		let g = extract_default("m.py", src, &make_anchor(), false);
725		let r = g
726			.refs()
727			.find(|r| {
728				r.kind == b"method_call"
729					&& r.target.as_view().segments().last().unwrap().name == b"label"
730			})
731			.expect("method_call label");
732		assert_eq!(r.confidence, b"unresolved".to_vec());
733	}
734
735	#[test]
736	fn extract_factory_return_type_uses_defining_scope() {
737		let src = "class Product:\n    def label(self) -> str:\n        return 'product'\n\ndef make() -> Product:\n    return Product()\n\nclass View:\n    class Product:\n        def label(self) -> str:\n            return 'shadow'\n\n    def render(self):\n        value = make()\n        return value.label()\n";
738		let g = extract_default("m.py", src, &make_anchor(), false);
739		let r = g
740			.refs()
741			.find(|r| {
742				r.kind == b"method_call"
743					&& g.defs().nth(r.source).is_some_and(|source| {
744						source
745							.moniker
746							.as_view()
747							.segments()
748							.last()
749							.is_some_and(|segment| segment.name == b"render()")
750					}) && r.target.as_view().segments().last().unwrap().name == b"label()"
751			})
752			.expect("method_call Product.label");
753		let owner = r.target.parent().expect("method owner");
754		assert_eq!(owner.as_view().segments().last().unwrap().name, b"Product");
755		assert_eq!(owner.parent(), Some(g.root().clone()));
756	}
757
758	#[test]
759	fn extract_bare_call_in_method_does_not_bind_sibling_method() {
760		let src = "class Product:\n    def label(self):\n        pass\n\nclass Factory:\n    def make(self) -> Product:\n        return Product()\n\n    def render(self):\n        value = make()\n        return value.label()\n";
761		let g = extract_default("m.py", src, &make_anchor(), false);
762		let r = g
763			.refs()
764			.find(|r| {
765				r.kind == b"method_call"
766					&& r.target.as_view().segments().last().unwrap().name == b"label"
767			})
768			.expect("method_call label");
769		assert_eq!(r.confidence, b"unresolved".to_vec());
770	}
771
772	#[test]
773	fn extract_builtin_annotated_receiver_method_is_external() {
774		let src = "def normalize(value: str) -> str:\n    return value.strip()\n";
775		let g = extract_default("m.py", src, &make_anchor(), false);
776		let r = g
777			.refs()
778			.find(|r| {
779				r.kind == b"method_call"
780					&& r.target.as_view().segments().last().unwrap().name == b"strip"
781			})
782			.expect("method_call str.strip");
783		assert_eq!(r.confidence, b"external".to_vec());
784		let segments = r.target.as_view().segments().collect::<Vec<_>>();
785		assert_eq!(segments[0].kind, b"sdk");
786		assert_eq!(segments[0].name, b"python");
787		assert_eq!(segments[1].name, b"builtins");
788		assert_eq!(segments[2].name, b"str");
789	}
790
791	#[test]
792	fn extract_builtin_generic_receiver_uses_container_type() {
793		let src = "def add(values: list[str]):\n    values.append('x')\n";
794		let g = extract_default("m.py", src, &make_anchor(), false);
795		let r = g
796			.refs()
797			.find(|reference| reference.kind == b"method_call")
798			.expect("method_call list.append");
799		assert_eq!(r.confidence, b"external".to_vec());
800		assert_eq!(r.target.as_view().segments().nth(2).unwrap().name, b"list");
801	}
802
803	#[test]
804	fn extract_variadic_parameters_use_runtime_container_types() {
805		let src = "def collect(*items: str, **options: int):\n    items.count('x')\n    options.get('limit')\n";
806		let g = extract_default("m.py", src, &make_anchor(), false);
807		let targets = g
808			.refs()
809			.filter(|reference| reference.kind == b"method_call")
810			.map(|reference| {
811				reference
812					.target
813					.as_view()
814					.segments()
815					.nth(2)
816					.unwrap()
817					.name
818					.to_vec()
819			})
820			.collect::<Vec<_>>();
821		assert!(targets.contains(&b"tuple".to_vec()), "{targets:?}");
822		assert!(targets.contains(&b"dict".to_vec()), "{targets:?}");
823	}
824
825	#[test]
826	fn extract_with_alias_preserves_constructed_receiver_type() {
827		let src = "class Session:\n    def __enter__(self) -> Session:\n        return self\n\n    def __exit__(self, exc_type, exc, tb):\n        pass\n\n    def close(self):\n        pass\n\ndef run():\n    with Session() as session:\n        session.close()\n";
828		let g = extract_default("m.py", src, &make_anchor(), false);
829		let r = g
830			.refs()
831			.find(|reference| reference.kind == b"method_call")
832			.expect("method_call Session.close");
833		assert_eq!(r.confidence, b"resolved".to_vec());
834		assert_eq!(
835			r.target
836				.parent()
837				.unwrap()
838				.as_view()
839				.segments()
840				.last()
841				.unwrap()
842				.name,
843			b"Session"
844		);
845	}
846
847	#[test]
848	fn extract_annotated_iterable_types_the_loop_binding() {
849		let src = "class Item:\n    def label(self):\n        pass\n\ndef render(items: list[Item]):\n    for item in items:\n        item.label()\n";
850		let g = extract_default("m.py", src, &make_anchor(), false);
851		let r = g
852			.refs()
853			.find(|reference| reference.kind == b"method_call")
854			.expect("method_call Item.label");
855		assert_eq!(r.confidence, b"resolved".to_vec());
856		assert_eq!(
857			r.target
858				.parent()
859				.unwrap()
860				.as_view()
861				.segments()
862				.last()
863				.unwrap()
864				.name,
865			b"Item"
866		);
867	}
868
869	#[test]
870	fn extract_heterogeneous_tuple_annotation_keeps_all_loop_element_types() {
871		let src = "class Alpha:\n    pass\n\nclass Beta:\n    pass\n\ndef render(values: tuple[Alpha, Beta]):\n    for value in values:\n        pass\n";
872		let g = extract_default("m.py", src, &make_anchor(), false);
873		let mut targets = g
874			.refs()
875			.filter(|reference| reference.kind == b"typed_as" && reference.alias == b"value")
876			.map(|reference| {
877				reference
878					.target
879					.as_view()
880					.segments()
881					.last()
882					.unwrap()
883					.name
884					.to_vec()
885			})
886			.collect::<Vec<_>>();
887		targets.sort();
888		assert_eq!(targets, vec![b"Alpha".to_vec(), b"Beta".to_vec()]);
889	}
890
891	#[test]
892	fn extract_except_alias_uses_the_exception_type() {
893		let src = "class Problem(Exception):\n    def explain(self):\n        pass\n\ndef run():\n    try:\n        pass\n    except Problem as error:\n        error.explain()\n";
894		let g = extract_default("m.py", src, &make_anchor(), false);
895		let r = g
896			.refs()
897			.find(|reference| reference.kind == b"method_call")
898			.expect("method_call Problem.explain");
899		assert_eq!(r.confidence, b"resolved".to_vec());
900		assert_eq!(
901			r.target
902				.parent()
903				.unwrap()
904				.as_view()
905				.segments()
906				.last()
907				.unwrap()
908				.name,
909			b"Problem"
910		);
911	}
912
913	#[test]
914	fn extract_exception_tuple_keeps_all_alias_types() {
915		let src = "class AlphaError(Exception):\n    pass\n\nclass BetaError(Exception):\n    pass\n\ndef run():\n    try:\n        pass\n    except (AlphaError, BetaError) as error:\n        pass\n";
916		let g = extract_default("m.py", src, &make_anchor(), false);
917		let mut targets = g
918			.refs()
919			.filter(|reference| reference.kind == b"typed_as" && reference.alias == b"error")
920			.map(|reference| {
921				reference
922					.target
923					.as_view()
924					.segments()
925					.last()
926					.unwrap()
927					.name
928					.to_vec()
929			})
930			.collect::<Vec<_>>();
931		targets.sort();
932		assert_eq!(targets, vec![b"AlphaError".to_vec(), b"BetaError".to_vec()]);
933	}
934
935	#[test]
936	fn extract_distinct_builtin_union_does_not_invent_receiver_type() {
937		let src = "def normalize(value: str | bytes):\n    return value.strip()\n";
938		let g = extract_default("m.py", src, &make_anchor(), false);
939		let r = g
940			.refs()
941			.find(|r| {
942				r.kind == b"method_call"
943					&& r.target.as_view().segments().last().unwrap().name == b"strip"
944			})
945			.expect("method_call strip");
946		assert_eq!(r.confidence, b"unresolved".to_vec());
947	}
948
949	#[test]
950	fn extract_workspace_union_parameter_emits_each_local_type_fact() {
951		let src = "class Alpha:\n    pass\n\nclass Beta:\n    pass\n\ndef render(value: Alpha | Beta):\n    return value\n";
952		let g = extract_default("m.py", src, &make_anchor(), false);
953		let mut targets = g
954			.refs()
955			.filter(|reference| reference.kind == b"typed_as" && reference.alias == b"value")
956			.map(|reference| {
957				reference
958					.target
959					.as_view()
960					.segments()
961					.last()
962					.unwrap()
963					.name
964					.to_vec()
965			})
966			.collect::<Vec<_>>();
967		targets.sort();
968		assert_eq!(targets, vec![b"Alpha".to_vec(), b"Beta".to_vec()]);
969	}
970
971	#[test]
972	fn extract_optional_builtin_does_not_invent_receiver_type() {
973		let src = "from typing import Optional\n\ndef normalize(value: Optional[str]):\n    return value.strip()\n";
974		let g = extract_default("m.py", src, &make_anchor(), false);
975		let r = g
976			.refs()
977			.find(|r| {
978				r.kind == b"method_call"
979					&& r.target.as_view().segments().last().unwrap().name == b"strip"
980			})
981			.expect("method_call strip");
982		assert_eq!(r.confidence, b"unresolved".to_vec());
983	}
984
985	#[test]
986	fn extract_workspace_type_shadows_builtin_name() {
987		let src = "class str:\n    def custom(self):\n        pass\n\ndef use(value: str):\n    value.custom()\n";
988		let g = extract_default("m.py", src, &make_anchor(), false);
989		let r = g
990			.refs()
991			.find(|r| {
992				r.kind == b"method_call"
993					&& r.target.as_view().segments().last().unwrap().name == b"custom()"
994			})
995			.expect("method_call workspace str.custom");
996		assert_eq!(r.confidence, b"resolved".to_vec());
997		assert_eq!(r.target.parent().unwrap().parent(), Some(g.root().clone()));
998	}
999
1000	#[test]
1001	fn extract_literal_assignment_infers_builtin_receiver_type() {
1002		let src = "def normalize():\n    value = ' user '\n    return value.strip()\n";
1003		let g = extract_default("m.py", src, &make_anchor(), false);
1004		let r = g
1005			.refs()
1006			.find(|r| {
1007				r.kind == b"method_call"
1008					&& r.target.as_view().segments().last().unwrap().name == b"strip"
1009			})
1010			.expect("method_call str.strip");
1011		assert_eq!(r.confidence, b"external".to_vec());
1012		assert_eq!(r.target.as_view().segments().nth(2).unwrap().name, b"str");
1013	}
1014
1015	#[test]
1016	fn extract_raw_bytes_literal_infers_bytes_receiver_type() {
1017		let src = "def decode():\n    value = rb'user'\n    return value.decode()\n";
1018		let g = extract_default("m.py", src, &make_anchor(), false);
1019		let r = g
1020			.refs()
1021			.find(|r| {
1022				r.kind == b"method_call"
1023					&& r.target.as_view().segments().last().unwrap().name == b"decode"
1024			})
1025			.expect("method_call bytes.decode");
1026		assert_eq!(r.confidence, b"external".to_vec());
1027		assert_eq!(r.target.as_view().segments().nth(2).unwrap().name, b"bytes");
1028	}
1029
1030	#[test]
1031	fn extract_builtin_return_annotation_emits_external_returns_type() {
1032		let src = "def label() -> str:\n    return 'user'\n";
1033		let g = extract_default("m.py", src, &make_anchor(), false);
1034		let r = g
1035			.refs()
1036			.find(|r| r.kind == b"returns_type")
1037			.expect("returns_type str");
1038		assert_eq!(r.confidence, b"external".to_vec());
1039		assert_eq!(r.target.as_view().segments().nth(2).unwrap().name, b"str");
1040	}
1041
1042	#[test]
1043	fn extract_async_return_type_requires_await_for_local_inference() {
1044		let src = "class User:\n    def label(self) -> str:\n        return 'user'\n\nasync def make() -> User:\n    return User()\n\nasync def direct():\n    value = make()\n    return value.label()\n\nasync def awaited():\n    value = await make()\n    return value.label()\n";
1045		let g = extract_default("m.py", src, &make_anchor(), false);
1046		assert!(!g.refs().any(|r| r.kind == b"returns_type" && {
1047			g.defs().nth(r.source).is_some_and(|source| {
1048				source
1049					.moniker
1050					.as_view()
1051					.segments()
1052					.last()
1053					.is_some_and(|segment| segment.name == b"make()")
1054			})
1055		}));
1056		let method_calls = g
1057			.refs()
1058			.filter(|r| r.kind == b"method_call")
1059			.map(|r| {
1060				let source = g.defs().nth(r.source).unwrap();
1061				(
1062					source
1063						.moniker
1064						.as_view()
1065						.segments()
1066						.last()
1067						.unwrap()
1068						.name
1069						.to_vec(),
1070					r.confidence.to_vec(),
1071				)
1072			})
1073			.collect::<Vec<_>>();
1074		assert!(method_calls.contains(&(b"direct()".to_vec(), b"unresolved".to_vec())));
1075		assert!(method_calls.contains(&(b"awaited()".to_vec(), b"resolved".to_vec())));
1076	}
1077
1078	#[test]
1079	fn extract_async_method_does_not_emit_direct_return_type() {
1080		let src = "class User:\n    pass\n\nclass Factory:\n    async def make(self) -> User:\n        return User()\n";
1081		let g = extract_default("m.py", src, &make_anchor(), false);
1082		assert!(!g.refs().any(|r| r.kind == b"returns_type"));
1083	}
1084
1085	#[test]
1086	fn extract_conflicting_factory_returns_do_not_type_local_assignment() {
1087		let src = "class First:\n    def marker(self):\n        pass\n\nclass Second:\n    def marker(self):\n        pass\n\ndef make() -> First:\n    return First()\n\ndef make() -> Second:\n    return Second()\n\ndef render():\n    value = make()\n    return value.marker()\n";
1088		let g = extract_default("m.py", src, &make_anchor(), false);
1089		let r = g
1090			.refs()
1091			.find(|r| {
1092				r.kind == b"method_call"
1093					&& r.target.as_view().segments().last().unwrap().name == b"marker"
1094			})
1095			.expect("method_call marker");
1096		assert_eq!(r.confidence, b"unresolved".to_vec());
1097		assert!(!g.refs().any(|r| r.kind == b"returns_type"));
1098	}
1099
1100	#[test]
1101	fn extract_local_import_shadows_module_factory_return() {
1102		let src = "class Local:\n    def marker(self):\n        pass\n\ndef make() -> Local:\n    return Local()\n\ndef render():\n    from other import make\n    value = make()\n    return value.marker()\n";
1103		let g = extract_default("m.py", src, &make_anchor(), false);
1104		let r = g
1105			.refs()
1106			.find(|r| {
1107				r.kind == b"method_call"
1108					&& r.target.as_view().segments().last().unwrap().name == b"marker"
1109			})
1110			.expect("method_call marker");
1111		assert_ne!(
1112			r.target
1113				.parent()
1114				.unwrap()
1115				.as_view()
1116				.segments()
1117				.last()
1118				.unwrap()
1119				.name,
1120			b"Local"
1121		);
1122	}
1123
1124	#[test]
1125	fn extract_aliased_optional_does_not_become_receiver_type() {
1126		let src = "from typing import Optional as Maybe\n\ndef normalize(value: Maybe[str]):\n    return value.strip()\n";
1127		let g = extract_default("m.py", src, &make_anchor(), false);
1128		let r = g
1129			.refs()
1130			.find(|r| {
1131				r.kind == b"method_call"
1132					&& r.target.as_view().segments().last().unwrap().name == b"strip"
1133			})
1134			.expect("method_call strip");
1135		assert_eq!(r.confidence, b"unresolved".to_vec());
1136	}
1137
1138	#[test]
1139	fn extract_typing_any_remains_dynamic() {
1140		let src = "from typing import Any\n\ndef make() -> Any:\n    raise RuntimeError\n\ndef use(value: Any):\n    return value.dynamic()\n";
1141		let g = extract_default("m.py", src, &make_anchor(), false);
1142		assert!(!g.refs().any(|r| r.kind == b"returns_type"));
1143		let r = g
1144			.refs()
1145			.find(|r| {
1146				r.kind == b"method_call"
1147					&& r.target.as_view().segments().last().unwrap().name == b"dynamic"
1148			})
1149			.expect("method_call dynamic");
1150		assert_eq!(r.confidence, b"unresolved".to_vec());
1151	}
1152
1153	#[test]
1154	fn extract_conflicting_local_assignment_types_remain_ambiguous() {
1155		let src = "def mutate(flag):\n    if flag:\n        value = []\n    else:\n        value = {}\n    value.append(1)\n";
1156		let g = extract_default("m.py", src, &make_anchor(), false);
1157		let r = g
1158			.refs()
1159			.find(|r| {
1160				r.kind == b"method_call"
1161					&& r.target.as_view().segments().last().unwrap().name == b"append"
1162			})
1163			.expect("method_call append");
1164		assert_eq!(r.confidence, b"unresolved".to_vec());
1165	}
1166
1167	#[test]
1168	fn extract_unknown_reassignment_invalidates_known_local_type() {
1169		let src = "def mutate():\n    value = []\n    value = unknown()\n    value.append(1)\n";
1170		let g = extract_default("m.py", src, &make_anchor(), false);
1171		let r = g
1172			.refs()
1173			.find(|r| {
1174				r.kind == b"method_call"
1175					&& r.target.as_view().segments().last().unwrap().name == b"append"
1176			})
1177			.expect("method_call append");
1178		assert_eq!(r.confidence, b"unresolved".to_vec());
1179	}
1180
1181	#[test]
1182	fn extract_conflicting_instance_attribute_types_remain_ambiguous() {
1183		let src = "class Holder:\n    def mutate(self, flag):\n        if flag:\n            self.value = []\n        else:\n            self.value = {}\n        self.value.append(1)\n";
1184		let g = extract_default("m.py", src, &make_anchor(), false);
1185		let r = g
1186			.refs()
1187			.find(|r| {
1188				r.kind == b"method_call"
1189					&& r.target.as_view().segments().last().unwrap().name == b"append"
1190			})
1191			.expect("method_call append");
1192		assert_eq!(r.confidence, b"unresolved".to_vec());
1193	}
1194
1195	#[test]
1196	fn extract_qualified_typing_any_does_not_bind_workspace_homonym() {
1197		let src = "import typing\n\nclass Any:\n    def dynamic(self):\n        pass\n\ndef use(value: typing.Any):\n    value.dynamic()\n";
1198		let g = extract_default("m.py", src, &make_anchor(), false);
1199		let r = g
1200			.refs()
1201			.find(|r| {
1202				r.kind == b"method_call"
1203					&& r.target.as_view().segments().last().unwrap().name == b"dynamic"
1204			})
1205			.expect("method_call dynamic");
1206		assert_eq!(r.confidence, b"unresolved".to_vec());
1207	}
1208
1209	#[test]
1210	fn extract_match_capture_shadows_module_factory() {
1211		let src = "class Local:\n    def marker(self):\n        pass\n\ndef make() -> Local:\n    return Local()\n\ndef render(subject):\n    value = make()\n    match subject:\n        case make:\n            pass\n    return value.marker()\n";
1212		let g = extract_default("m.py", src, &make_anchor(), false);
1213		let r = g
1214			.refs()
1215			.find(|r| {
1216				r.kind == b"method_call"
1217					&& r.target.as_view().segments().last().unwrap().name == b"marker"
1218			})
1219			.expect("method_call marker");
1220		assert_eq!(r.confidence, b"unresolved".to_vec());
1221	}
1222
1223	#[test]
1224	fn extract_comprehension_walrus_shadows_module_factory() {
1225		let src = "class Local:\n    def marker(self):\n        pass\n\ndef make() -> Local:\n    return Local()\n\ndef render(items):\n    value = make()\n    selected = [item for item in items if (make := item)]\n    return value.marker()\n";
1226		let g = extract_default("m.py", src, &make_anchor(), false);
1227		let r = g
1228			.refs()
1229			.find(|r| {
1230				r.kind == b"method_call"
1231					&& r.target.as_view().segments().last().unwrap().name == b"marker"
1232			})
1233			.expect("method_call marker");
1234		assert_eq!(r.confidence, b"unresolved".to_vec());
1235	}
1236
1237	#[test]
1238	fn extract_inner_param_shadows_outer_inferred_type() {
1239		let src =
1240			"def outer():\n    value = []\n\n    def inner(value):\n        value.append(1)\n";
1241		let g = extract_default("m.py", src, &make_anchor(), false);
1242		let r = g
1243			.refs()
1244			.find(|r| {
1245				r.kind == b"method_call"
1246					&& r.target.as_view().segments().last().unwrap().name == b"append"
1247			})
1248			.expect("method_call append");
1249		assert_eq!(r.confidence, b"unresolved".to_vec());
1250	}
1251
1252	#[test]
1253	fn extract_open_union_return_keeps_known_type_with_open_marker() {
1254		let src = "class User:\n    pass\n\ndef maybe_make() -> User | None:\n    return None\n";
1255		let g = extract_default("m.py", src, &make_anchor(), false);
1256		let fact = g
1257			.refs()
1258			.find(|reference| reference.kind == b"returns_type")
1259			.expect("known User return candidate");
1260		assert_eq!(
1261			fact.target.as_view().segments().last().unwrap().name,
1262			b"User"
1263		);
1264		assert_eq!(fact.receiver_hint, b"python_open_type_set".to_vec());
1265	}
1266
1267	#[test]
1268	fn extract_closed_union_return_emits_each_type_fact() {
1269		let src = "class Alpha:\n    pass\n\nclass Beta:\n    pass\n\ndef make() -> Alpha | Beta:\n    return Alpha()\n";
1270		let g = extract_default("m.py", src, &make_anchor(), false);
1271		let mut targets = g
1272			.refs()
1273			.filter(|reference| reference.kind == b"returns_type")
1274			.map(|reference| {
1275				reference
1276					.target
1277					.as_view()
1278					.segments()
1279					.last()
1280					.unwrap()
1281					.name
1282					.to_vec()
1283			})
1284			.collect::<Vec<_>>();
1285		targets.sort();
1286		assert_eq!(targets, vec![b"Alpha".to_vec(), b"Beta".to_vec()]);
1287	}
1288
1289	#[test]
1290	fn extract_local_class_call_prefers_function_scoped_type() {
1291		let src = "class Local:\n    pass\n\ndef make():\n    class Local:\n        pass\n    return Local()\n";
1292		let g = extract_default("m.py", src, &make_anchor(), false);
1293		let r = g
1294			.refs()
1295			.find(|r| r.kind == b"instantiates")
1296			.expect("instantiates local Local");
1297		let target_last = r.target.as_view().segments().last().unwrap();
1298		assert_eq!(target_last.kind, b"class");
1299		assert_eq!(target_last.name, b"Local");
1300		let parent = r.target.parent().expect("class parent");
1301		let parent_last = parent.as_view().segments().last().unwrap();
1302		assert_eq!(parent_last.kind, b"function");
1303		assert_eq!(parent_last.name, b"make()");
1304	}
1305
1306	#[test]
1307	fn extract_keyword_argument_names_are_not_reads() {
1308		let src = "def save(value):\n    return dict(id=value)\n";
1309		let g = extract_default("m.py", src, &make_anchor(), false);
1310		assert!(!g.refs().any(|r| {
1311			r.kind == b"reads" && r.target.as_view().segments().last().unwrap().name == b"id"
1312		}));
1313	}
1314
1315	#[test]
1316	fn extract_attribute_tail_is_not_a_bare_read() {
1317		let src = "def f(payment):\n    return payment.id\n";
1318		let g = extract_default("m.py", src, &make_anchor(), false);
1319		assert!(!g.refs().any(|r| {
1320			r.kind == b"reads" && r.target.as_view().segments().last().unwrap().name == b"id"
1321		}));
1322	}
1323
1324	#[test]
1325	fn extract_function_docstring_emits_comment_def_parented_on_function() {
1326		let src = "def f():\n    \"\"\"docstring\"\"\"\n    return 0\n";
1327		let g = extract_default("m.py", src, &make_anchor(), false);
1328		let fn_moniker = MonikerBuilder::new()
1329			.project(b"app")
1330			.segment(b"lang", b"python")
1331			.segment(b"module", b"m")
1332			.segment(b"function", b"f()")
1333			.build();
1334		let docstring_count = g
1335			.defs()
1336			.filter(|d| d.kind == b"comment")
1337			.filter(|d| {
1338				d.parent
1339					.and_then(|i| g.defs().nth(i))
1340					.is_some_and(|p| p.moniker == fn_moniker)
1341			})
1342			.count();
1343		assert_eq!(
1344			docstring_count,
1345			1,
1346			"function docstring must emit one comment def parented on the function. defs: {:?}",
1347			g.def_monikers()
1348		);
1349	}
1350
1351	#[test]
1352	fn extract_class_and_module_docstring_each_emit_one_comment() {
1353		let src = "\"\"\"module doc\"\"\"\nclass A:\n    \"\"\"class doc\"\"\"\n    pass\n";
1354		let g = extract_default("m.py", src, &make_anchor(), false);
1355		assert_eq!(
1356			g.defs().filter(|d| d.kind == b"comment").count(),
1357			2,
1358			"module-level and class docstrings should each yield one comment def. defs: {:?}",
1359			g.def_monikers()
1360		);
1361	}
1362
1363	#[test]
1364	fn extract_non_docstring_string_at_start_is_not_a_comment() {
1365		let src = "x = \"hello\"\n";
1366		let g = extract_default("m.py", src, &make_anchor(), false);
1367		assert_eq!(
1368			g.defs().filter(|d| d.kind == b"comment").count(),
1369			0,
1370			"string literals that aren't bare expression-statement-strings must NOT be treated as docstrings"
1371		);
1372	}
1373}