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