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 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 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(¶m));
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}