brokk_bifrost_cpp/graph/
syntax.rs1use crate::graph::resolver::{
2 cpp_name_component_nodes, cpp_type_name_components, is_globally_qualified_cpp_name,
3 is_nested_type_node, qualified_owner_components,
4};
5use std::ops::Range;
6use tree_sitter::{Node, Parser};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct MacroReplacementTypeReference {
10 pub components: Vec<String>,
11 pub component_ranges: Vec<Range<usize>>,
12 pub global: bool,
13}
14
15pub fn object_macro_replacement_type_references(
23 node: Node<'_>,
24 source: &str,
25) -> Vec<MacroReplacementTypeReference> {
26 if node.kind() != "preproc_arg"
27 || !node.parent().is_some_and(|parent| {
28 parent.kind() == "preproc_def"
29 && parent
30 .child_by_field_name("value")
31 .is_some_and(|value| value == node)
32 })
33 {
34 return Vec::new();
35 }
36 let Some(replacement) = source.get(node.start_byte()..node.end_byte()) else {
37 return Vec::new();
38 };
39 const PREFIX: &str = "void __bifrost_macro_reference() { ";
40 let synthetic = format!("{PREFIX}{replacement}; }}");
41 let mut parser = Parser::new();
42 if parser
43 .set_language(&tree_sitter_cpp::LANGUAGE.into())
44 .is_err()
45 {
46 return Vec::new();
47 }
48 let Some(tree) = parser.parse(&synthetic, None) else {
49 return Vec::new();
50 };
51 if tree.root_node().has_error() {
52 return Vec::new();
53 }
54
55 let mut references = Vec::new();
56 let mut stack = vec![tree.root_node()];
57 while let Some(current) = stack.pop() {
58 let structured = if matches!(
59 current.kind(),
60 "type_identifier" | "scoped_type_identifier" | "template_type"
61 ) && !is_nested_type_node(current)
62 {
63 cpp_type_name_components(current, &synthetic)
64 .zip(cpp_name_component_nodes(current))
65 .map(|(components, nodes)| {
66 (components, nodes, is_globally_qualified_cpp_name(current))
67 })
68 } else if current.kind() == "qualified_identifier"
69 && !current.parent().is_some_and(|parent| {
70 matches!(
71 parent.kind(),
72 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
73 )
74 })
75 {
76 qualified_owner_components(current, &synthetic)
77 .map(|owner| (owner.names, owner.nodes, owner.global))
78 } else {
79 None
80 };
81 if let Some((components, component_nodes, global)) = structured {
82 let component_ranges = component_nodes
83 .into_iter()
84 .map(|component| {
85 let start = component.start_byte().checked_sub(PREFIX.len())?;
86 let end = component.end_byte().checked_sub(PREFIX.len())?;
87 (end <= replacement.len())
88 .then_some(node.start_byte() + start..node.start_byte() + end)
89 })
90 .collect::<Option<Vec<_>>>();
91 if let Some(component_ranges) = component_ranges
92 && component_ranges.len() == components.len()
93 {
94 let reference = MacroReplacementTypeReference {
95 components,
96 component_ranges,
97 global,
98 };
99 if !references.contains(&reference) {
100 references.push(reference);
101 }
102 }
103 }
104 for index in (0..current.named_child_count()).rev() {
105 if let Some(child) = current.named_child(index) {
106 stack.push(child);
107 }
108 }
109 }
110 references
111}
112
113#[derive(Clone)]
114pub struct QualifiedCallableValue<'tree> {
115 pub qualified: Node<'tree>,
116 pub global: bool,
117 pub owner_components: Vec<Node<'tree>>,
118 pub member: Node<'tree>,
119}
120
121pub fn explicit_qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
128 if node.kind() != "pointer_expression" || node.child_by_field_name("operator")?.kind() != "&" {
129 return None;
130 }
131 let qualified = node.child_by_field_name("argument")?;
132 qualified_callable_value_from_node(qualified)
133}
134
135pub fn qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
141 if let Some(value) = explicit_qualified_callable_value(node) {
142 return Some(value);
143 }
144 if node.kind() != "qualified_identifier" {
145 return None;
146 }
147 if crate::graph::resolver::is_declaration_name(node) {
148 return None;
149 }
150 if node.parent().is_some_and(|parent| {
151 parent.child_by_field_name("type") == Some(node)
152 || (parent.kind() == "call_expression"
153 && parent.child_by_field_name("function") == Some(node))
154 || (parent.kind() == "pointer_expression"
155 && parent.child_by_field_name("argument") == Some(node))
156 || matches!(
157 parent.kind(),
158 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
159 )
160 }) {
161 return None;
162 }
163 qualified_callable_value_from_node(node)
164}
165
166fn qualified_callable_value_from_node(qualified: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
167 if qualified.kind() != "qualified_identifier" {
168 return None;
169 }
170 let mut components = Vec::new();
171 let global = qualified.child_by_field_name("scope").is_none()
172 && qualified.child(0).is_some_and(|child| child.kind() == "::");
173 append_qualified_components(qualified, &mut components)?;
174 let member = components.pop()?;
175 if components.is_empty() {
176 return None;
177 }
178 Some(QualifiedCallableValue {
179 qualified,
180 global,
181 owner_components: components,
182 member,
183 })
184}
185
186fn append_qualified_components<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) -> Option<()> {
187 let mut stack = vec![node];
188 while let Some(current) = stack.pop() {
189 match current.kind() {
190 "identifier" | "namespace_identifier" | "type_identifier" | "operator_name" => {
191 out.push(current)
192 }
193 "qualified_identifier" | "scoped_identifier" => {
194 stack.push(current.child_by_field_name("name")?);
195 if let Some(scope) = current.child_by_field_name("scope") {
196 stack.push(scope);
197 } else if current.child(0).is_none_or(|child| child.kind() != "::") {
198 return None;
199 }
200 }
201 "template_type" | "template_function" => {
202 stack.push(current.child_by_field_name("name")?);
203 }
204 "nested_namespace_specifier" => {
205 for index in (0..current.named_child_count()).rev() {
206 stack.push(current.named_child(index)?);
207 }
208 }
209 _ => return None,
210 }
211 }
212 Some(())
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 fn references(source: &str) -> Vec<MacroReplacementTypeReference> {
220 let mut parser = Parser::new();
221 parser
222 .set_language(&tree_sitter_cpp::LANGUAGE.into())
223 .expect("C++ grammar");
224 let tree = parser.parse(source, None).expect("macro fixture tree");
225 let value = tree
226 .root_node()
227 .named_child(0)
228 .and_then(|definition| definition.child_by_field_name("value"))
229 .expect("macro replacement");
230 object_macro_replacement_type_references(value, source)
231 }
232
233 #[test]
234 fn object_macro_replacement_reparse_preserves_type_ranges() {
235 let source = "#define SETTINGS (*api::SettingsImpl::GetInstance())\n";
236 let references = references(source);
237 let reference = references
238 .iter()
239 .find(|reference| reference.components == ["api", "SettingsImpl"])
240 .expect("qualified callable owner");
241 let rendered = reference
242 .component_ranges
243 .iter()
244 .map(|range| &source[range.clone()])
245 .collect::<Vec<_>>();
246 assert_eq!(rendered, ["api", "SettingsImpl"]);
247 }
248
249 #[test]
250 fn macro_reparse_ignores_function_like_and_non_code_text() {
251 let function_like = "#define SETTINGS(Type) (*Type::GetInstance())\n";
252 assert!(references(function_like).is_empty());
253
254 let text = "#define SETTINGS \"SettingsImpl::GetInstance()\"\n";
255 assert!(references(text).is_empty());
256 }
257}