Skip to main content

code_moniker_core/lang/c/
mod.rs

1use std::collections::BTreeSet;
2use std::sync::Arc;
3
4use tree_sitter::{Language, Parser, Tree};
5
6use crate::core::code_graph::CodeGraph;
7use crate::core::moniker::Moniker;
8use crate::core::shape::Shape;
9
10use crate::lang::KindSpec;
11
12mod kinds;
13mod sdk_pipeline;
14
15#[derive(Clone, Debug, Default)]
16pub struct Presets {
17	/// Project-relative compiler include roots, in search order.
18	pub include_paths: Vec<String>,
19	/// Project-relative paths known to exist in the indexed source root.
20	pub workspace_files: Arc<BTreeSet<String>>,
21	/// Package owning unresolved quoted headers declared by the build system.
22	pub external_include_package: Option<String>,
23}
24
25pub fn parse(source: &str) -> Tree {
26	let mut parser = Parser::new();
27	let language: Language = tree_sitter_c::LANGUAGE.into();
28	parser.set_language(&language).unwrap_or_else(|err| {
29		panic!("failed to load tree-sitter C grammar: {err}");
30	});
31	parser.parse(source, None).unwrap_or_else(|| {
32		panic!("tree-sitter parse returned None on a non-cancelled call");
33	})
34}
35
36pub fn extract(
37	uri: &str,
38	source: &str,
39	anchor: &Moniker,
40	deep: bool,
41	presets: &Presets,
42) -> CodeGraph {
43	sdk_pipeline::extract(uri, source, anchor, deep, presets)
44}
45
46pub struct Lang;
47
48const DEF_KINDS: &[&str] = &[
49	"struct",
50	"enum",
51	"type",
52	"func",
53	"macro",
54	"field",
55	"enum_constant",
56	"var",
57	"const",
58];
59
60const DEF_KIND_SPECS: &[KindSpec] = &[
61	KindSpec::new("struct", Shape::Type, 20, "struct"),
62	KindSpec::new("enum", Shape::Type, 21, "enum"),
63	KindSpec::new("type", Shape::Type, 22, "typedef"),
64	KindSpec::new("enum_constant", Shape::Value, 30, "enum constant"),
65	KindSpec::new("field", Shape::Value, 31, "field"),
66	KindSpec::new("func", Shape::Callable, 40, "function"),
67	KindSpec::new("macro", Shape::Callable, 41, "macro"),
68	KindSpec::new("const", Shape::Value, 60, "define"),
69	KindSpec::new("var", Shape::Value, 61, "variable"),
70];
71
72impl crate::lang::LangExtractor for Lang {
73	type Presets = Presets;
74	const LANG_TAG: &'static str = "c";
75	const ALLOWED_KINDS: &'static [&'static str] = DEF_KINDS;
76	const KIND_SPECS: &'static [KindSpec] = DEF_KIND_SPECS;
77	const ALLOWED_VISIBILITIES: &'static [&'static str] = &["public", "module"];
78
79	fn extract(
80		uri: &str,
81		source: &str,
82		anchor: &Moniker,
83		deep: bool,
84		presets: &Self::Presets,
85	) -> CodeGraph {
86		extract(uri, source, anchor, deep, presets)
87	}
88}
89
90#[cfg(test)]
91mod tests {
92	use super::*;
93	use crate::core::moniker::MonikerBuilder;
94	use crate::lang::assert_conformance;
95
96	fn make_anchor() -> Moniker {
97		MonikerBuilder::new().project(b"app").build()
98	}
99
100	fn extract_default(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
101		let g = extract(uri, source, anchor, deep, &Presets::default());
102		assert_conformance::<super::Lang>(&g, anchor);
103		g
104	}
105
106	#[test]
107	fn parse_empty_returns_translation_unit() {
108		let tree = parse("");
109		assert_eq!(tree.root_node().kind(), "translation_unit");
110	}
111
112	#[test]
113	fn extract_module_strips_c_extension_and_keeps_h() {
114		let g = extract_default("src/util/text.c", "int x;\n", &make_anchor(), false);
115		let expected = MonikerBuilder::new()
116			.project(b"app")
117			.segment(b"lang", b"c")
118			.segment(b"dir", b"src")
119			.segment(b"dir", b"util")
120			.segment(b"module", b"text")
121			.build();
122		assert_eq!(g.root(), &expected);
123
124		let h = extract_default("src/util/text.h", "int y;\n", &make_anchor(), false);
125		let expected_h = MonikerBuilder::new()
126			.project(b"app")
127			.segment(b"lang", b"c")
128			.segment(b"dir", b"src")
129			.segment(b"dir", b"util")
130			.segment(b"module", b"text.h")
131			.build();
132		assert_eq!(h.root(), &expected_h);
133	}
134
135	#[test]
136	fn extract_function_definition_with_pointer_params() {
137		let src = "int run(char *name, int n) { return n; }\n";
138		let g = extract_default("main.c", src, &make_anchor(), false);
139		let run = MonikerBuilder::new()
140			.project(b"app")
141			.segment(b"lang", b"c")
142			.segment(b"module", b"main")
143			.segment(b"func", b"run(name:char*,n:int)")
144			.build();
145		assert!(
146			g.contains(&run),
147			"function def expected; defs: {:?}",
148			g.def_monikers()
149		);
150	}
151
152	#[test]
153	fn extract_static_function_is_module_visible() {
154		let src = "static void helper(void) {}\n";
155		let g = extract_default("main.c", src, &make_anchor(), false);
156		let def = g.defs().find(|d| d.kind == b"func").expect("func def");
157		assert_eq!(def.visibility, b"module".to_vec());
158		let name = def
159			.moniker
160			.as_view()
161			.segments()
162			.last()
163			.unwrap()
164			.name
165			.to_vec();
166		assert_eq!(name, b"helper()".to_vec(), "(void) collapses to zero slots");
167	}
168
169	#[test]
170	fn extract_struct_with_fields_and_typedef() {
171		let src = "typedef struct obj { int refcount; void *ptr; } obj;\n";
172		let g = extract_default("obj.c", src, &make_anchor(), false);
173		let strukt = MonikerBuilder::new()
174			.project(b"app")
175			.segment(b"lang", b"c")
176			.segment(b"module", b"obj")
177			.segment(b"struct", b"obj")
178			.build();
179		let field = MonikerBuilder::new()
180			.project(b"app")
181			.segment(b"lang", b"c")
182			.segment(b"module", b"obj")
183			.segment(b"struct", b"obj")
184			.segment(b"field", b"refcount")
185			.build();
186		assert!(g.contains(&strukt));
187		assert!(g.contains(&field));
188	}
189
190	#[test]
191	fn extract_anonymous_typedef_struct_owns_fields() {
192		let src = "typedef struct { int len; char buf[8]; } sds;\n";
193		let g = extract_default("sds.c", src, &make_anchor(), false);
194		let ty = MonikerBuilder::new()
195			.project(b"app")
196			.segment(b"lang", b"c")
197			.segment(b"module", b"sds")
198			.segment(b"type", b"sds")
199			.build();
200		let field = MonikerBuilder::new()
201			.project(b"app")
202			.segment(b"lang", b"c")
203			.segment(b"module", b"sds")
204			.segment(b"type", b"sds")
205			.segment(b"field", b"len")
206			.build();
207		assert!(g.contains(&ty));
208		assert!(g.contains(&field));
209	}
210
211	#[test]
212	fn extract_enum_constants_are_defs() {
213		let src = "enum color { RED, GREEN = 2 };\n";
214		let g = extract_default("color.c", src, &make_anchor(), false);
215		let red = MonikerBuilder::new()
216			.project(b"app")
217			.segment(b"lang", b"c")
218			.segment(b"module", b"color")
219			.segment(b"enum", b"color")
220			.segment(b"enum_constant", b"RED")
221			.build();
222		assert!(g.contains(&red));
223	}
224
225	#[test]
226	fn extract_macros_as_defs() {
227		let src = "#define MAX_LEN 128\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\n";
228		let g = extract_default("m.c", src, &make_anchor(), false);
229		let object = MonikerBuilder::new()
230			.project(b"app")
231			.segment(b"lang", b"c")
232			.segment(b"module", b"m")
233			.segment(b"const", b"MAX_LEN")
234			.build();
235		let function_like = MonikerBuilder::new()
236			.project(b"app")
237			.segment(b"lang", b"c")
238			.segment(b"module", b"m")
239			.segment(b"macro", b"MIN(a,b)")
240			.build();
241		assert!(g.contains(&object));
242		assert!(g.contains(&function_like));
243	}
244
245	#[test]
246	fn extract_header_guard_is_not_a_definition() {
247		let src = "#ifndef UTIL_H\n#define UTIL_H\n#define MAX_LEN 128\n#endif\n";
248		let g = extract_default("util.h", src, &make_anchor(), false);
249		let names = g
250			.defs()
251			.filter_map(|definition| {
252				definition
253					.moniker
254					.as_view()
255					.segments()
256					.last()
257					.map(|segment| segment.name.to_vec())
258			})
259			.collect::<Vec<_>>();
260		assert!(!names.iter().any(|name| name == b"UTIL_H"));
261		assert!(names.iter().any(|name| name == b"MAX_LEN"));
262	}
263
264	#[test]
265	fn extract_pointer_fields_and_vars_keep_pointer_signature() {
266		let src = "struct item { char *name; }; extern struct item *current;";
267		let g = extract_default("types.c", src, &make_anchor(), false);
268		let field = g
269			.defs()
270			.find(|definition| definition.kind == b"field")
271			.expect("field definition");
272		let variable = g
273			.defs()
274			.find(|definition| definition.kind == b"var")
275			.expect("variable definition");
276		assert_eq!(field.signature, b"char*");
277		assert_eq!(variable.signature, b"structitem*");
278	}
279
280	#[test]
281	fn extract_field_before_trailing_attribute_macro() {
282		let source = "typedef struct Table { int *value field_attr(ignore); int count field_attr(ignore); } Table;\n";
283		let graph = extract_default("table.h", source, &make_anchor(), false);
284
285		assert!(graph.defs().any(|definition| {
286			definition.kind == b"field"
287				&& definition
288					.moniker
289					.as_view()
290					.segments()
291					.last()
292					.is_some_and(|segment| segment.name == b"value")
293		}));
294		assert!(graph.defs().any(|definition| {
295			definition.kind == b"field"
296				&& definition
297					.moniker
298					.as_view()
299					.segments()
300					.last()
301					.is_some_and(|segment| segment.name == b"count")
302		}));
303	}
304
305	#[test]
306	fn extract_system_include_is_external_and_local_include_internal() {
307		let src = "#include <stdio.h>\n#include <project/api.h>\n#include \"vendor/util.h\"\n";
308		let presets = Presets {
309			include_paths: vec![String::new()],
310			workspace_files: Arc::new(BTreeSet::from([
311				"main.c".to_string(),
312				"project/api.h".to_string(),
313				"vendor/util.h".to_string(),
314			])),
315			..Presets::default()
316		};
317		let g = extract("main.c", src, &make_anchor(), false, &presets);
318		assert_conformance::<super::Lang>(&g, &make_anchor());
319		let external = g
320			.refs()
321			.find(|r| r.kind == b"imports_module" && r.confidence == b"external".to_vec())
322			.expect("system include ref");
323		assert!(
324			external
325				.target
326				.as_view()
327				.segments()
328				.any(|s| s.kind == b"sdk" && s.name == b"c"),
329		);
330		assert!(
331			external
332				.target
333				.as_view()
334				.segments()
335				.any(|s| s.kind == b"path" && s.name == b"stdio")
336		);
337		let internal = g
338			.refs()
339			.find(|r| r.kind == b"imports_module" && r.confidence == b"imported".to_vec())
340			.expect("local include ref");
341		assert!(
342			internal
343				.target
344				.as_view()
345				.segments()
346				.any(|s| s.kind == b"module" && s.name == b"api.h"),
347		);
348		let quoted = g
349			.refs()
350			.find(|r| {
351				r.kind == b"imports_module"
352					&& r.target
353						.as_view()
354						.segments()
355						.any(|s| s.kind == b"module" && s.name == b"util.h")
356			})
357			.expect("quoted include ref");
358		assert!(
359			quoted
360				.target
361				.as_view()
362				.segments()
363				.any(|segment| { segment.kind == b"dir" && segment.name == b"vendor" })
364		);
365	}
366
367	#[test]
368	fn extract_missing_angle_include_is_external_dependency() {
369		let graph = extract_default(
370			"main.c",
371			"#include <protobuf-c/protobuf-c.h>\n",
372			&make_anchor(),
373			false,
374		);
375		let include = graph
376			.refs()
377			.find(|reference| reference.kind == b"imports_module")
378			.expect("include reference");
379
380		assert_eq!(include.confidence, b"external");
381		assert!(
382			include.target.as_view().segments().any(|segment| {
383				segment.kind == b"external_pkg" && segment.name == b"protobuf-c"
384			})
385		);
386	}
387
388	#[test]
389	fn pgxs_only_claims_known_postgresql_quoted_headers() {
390		let presets = Presets {
391			external_include_package: Some("postgresql".to_string()),
392			..Presets::default()
393		};
394		let graph = extract(
395			"extension.c",
396			"#include \"postgres.h\"\n#include \"local_generated.h\"\n",
397			&make_anchor(),
398			false,
399			&presets,
400		);
401		let includes = graph.refs().collect::<Vec<_>>();
402
403		assert!(includes.iter().any(|reference| {
404			reference.receiver_hint == b"c_build_dependency"
405				&& reference
406					.target
407					.as_view()
408					.segments()
409					.any(|segment| segment.name == b"postgres")
410		}));
411		assert!(includes.iter().any(|reference| {
412			reference.receiver_hint.is_empty()
413				&& reference
414					.target
415					.as_view()
416					.segments()
417					.any(|segment| segment.name == b"local_generated.h")
418		}));
419	}
420
421	#[test]
422	fn extract_quoted_include_cannot_escape_workspace_root() {
423		let graph = extract_default(
424			"src/main.c",
425			"#include \"../../secret.h\"\n",
426			&make_anchor(),
427			false,
428		);
429		let include = graph
430			.refs()
431			.find(|reference| reference.kind == b"imports_module")
432			.expect("include reference");
433
434		assert_eq!(include.confidence, b"external");
435		assert!(
436			include.target.as_view().segments().any(|segment| {
437				segment.kind == b"external_pkg" && segment.name == b"filesystem"
438			})
439		);
440	}
441
442	#[test]
443	fn extract_quoted_include_uses_configured_search_root_when_not_source_relative() {
444		let src = "#include \"protobuf/model.h\"\n";
445		let presets = Presets {
446			include_paths: vec![String::new()],
447			workspace_files: Arc::new(BTreeSet::from([
448				"examples/main.c".to_string(),
449				"protobuf/model.h".to_string(),
450			])),
451			..Presets::default()
452		};
453		let graph = extract("examples/main.c", src, &make_anchor(), false, &presets);
454		let include = graph
455			.refs()
456			.find(|reference| reference.kind == b"imports_module")
457			.expect("include reference");
458		let segments = include.target.as_view().segments().collect::<Vec<_>>();
459
460		assert!(
461			segments
462				.iter()
463				.any(|segment| segment.kind == b"dir" && segment.name == b"protobuf")
464		);
465		assert!(
466			!segments
467				.iter()
468				.any(|segment| segment.kind == b"dir" && segment.name == b"examples")
469		);
470	}
471
472	#[test]
473	fn extract_function_definition_replaces_forward_declaration_slice() {
474		let src = "static int work(int value);\nstatic int work(int value) { return value; }\n";
475		let graph = extract_default("main.c", src, &make_anchor(), false);
476		let work = graph
477			.defs()
478			.find(|definition| definition.kind == b"func")
479			.expect("work definition");
480		assert_eq!(
481			work.position.expect("definition position").0,
482			src.rfind("static int work").unwrap() as u32,
483		);
484	}
485
486	#[test]
487	fn extract_same_file_call_resolves_and_libc_call_is_external() {
488		let src = "#include <string.h>\nstatic int helper(int a) { return a; }\nint run(void) { helper(1); return strlen(\"x\");}\n";
489		let g = extract_default("main.c", src, &make_anchor(), false);
490		let resolved = g
491			.refs()
492			.find(|r| {
493				r.kind == b"calls"
494					&& r.target.as_view().segments().last().unwrap().name == b"helper(a:int)"
495			})
496			.expect("resolved same-file call");
497		assert_eq!(resolved.confidence, b"resolved".to_vec());
498		let libc = g
499			.refs()
500			.find(|r| {
501				r.kind == b"calls"
502					&& r.target.as_view().segments().last().unwrap().name == b"strlen"
503			})
504			.expect("libc call");
505		assert_eq!(libc.confidence, b"external".to_vec());
506		assert!(
507			libc.target
508				.as_view()
509				.segments()
510				.any(|s| s.kind == b"sdk" && s.name == b"c"),
511		);
512		assert!(
513			libc.target
514				.as_view()
515				.segments()
516				.any(|s| s.kind == b"path" && s.name == b"libc")
517		);
518	}
519
520	#[test]
521	fn extract_unresolved_bare_call_stays_name_match_with_hints() {
522		let src = "int run(void) { return listLength(0); }\n";
523		let g = extract_default("main.c", src, &make_anchor(), false);
524		let r = g
525			.refs()
526			.find(|r| {
527				r.kind == b"calls"
528					&& r.target.as_view().segments().last().unwrap().name == b"listLength"
529			})
530			.expect("bare unresolved call");
531		assert_eq!(r.confidence, b"name_match".to_vec());
532		assert_eq!(r.call_name, b"listLength".to_vec());
533		assert_eq!(r.call_arity, Some(1));
534	}
535
536	#[test]
537	fn extract_typed_receiver_field_call_targets_the_field() {
538		let src =
539			"typedef struct vt { void (*free)(void *p); } vt;\nvoid run(vt *v) { v->free(0); }\n";
540		let g = extract_default("vt.c", src, &make_anchor(), false);
541		let call = g
542			.refs()
543			.find(|r| r.kind == b"calls" && r.call_name == b"free".to_vec())
544			.expect("field call");
545		assert_eq!(call.confidence, b"resolved".to_vec());
546		let target_kinds: Vec<&[u8]> = call.target.as_view().segments().map(|s| s.kind).collect();
547		assert!(target_kinds.contains(&b"field".as_slice()));
548	}
549
550	#[test]
551	fn extract_untyped_field_call_is_method_call_fact() {
552		let src = "int run(void *o) { return ((obj *)o)->count; }\nvoid go(void *h) { handler(h)->emit(1); }\n";
553		let g = extract_default("d.c", src, &make_anchor(), false);
554		let dynamic = g
555			.refs()
556			.find(|r| r.kind == b"method_call" && r.call_name == b"emit".to_vec())
557			.expect("untyped fn-pointer call kept as method_call fact");
558		assert_eq!(dynamic.confidence, b"name_match".to_vec());
559	}
560
561	#[test]
562	fn extract_local_type_flow_respects_nested_shadowing() {
563		let src = r#"
564typedef struct first { void (*run)(void); } first;
565typedef struct second { void (*stop)(void); } second;
566int use(first *value) {
567	value->run();
568	{ second *value; value->stop(); }
569	value->run();
570	return 0;
571}
572"#;
573		let graph = extract_default("flow.c", src, &make_anchor(), false);
574		let calls = graph
575			.refs()
576			.filter(|reference| reference.kind == b"calls")
577			.collect::<Vec<_>>();
578		assert_eq!(
579			calls
580				.iter()
581				.filter(|reference| {
582					reference
583						.target
584						.as_view()
585						.segments()
586						.last()
587						.is_some_and(|segment| segment.name == b"run")
588				})
589				.count(),
590			2,
591		);
592		assert!(calls.iter().any(|reference| {
593			reference
594				.target
595				.as_view()
596				.segments()
597				.last()
598				.is_some_and(|segment| segment.name == b"stop")
599		}));
600	}
601
602	#[test]
603	fn extract_global_and_macro_value_reads_are_accounted() {
604		let src =
605			"#define MAX_LEN 10\nint count;\nint run(void) { count = MAX_LEN; return count; }\n";
606		let graph = extract_default("reads.c", src, &make_anchor(), false);
607		let read_targets = graph
608			.refs()
609			.filter(|reference| reference.kind == b"reads")
610			.filter_map(|reference| {
611				reference
612					.target
613					.as_view()
614					.segments()
615					.last()
616					.map(|segment| segment.name.to_vec())
617			})
618			.collect::<Vec<_>>();
619		assert!(read_targets.iter().any(|name| name == b"MAX_LEN"));
620		assert!(read_targets.iter().any(|name| name == b"count"));
621	}
622
623	#[test]
624	fn extract_shallow_skips_param_and_local_defs() {
625		let src = "int run(int x) { int y = 1; return x + y; }\n";
626		let g = extract_default("main.c", src, &make_anchor(), false);
627		assert!(
628			g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
629			"shallow extraction must not emit param/local defs"
630		);
631	}
632
633	#[test]
634	fn extract_deep_emits_param_and_local_defs() {
635		let src = "int run(int x) { int y = 1; return x + y; }\n";
636		let g = extract_default("main.c", src, &make_anchor(), true);
637		let param = MonikerBuilder::new()
638			.project(b"app")
639			.segment(b"lang", b"c")
640			.segment(b"module", b"main")
641			.segment(b"func", b"run(x:int)")
642			.segment(b"param", b"x")
643			.build();
644		let local = MonikerBuilder::new()
645			.project(b"app")
646			.segment(b"lang", b"c")
647			.segment(b"module", b"main")
648			.segment(b"func", b"run(x:int)")
649			.segment(b"local", b"y")
650			.build();
651		assert!(g.contains(&param));
652		assert!(g.contains(&local));
653	}
654
655	#[test]
656	fn extract_defs_inside_preproc_conditionals() {
657		let src = "#ifdef __linux__\nstatic int only_linux(void) { return 1; }\n#else\nstatic int fallback(void) { return 2; }\n#endif\n";
658		let g = extract_default("port.c", src, &make_anchor(), false);
659		let names: Vec<Vec<u8>> = g
660			.defs()
661			.filter(|d| d.kind == b"func")
662			.map(|d| d.moniker.as_view().segments().last().unwrap().name.to_vec())
663			.collect();
664		assert!(names.contains(&b"only_linux()".to_vec()));
665		assert!(names.contains(&b"fallback()".to_vec()));
666	}
667
668	#[test]
669	fn extract_defs_inside_parser_recovery_nodes() {
670		let src = "#ifndef RECOVERY_H\n#define RECOVERY_H\nBROKEN(\n#define RECOVERED_VALUE 1\n)\n#endif\n";
671		let graph = extract_default("recovery.h", src, &make_anchor(), false);
672
673		assert!(graph.defs().any(|definition| {
674			definition.kind == b"const"
675				&& definition
676					.moniker
677					.as_view()
678					.segments()
679					.last()
680					.is_some_and(|segment| segment.name == b"RECOVERED_VALUE")
681		}));
682		assert!(!graph.defs().any(|definition| {
683			definition
684				.moniker
685				.as_view()
686				.segments()
687				.last()
688				.is_some_and(|segment| segment.name == b"RECOVERY_H")
689		}));
690	}
691
692	#[test]
693	fn extract_defs_inside_conditional_cpp_linkage_block() {
694		let src = "#if defined(__cplusplus)\nextern \"C\" {\n#endif\n#define PUBLIC_API 1\ntypedef struct api_state api_state;\n#if defined(__cplusplus)\n}\n#endif\n";
695		let graph = extract_default("api.h", src, &make_anchor(), false);
696		let names = graph
697			.defs()
698			.filter_map(|definition| {
699				definition
700					.moniker
701					.as_view()
702					.segments()
703					.last()
704					.map(|segment| segment.name.to_vec())
705			})
706			.collect::<Vec<_>>();
707
708		assert!(names.contains(&b"PUBLIC_API".to_vec()));
709		assert!(names.contains(&b"api_state".to_vec()));
710	}
711
712	#[test]
713	fn extract_generated_union_across_line_directives() {
714		let src = r#"
715#if ! defined YYSTYPE
716typedef union YYSTYPE
717#line 233 "gram.y"
718{
719	int ival;
720	void *node;
721}
722#line 1425 "gram.c"
723YYSTYPE;
724#endif
725int read_value(YYSTYPE *value) { return value->ival; }
726"#;
727		let g = extract_default("gram.c", src, &make_anchor(), true);
728		let union = MonikerBuilder::new()
729			.project(b"app")
730			.segment(b"lang", b"c")
731			.segment(b"module", b"gram")
732			.segment(b"struct", b"YYSTYPE")
733			.build();
734		let field = MonikerBuilder::new()
735			.project(b"app")
736			.segment(b"lang", b"c")
737			.segment(b"module", b"gram")
738			.segment(b"struct", b"YYSTYPE")
739			.segment(b"field", b"ival")
740			.build();
741		assert!(
742			g.contains(&union),
743			"generated union expected; defs: {:?}",
744			g.def_monikers()
745		);
746		assert!(
747			g.contains(&field),
748			"generated union field expected; defs: {:?}",
749			g.def_monikers()
750		);
751	}
752
753	#[test]
754	fn extract_prototype_in_header_is_a_func_def() {
755		let src = "struct list;\nstruct list *listCreate(void);\n";
756		let g = extract_default("list.h", src, &make_anchor(), false);
757		let proto = MonikerBuilder::new()
758			.project(b"app")
759			.segment(b"lang", b"c")
760			.segment(b"module", b"list.h")
761			.segment(b"func", b"listCreate()")
762			.build();
763		assert!(g.contains(&proto));
764	}
765
766	#[test]
767	fn extract_function_pointer_variable_is_var_not_func() {
768		let src = "int (*handler)(int) = 0;\n";
769		let g = extract_default("h.c", src, &make_anchor(), false);
770		let var = MonikerBuilder::new()
771			.project(b"app")
772			.segment(b"lang", b"c")
773			.segment(b"module", b"h")
774			.segment(b"var", b"handler")
775			.build();
776		assert!(g.contains(&var), "defs: {:?}", g.def_monikers());
777		assert!(g.defs().all(|d| d.kind != b"func"));
778	}
779
780	#[test]
781	fn extract_shallow_function_pointer_call_remains_accounted() {
782		let source = "int run(void) { int (*handler)(void) = 0; return handler(); }\n";
783		let graph = extract_default("main.c", source, &make_anchor(), false);
784		let call = graph
785			.refs()
786			.find(|reference| {
787				reference.kind == b"calls" && reference.call_name == b"handler".to_vec()
788			})
789			.expect("function-pointer call fact");
790		assert_eq!(call.confidence, b"name_match".to_vec());
791		let target = call
792			.target
793			.as_view()
794			.segments()
795			.last()
796			.expect("local target");
797		assert_eq!(target.kind, b"local");
798		assert_eq!(target.name, b"handler");
799	}
800}