1use crate::declarations::node_text;
2use crate::graph::resolver::{
3 cpp_name_component_nodes, cpp_type_name_components, is_globally_qualified_cpp_name,
4 is_nested_type_node, qualified_owner_components,
5};
6use brokk_bifrost_core::analyzer::tree_walk::push_named_children_reversed;
7use std::ops::Range;
8use tree_sitter::{Node, Parser};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct MacroReplacementTypeReference {
12 pub components: Vec<String>,
13 pub component_ranges: Vec<Range<usize>>,
14 pub global: bool,
15}
16
17pub fn is_cpp_recovered_callable_declaration_reference(node: Node<'_>) -> bool {
28 if !matches!(node.kind(), "identifier" | "field_identifier") {
29 return false;
30 }
31 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
32 return false;
33 };
34 let Some(pointer) = error.parent().filter(|parent| {
35 parent.kind() == "pointer_declarator"
36 && parent.named_child(0) == Some(error)
37 && parent
38 .child_by_field_name("declarator")
39 .is_some_and(|declarator| declarator.kind() == "function_declarator")
40 }) else {
41 return false;
42 };
43 pointer.parent().is_some_and(|declaration| {
44 declaration.kind() == "declaration"
45 && declaration.child_by_field_name("declarator") == Some(pointer)
46 })
47}
48
49#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct MacroReplacementField {
55 pub name: String,
56 pub declaration: String,
57}
58
59#[derive(Clone, Debug, Default, PartialEq, Eq)]
65pub struct ObjectMacroReplacement {
66 pub fields: Vec<MacroReplacementField>,
67 pub nested: Vec<String>,
68}
69
70impl ObjectMacroReplacement {
71 pub fn is_empty(&self) -> bool {
72 self.fields.is_empty() && self.nested.is_empty()
73 }
74
75 pub fn intersect(&self, other: &Self) -> Self {
83 Self {
84 fields: self
85 .fields
86 .iter()
87 .filter(|field| other.fields.contains(field))
88 .cloned()
89 .collect(),
90 nested: self
91 .nested
92 .iter()
93 .filter(|name| other.nested.contains(name))
94 .cloned()
95 .collect(),
96 }
97 }
98}
99
100pub fn object_macro_replacement(replacement: &str) -> ObjectMacroReplacement {
113 if replacement.trim().is_empty() {
114 return ObjectMacroReplacement::default();
115 }
116 let normalized_replacement = normalize_macro_continuations(replacement);
117 const PREFIX: &str = "struct __bifrost_macro_fields { ";
118 let synthetic = format!("{PREFIX}{normalized_replacement} }};");
119 let mut parser = Parser::new();
120 if parser
121 .set_language(&tree_sitter_cpp::LANGUAGE.into())
122 .is_err()
123 {
124 return ObjectMacroReplacement::default();
125 }
126 let Some(tree) = parser.parse(&synthetic, None) else {
127 return ObjectMacroReplacement::default();
128 };
129 let mut stack = vec![tree.root_node()];
130 let body = loop {
131 let Some(current) = stack.pop() else {
132 return ObjectMacroReplacement::default();
133 };
134 if current.kind() == "struct_specifier"
135 && let Some(body) = current.child_by_field_name("body")
136 {
137 break body;
138 }
139 let mut cursor = current.walk();
140 for child in current.named_children(&mut cursor) {
141 stack.push(child);
142 }
143 };
144 let mut recovered = ObjectMacroReplacement::default();
145 let mut composed_terminators = Vec::new();
146 let mut cursor = body.walk();
147 for declaration in body.named_children(&mut cursor) {
148 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
149 continue;
150 }
151 if let Some((name, terminator)) = nested_object_macro_invocation(declaration, &synthetic) {
152 recovered.nested.push(name);
153 composed_terminators.push(terminator.id());
154 continue;
155 }
156 let Some(declarator) = declaration
157 .child_by_field_name("declarator")
158 .or_else(|| declaration.named_child(1))
159 else {
160 continue;
161 };
162 let Some(name) = macro_replacement_declarator_name(declarator, &synthetic) else {
163 continue;
164 };
165 let Some(declaration_text) =
166 declaration_text_without_synthetic_prefix(declaration, replacement, PREFIX.len())
167 else {
168 continue;
169 };
170 recovered.fields.push(MacroReplacementField {
171 name,
172 declaration: declaration_text,
173 });
174 }
175 if !malformed_regions_are_composition(tree.root_node(), &composed_terminators) {
176 return ObjectMacroReplacement::default();
177 }
178 recovered
179}
180
181fn nested_object_macro_invocation<'tree>(
186 declaration: Node<'tree>,
187 source: &str,
188) -> Option<(String, Node<'tree>)> {
189 if declaration.kind() != "field_declaration" {
190 return None;
191 }
192 let mut cursor = declaration.walk();
193 let children = declaration.children(&mut cursor).collect::<Vec<_>>();
194 let [name, terminator] = children.as_slice() else {
195 return None;
196 };
197 if name.kind() != "type_identifier" || terminator.kind() != ";" || !terminator.is_missing() {
198 return None;
199 }
200 let text = node_text(*name, source).trim();
201 (!text.is_empty()).then(|| (text.to_string(), *terminator))
202}
203
204fn malformed_regions_are_composition(root: Node<'_>, composed_terminators: &[usize]) -> bool {
209 let mut stack = vec![root];
210 while let Some(node) = stack.pop() {
211 if !node.has_error() && !node.is_missing() {
212 continue;
213 }
214 if (node.is_error() || node.is_missing()) && !composed_terminators.contains(&node.id()) {
215 return false;
216 }
217 let mut cursor = node.walk();
218 let children = node.children(&mut cursor).collect::<Vec<_>>();
219 stack.extend(children);
220 }
221 true
222}
223
224#[derive(Clone, Debug, PartialEq, Eq)]
227pub struct RecoveredAggregateField {
228 pub name: String,
229 pub declaration: String,
230 pub range: Range<usize>,
231}
232
233pub fn recovered_aggregate_fields(
243 source: &str,
244 span: Range<usize>,
245) -> Vec<RecoveredAggregateField> {
246 let Some(region) = source.get(span.clone()) else {
247 return Vec::new();
248 };
249 if region.trim().is_empty() {
250 return Vec::new();
251 }
252 const PREFIX: &str = "struct __bifrost_recovered_members { ";
253 let synthetic = format!("{PREFIX}{region} }};");
254 let mut parser = Parser::new();
255 if parser
256 .set_language(&tree_sitter_cpp::LANGUAGE.into())
257 .is_err()
258 {
259 return Vec::new();
260 }
261 let Some(tree) = parser.parse(&synthetic, None) else {
262 return Vec::new();
263 };
264 if tree.root_node().has_error() {
265 return Vec::new();
266 }
267 let mut stack = vec![tree.root_node()];
268 let body = loop {
269 let Some(current) = stack.pop() else {
270 return Vec::new();
271 };
272 if current.kind() == "struct_specifier"
273 && let Some(body) = current.child_by_field_name("body")
274 {
275 break body;
276 }
277 let mut cursor = current.walk();
278 for child in current.named_children(&mut cursor) {
279 stack.push(child);
280 }
281 };
282 let mut fields = Vec::new();
283 let mut cursor = body.walk();
284 for declaration in body.named_children(&mut cursor) {
285 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
286 continue;
287 }
288 let Some(declarator) = declaration
289 .child_by_field_name("declarator")
290 .or_else(|| declaration.named_child(1))
291 else {
292 continue;
293 };
294 let Some(name) = macro_replacement_declarator_name(declarator, &synthetic) else {
295 continue;
296 };
297 let Some(declaration_text) =
298 declaration_text_without_synthetic_prefix(declaration, region, PREFIX.len())
299 else {
300 continue;
301 };
302 let start = span.start + declaration.start_byte() - PREFIX.len();
303 let end = span.start + declaration.end_byte() - PREFIX.len();
304 fields.push(RecoveredAggregateField {
305 name,
306 declaration: declaration_text,
307 range: start..end,
308 });
309 }
310 fields
311}
312
313pub fn object_macro_replacement_span(node: Node<'_>, source: &str) -> Option<Range<usize>> {
324 if node.kind() != "preproc_def" {
325 return None;
326 }
327 let start = node.child_by_field_name("name")?.end_byte();
328 (start <= source.len()).then(|| start..logical_line_end(start, source))
329}
330
331pub fn function_macro_replacement_span(node: Node<'_>, source: &str) -> Option<Range<usize>> {
340 let parameters = match node.kind() {
341 "preproc_function_def" => node.child_by_field_name("parameters"),
342 "preproc_def" => {
343 let mut stack = vec![node];
346 let mut parameters = None;
347 while let Some(part) = stack.pop() {
348 if part.kind() == "preproc_params" {
349 parameters = Some(part);
350 break;
351 }
352 if part == node || part.is_error() {
353 push_named_children_reversed(part, &mut stack);
354 }
355 }
356 parameters
357 }
358 _ => None,
359 }?;
360 let start = parameters.end_byte();
361 (start <= source.len()).then(|| start..logical_line_end(start, source))
362}
363
364fn logical_line_end(start: usize, source: &str) -> usize {
368 let bytes = source.as_bytes();
369 let mut index = start;
370 while index < bytes.len() {
371 if bytes[index] != b'\n' {
372 index += 1;
373 continue;
374 }
375 let mut previous = index;
376 if previous > start && bytes[previous - 1] == b'\r' {
377 previous -= 1;
378 }
379 if previous > start && bytes[previous - 1] == b'\\' {
380 index += 1;
381 continue;
382 }
383 break;
384 }
385 index
386}
387
388pub(crate) fn normalize_macro_continuations(replacement: &str) -> String {
394 let source = replacement.as_bytes();
395 let mut normalized = source.to_vec();
396 let mut index = 0;
397 while index + 1 < source.len() {
398 if source[index] == b'\\' && source[index + 1] == b'\n' {
399 normalized[index] = b' ';
400 normalized[index + 1] = b' ';
401 index += 2;
402 } else if index + 2 < source.len()
403 && source[index] == b'\\'
404 && source[index + 1] == b'\r'
405 && source[index + 2] == b'\n'
406 {
407 normalized[index] = b' ';
408 normalized[index + 1] = b' ';
409 normalized[index + 2] = b' ';
410 index += 3;
411 } else {
412 index += 1;
413 }
414 }
415 String::from_utf8(normalized).expect("source text must remain valid UTF-8")
416}
417
418fn macro_replacement_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
419 match node.kind() {
420 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
421 let name = node_text(node, source).trim();
422 (!name.is_empty()).then(|| name.to_string())
423 }
424 "function_declarator" => None,
425 _ => node
426 .child_by_field_name("declarator")
427 .or_else(|| node.child_by_field_name("name"))
428 .and_then(|child| macro_replacement_declarator_name(child, source)),
429 }
430}
431
432fn declaration_text_without_synthetic_prefix(
433 node: Node<'_>,
434 source: &str,
435 prefix_len: usize,
436) -> Option<String> {
437 let start = node.start_byte().checked_sub(prefix_len)?;
438 let end = node.end_byte().checked_sub(prefix_len)?;
439 (end <= source.len()).then(|| source[start..end].to_string())
440}
441
442pub fn object_macro_replacement_type_references(
450 node: Node<'_>,
451 source: &str,
452) -> Vec<MacroReplacementTypeReference> {
453 if node.kind() != "preproc_arg"
454 || !node.parent().is_some_and(|parent| {
455 parent.kind() == "preproc_def"
456 && parent
457 .child_by_field_name("value")
458 .is_some_and(|value| value == node)
459 })
460 {
461 return Vec::new();
462 }
463 let Some(replacement) = source.get(node.start_byte()..node.end_byte()) else {
464 return Vec::new();
465 };
466 const PREFIX: &str = "void __bifrost_macro_reference() { ";
467 let synthetic = format!("{PREFIX}{replacement}; }}");
468 let mut parser = Parser::new();
469 if parser
470 .set_language(&tree_sitter_cpp::LANGUAGE.into())
471 .is_err()
472 {
473 return Vec::new();
474 }
475 let Some(tree) = parser.parse(&synthetic, None) else {
476 return Vec::new();
477 };
478 if tree.root_node().has_error() {
479 return Vec::new();
480 }
481
482 let mut references = Vec::new();
483 let mut stack = vec![tree.root_node()];
484 while let Some(current) = stack.pop() {
485 let structured = if matches!(
486 current.kind(),
487 "type_identifier" | "scoped_type_identifier" | "template_type"
488 ) && !is_nested_type_node(current)
489 {
490 cpp_type_name_components(current, &synthetic)
491 .zip(cpp_name_component_nodes(current))
492 .map(|(components, nodes)| {
493 (components, nodes, is_globally_qualified_cpp_name(current))
494 })
495 } else if current.kind() == "qualified_identifier"
496 && !current.parent().is_some_and(|parent| {
497 matches!(
498 parent.kind(),
499 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
500 )
501 })
502 {
503 qualified_owner_components(current, &synthetic)
504 .map(|owner| (owner.names, owner.nodes, owner.global))
505 } else {
506 None
507 };
508 if let Some((components, component_nodes, global)) = structured {
509 let component_ranges = component_nodes
510 .into_iter()
511 .map(|component| {
512 let start = component.start_byte().checked_sub(PREFIX.len())?;
513 let end = component.end_byte().checked_sub(PREFIX.len())?;
514 (end <= replacement.len())
515 .then_some(node.start_byte() + start..node.start_byte() + end)
516 })
517 .collect::<Option<Vec<_>>>();
518 if let Some(component_ranges) = component_ranges
519 && component_ranges.len() == components.len()
520 {
521 let reference = MacroReplacementTypeReference {
522 components,
523 component_ranges,
524 global,
525 };
526 if !references.contains(&reference) {
527 references.push(reference);
528 }
529 }
530 }
531 push_named_children_reversed(current, &mut stack);
532 }
533 references
534}
535
536#[derive(Clone)]
537pub struct QualifiedCallableValue<'tree> {
538 pub qualified: Node<'tree>,
539 pub global: bool,
540 pub owner_components: Vec<Node<'tree>>,
541 pub member: Node<'tree>,
542}
543
544pub fn explicit_qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
551 if node.kind() != "pointer_expression" || node.child_by_field_name("operator")?.kind() != "&" {
552 return None;
553 }
554 let qualified = node.child_by_field_name("argument")?;
555 qualified_callable_value_from_node(qualified)
556}
557
558pub fn qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
564 if let Some(value) = explicit_qualified_callable_value(node) {
565 return Some(value);
566 }
567 if node.kind() != "qualified_identifier" {
568 return None;
569 }
570 if crate::graph::resolver::is_declaration_name(node) {
571 return None;
572 }
573 if node.parent().is_some_and(|parent| {
574 parent.child_by_field_name("type") == Some(node)
575 || (parent.kind() == "call_expression"
576 && parent.child_by_field_name("function") == Some(node))
577 || (parent.kind() == "pointer_expression"
578 && parent.child_by_field_name("argument") == Some(node))
579 || matches!(
580 parent.kind(),
581 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
582 )
583 }) {
584 return None;
585 }
586 qualified_callable_value_from_node(node)
587}
588
589fn qualified_callable_value_from_node(qualified: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
590 if qualified.kind() != "qualified_identifier" {
591 return None;
592 }
593 let mut components = Vec::new();
594 let global = qualified.child_by_field_name("scope").is_none()
595 && qualified.child(0).is_some_and(|child| child.kind() == "::");
596 append_qualified_components(qualified, &mut components)?;
597 let member = components.pop()?;
598 if components.is_empty() {
599 return None;
600 }
601 Some(QualifiedCallableValue {
602 qualified,
603 global,
604 owner_components: components,
605 member,
606 })
607}
608
609fn append_qualified_components<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) -> Option<()> {
610 let mut stack = vec![node];
611 while let Some(current) = stack.pop() {
612 match current.kind() {
613 "identifier" | "namespace_identifier" | "type_identifier" | "operator_name" => {
614 out.push(current)
615 }
616 "qualified_identifier" | "scoped_identifier" => {
617 stack.push(current.child_by_field_name("name")?);
618 if let Some(scope) = current.child_by_field_name("scope") {
619 stack.push(scope);
620 } else if current.child(0).is_none_or(|child| child.kind() != "::") {
621 return None;
622 }
623 }
624 "template_type" | "template_function" => {
625 stack.push(current.child_by_field_name("name")?);
626 }
627 "nested_namespace_specifier" => {
628 for index in (0..current.named_child_count()).rev() {
629 stack.push(current.named_child(index)?);
630 }
631 }
632 _ => return None,
633 }
634 }
635 Some(())
636}
637
638pub fn function_macro_included_ranges(source: &str) -> Option<Vec<tree_sitter::Range>> {
644 let mut parser = Parser::new();
645 parser
646 .set_language(&tree_sitter_cpp::LANGUAGE.into())
647 .ok()?;
648 let tree = parser.parse(source, None)?;
649 let mut replacements = Vec::new();
650 let mut comments = Vec::new();
651 let mut stack = vec![tree.root_node()];
652 while let Some(node) = stack.pop() {
653 if let Some(span) = function_macro_replacement_span(node, source) {
654 replacements.push(span);
655 }
656 if node.kind() == "comment" {
657 comments.push(node.range());
658 }
659 push_named_children_reversed(node, &mut stack);
660 }
661 comments.retain(|comment| {
662 replacements
663 .iter()
664 .any(|span| span.start <= comment.start_byte && span.end >= comment.end_byte)
665 });
666 if comments.is_empty() {
667 return None;
668 }
669 comments.sort_by_key(|range| range.start_byte);
670 let mut included = Vec::new();
671 let mut start_byte = 0;
672 let mut start_point = tree_sitter::Point::new(0, 0);
673 for comment in comments {
674 if start_byte < comment.start_byte {
675 included.push(tree_sitter::Range {
676 start_byte,
677 end_byte: comment.start_byte,
678 start_point,
679 end_point: comment.start_point,
680 });
681 }
682 start_byte = comment.end_byte;
683 start_point = comment.end_point;
684 }
685 if start_byte < source.len() {
686 included.push(tree_sitter::Range {
687 start_byte,
688 end_byte: source.len(),
689 start_point,
690 end_point: tree.root_node().end_position(),
691 });
692 }
693 Some(included)
694}
695
696#[cfg(test)]
697mod tests {
698 #[test]
699 fn issue_3089_macro_comments_do_not_consume_caller_function() {
700 let source = "#define PROCESS(handle, block) \\\ndo { /* comment */ \\\n int event; \\\n if (handle) block \\\n} while (0)\nstatic void caller(int handle) { PROCESS(handle, { event; }); }\n";
701 let ranges = super::function_macro_included_ranges(source).expect("macro comments");
702 let mut parser = tree_sitter::Parser::new();
703 parser
704 .set_language(&tree_sitter_cpp::LANGUAGE.into())
705 .unwrap();
706 parser.set_included_ranges(&ranges).unwrap();
707 let tree = parser.parse(source, None).unwrap();
708 let mut stack = vec![tree.root_node()];
709 let mut functions = Vec::new();
710 while let Some(node) = stack.pop() {
711 if node.kind() == "function_definition" {
712 functions.push(node);
713 }
714 super::push_named_children_reversed(node, &mut stack);
715 }
716 assert_eq!(functions.len(), 1, "{}", tree.root_node().to_sexp());
717 assert_eq!(
718 super::node_text(
719 functions[0]
720 .child_by_field_name("declarator")
721 .unwrap()
722 .child_by_field_name("declarator")
723 .unwrap(),
724 source
725 ),
726 "caller"
727 );
728 }
729 use super::*;
730
731 fn references(source: &str) -> Vec<MacroReplacementTypeReference> {
732 let mut parser = Parser::new();
733 parser
734 .set_language(&tree_sitter_cpp::LANGUAGE.into())
735 .expect("C++ grammar");
736 let tree = parser.parse(source, None).expect("macro fixture tree");
737 let value = tree
738 .root_node()
739 .named_child(0)
740 .and_then(|definition| definition.child_by_field_name("value"))
741 .expect("macro replacement");
742 object_macro_replacement_type_references(value, source)
743 }
744
745 #[test]
746 fn object_macro_replacement_reparse_preserves_type_ranges() {
747 let source = "#define SETTINGS (*api::SettingsImpl::GetInstance())\n";
748 let references = references(source);
749 let reference = references
750 .iter()
751 .find(|reference| reference.components == ["api", "SettingsImpl"])
752 .expect("qualified callable owner");
753 let rendered = reference
754 .component_ranges
755 .iter()
756 .map(|range| &source[range.clone()])
757 .collect::<Vec<_>>();
758 assert_eq!(rendered, ["api", "SettingsImpl"]);
759 }
760
761 #[test]
762 fn object_macro_replacement_fields_are_structured_and_direct_only() {
763 let replacement = object_macro_replacement(
764 r#"int public_value; \
765 union { int nested_value; }; \
766 unsigned private_value;"#,
767 );
768 assert_eq!(
769 replacement.fields,
770 vec![
771 MacroReplacementField {
772 name: "public_value".to_string(),
773 declaration: "int public_value;".to_string(),
774 },
775 MacroReplacementField {
776 name: "private_value".to_string(),
777 declaration: "unsigned private_value;".to_string(),
778 },
779 ]
780 );
781 assert!(replacement.nested.is_empty());
782 assert!(object_macro_replacement("not a declaration").is_empty());
783 }
784
785 #[test]
790 fn comment_split_replacement_keeps_every_member_and_its_composition() {
791 let source = "#define UV_HANDLE_FIELDS \\\n\
792 \x20 /* public */ \\\n\
793 \x20 void* data; \\\n\
794 \x20 /* read-only */ \\\n\
795 \x20 uv_loop_t* loop; \\\n\
796 \x20 UV_HANDLE_PRIVATE_FIELDS \\\n\
797 \nstruct uv_handle_s { UV_HANDLE_FIELDS };\n";
798 let mut parser = Parser::new();
799 parser
800 .set_language(&tree_sitter_cpp::LANGUAGE.into())
801 .expect("C++ grammar");
802 let tree = parser.parse(source, None).expect("macro fixture tree");
803 let mut stack = vec![tree.root_node()];
804 let definition = loop {
805 let current = stack.pop().expect("the fixture defines one object macro");
806 if current.kind() == "preproc_def" {
807 break current;
808 }
809 let mut cursor = current.walk();
810 for child in current.named_children(&mut cursor) {
811 stack.push(child);
812 }
813 };
814 let value = definition
815 .child_by_field_name("value")
816 .expect("truncated replacement token");
817 assert_eq!(
818 source[value.byte_range()]
819 .trim_end()
820 .trim_end_matches('\\')
821 .trim_end(),
822 "void* data;"
823 );
824
825 let span = object_macro_replacement_span(definition, source).expect("replacement span");
826 let replacement = object_macro_replacement(&source[span]);
827 assert_eq!(
828 replacement
829 .fields
830 .iter()
831 .map(|field| field.name.as_str())
832 .collect::<Vec<_>>(),
833 ["data", "loop"]
834 );
835 assert_eq!(replacement.nested, ["UV_HANDLE_PRIVATE_FIELDS"]);
836 }
837
838 #[test]
839 fn malformed_replacement_regions_still_refuse_the_whole_replacement() {
840 assert!(object_macro_replacement("int ok; struct {").is_empty());
841 }
842
843 #[test]
844 fn conflicting_replacements_keep_only_the_members_both_declare() {
845 let unix = object_macro_replacement("uv_handle_t* next_closing; unsigned int flags;");
846 let windows = object_macro_replacement("uv_handle_t* endgame_next; unsigned int flags;");
847 assert_eq!(
848 unix.intersect(&windows).fields,
849 vec![MacroReplacementField {
850 name: "flags".to_string(),
851 declaration: "unsigned int flags;".to_string(),
852 }]
853 );
854 }
855
856 #[test]
857 fn collapsed_aggregate_members_recover_their_names_and_ranges() {
858 let source = "struct uv_signal_s {\n UV_HANDLE_FIELDS\n uv_signal_cb signal_cb;\n};";
859 let span = source.find("uv_signal_cb").expect("member start")
860 ..source.find("signal_cb;").expect("member end") + "signal_cb;".len();
861 let fields = recovered_aggregate_fields(source, span);
862 assert_eq!(
863 fields
864 .iter()
865 .map(|field| (field.name.as_str(), field.declaration.as_str()))
866 .collect::<Vec<_>>(),
867 [("signal_cb", "uv_signal_cb signal_cb;")]
868 );
869 assert_eq!(&source[fields[0].range.clone()], "uv_signal_cb signal_cb;");
870 }
871
872 #[test]
873 fn macro_reparse_ignores_function_like_and_non_code_text() {
874 let function_like = "#define SETTINGS(Type) (*Type::GetInstance())\n";
875 assert!(references(function_like).is_empty());
876
877 let text = "#define SETTINGS \"SettingsImpl::GetInstance()\"\n";
878 assert!(references(text).is_empty());
879 }
880}