brokk_bifrost_python/
syntax.rs1use brokk_bifrost_core::analyzer::Range;
2use brokk_bifrost_core::cancellation::CancellationToken;
3use brokk_bifrost_core::hash::HashSet;
4use tree_sitter::{Node, Tree};
5
6#[derive(Debug, Default)]
7pub struct PythonOverloadDecoratorBindings {
8 direct: HashSet<String>,
9 namespaces: HashSet<String>,
10}
11
12impl PythonOverloadDecoratorBindings {
13 pub fn collect(root: Node<'_>, source: &str) -> Self {
14 let mut bindings = Self::default();
15 let mut stack = vec![root];
16
17 while let Some(node) = stack.pop() {
18 match node.kind() {
19 "function_definition" | "class_definition" | "lambda" => continue,
20 "import_statement" => bindings.collect_namespace_imports(node, source),
21 "import_from_statement" => bindings.collect_direct_imports(node, source),
22 _ => {}
23 }
24
25 let mut cursor = node.walk();
26 let children: Vec<_> = node.named_children(&mut cursor).collect();
27 stack.extend(children.into_iter().rev());
28 }
29
30 bindings
31 }
32
33 fn collect_namespace_imports(&mut self, node: Node<'_>, source: &str) {
34 let mut cursor = node.walk();
35 for imported in node.children_by_field_name("name", &mut cursor) {
36 match imported.kind() {
37 "dotted_name" => {
38 let module = node_text(imported, source).trim();
39 if is_typing_module(module) {
40 self.namespaces.insert(module.to_string());
41 }
42 }
43 "aliased_import" => {
44 let Some(name) = imported.child_by_field_name("name") else {
45 continue;
46 };
47 if !is_typing_module(node_text(name, source).trim()) {
48 continue;
49 }
50 let Some(alias) = imported.child_by_field_name("alias") else {
51 continue;
52 };
53 let alias = node_text(alias, source).trim();
54 if !alias.is_empty() {
55 self.namespaces.insert(alias.to_string());
56 }
57 }
58 _ => {}
59 }
60 }
61 }
62
63 fn collect_direct_imports(&mut self, node: Node<'_>, source: &str) {
64 let Some(module) = node.child_by_field_name("module_name") else {
65 return;
66 };
67 if !is_typing_module(node_text(module, source).trim()) {
68 return;
69 }
70
71 let mut cursor = node.walk();
72 for imported in node.children_by_field_name("name", &mut cursor) {
73 match imported.kind() {
74 "dotted_name" if node_text(imported, source).trim() == "overload" => {
75 self.direct.insert("overload".to_string());
76 }
77 "aliased_import" => {
78 let Some(name) = imported.child_by_field_name("name") else {
79 continue;
80 };
81 if node_text(name, source).trim() != "overload" {
82 continue;
83 }
84 let Some(alias) = imported.child_by_field_name("alias") else {
85 continue;
86 };
87 let alias = node_text(alias, source).trim();
88 if !alias.is_empty() {
89 self.direct.insert(alias.to_string());
90 }
91 }
92 _ => {}
93 }
94 }
95 }
96
97 pub fn decorates_as_overload(&self, function: Node<'_>, source: &str) -> bool {
98 let Some(parent) = function
99 .parent()
100 .filter(|node| node.kind() == "decorated_definition")
101 else {
102 return false;
103 };
104
105 let mut cursor = parent.walk();
106 parent
107 .named_children(&mut cursor)
108 .filter(|child| child.kind() == "decorator")
109 .filter_map(decorator_callee)
110 .any(|callee| match callee.kind() {
111 "identifier" => self.direct.contains(node_text(callee, source).trim()),
112 "attribute" => {
113 let Some(attribute) = callee.child_by_field_name("attribute") else {
114 return false;
115 };
116 if node_text(attribute, source).trim() != "overload" {
117 return false;
118 }
119 let Some(object) = callee.child_by_field_name("object") else {
120 return false;
121 };
122 object.kind() == "identifier"
123 && self.namespaces.contains(node_text(object, source).trim())
124 }
125 _ => false,
126 })
127 }
128}
129
130fn is_typing_module(module: &str) -> bool {
131 matches!(module, "typing" | "typing_extensions")
132}
133
134fn node_text<'a>(node: Node<'_>, source: &'a str) -> &'a str {
135 brokk_bifrost_core::analyzer::common::node_source_text(node, source)
136}
137
138pub fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
140 let mut current = expression;
141 loop {
142 match current.kind() {
143 "identifier" => return Some(current),
144 "attribute" => current = current.child_by_field_name("attribute")?,
145 "call" => current = current.child_by_field_name("function")?,
146 _ => return None,
147 }
148 }
149}
150
151pub fn decorator_callee<'tree>(decorator: Node<'tree>) -> Option<Node<'tree>> {
153 if decorator.kind() != "decorator" {
154 return None;
155 }
156 let mut expression = decorator.named_child(0)?;
157 while expression.kind() == "call" {
158 expression = expression.child_by_field_name("function")?;
159 }
160 Some(expression)
161}
162
163pub fn python_node_is_in_annotation(node: Node<'_>) -> bool {
166 let start = node.start_byte();
167 let end = node.end_byte();
168 let mut current = node;
169 while let Some(parent) = current.parent() {
170 let annotation = match parent.kind() {
171 "function_definition" => parent.child_by_field_name("return_type"),
172 "typed_parameter" | "typed_default_parameter" | "assignment" => {
173 parent.child_by_field_name("type")
174 }
175 _ => None,
176 };
177 if let Some(annotation) = annotation
178 && annotation.start_byte() <= start
179 && end <= annotation.end_byte()
180 {
181 return true;
182 }
183 current = parent;
184 }
185 false
186}
187
188pub fn python_deferred_annotation_identifier_ranges(
190 string: Node<'_>,
191 source: &str,
192 cancellation: Option<&CancellationToken>,
193) -> Option<Vec<Range>> {
194 let tree = python_deferred_annotation_tree(string, source, cancellation)?;
195
196 let mut ranges = Vec::new();
197 let mut stack = vec![tree.root_node()];
198 while let Some(current) = stack.pop() {
199 if cancellation.is_some_and(CancellationToken::is_cancelled) {
200 return None;
201 }
202 if current.kind() == "identifier" {
203 ranges.push(Range {
204 start_byte: current.start_byte(),
205 end_byte: current.end_byte(),
206 start_line: current.start_position().row + 1,
207 end_line: current.end_position().row + 1,
208 });
209 }
210 for index in (0..current.named_child_count()).rev() {
211 if let Some(child) = current.named_child(index) {
212 stack.push(child);
213 }
214 }
215 }
216 Some(ranges)
217}
218
219pub fn python_deferred_annotation_tree(
223 string: Node<'_>,
224 source: &str,
225 cancellation: Option<&CancellationToken>,
226) -> Option<Tree> {
227 if string.kind() != "string"
228 || string
229 .parent()
230 .is_some_and(|parent| parent.kind() == "concatenated_string")
231 || !python_node_is_in_annotation(string)
232 || python_string_is_literal_value(string, source)
233 {
234 return None;
235 }
236
237 let mut content = None;
238 for index in 0..string.named_child_count() {
239 let child = string.named_child(index)?;
240 match child.kind() {
241 "string_start" | "string_end" => {}
242 "string_content" if content.is_none() => content = Some(child),
243 _ => return None,
244 }
245 }
246 let content = content?;
247 let language = tree_sitter_python::LANGUAGE.into();
248 let tree = brokk_bifrost_core::analyzer::common::parse_source_range_with_cancellation(
249 &language,
250 source,
251 content.range(),
252 cancellation,
253 )?;
254 if tree.root_node().has_error() {
255 return None;
256 }
257 Some(tree)
258}
259
260fn python_string_is_literal_value(string: Node<'_>, source: &str) -> bool {
264 let start = string.start_byte();
265 let end = string.end_byte();
266 let mut current = string;
267 while let Some(parent) = current.parent() {
268 match parent.kind() {
269 "subscript" => {
270 let Some(value) = parent.child_by_field_name("value") else {
271 return false;
272 };
273 if value.start_byte() <= start && end <= value.end_byte() {
274 return false;
275 }
276 return python_literal_annotation_base(value, source);
277 }
278 "generic_type" => {
279 let Some(value) = parent.named_child(0) else {
280 return false;
281 };
282 return python_literal_annotation_base(value, source);
283 }
284 _ => current = parent,
285 }
286 }
287 false
288}
289
290fn python_literal_annotation_base(value: Node<'_>, source: &str) -> bool {
291 match value.kind() {
292 "identifier" => node_text(value, source) == "Literal",
293 "attribute" => {
294 let (Some(object), Some(attribute)) = (
295 value.child_by_field_name("object"),
296 value.child_by_field_name("attribute"),
297 ) else {
298 return false;
299 };
300 object.kind() == "identifier"
301 && matches!(node_text(object, source), "typing" | "typing_extensions")
302 && attribute.kind() == "identifier"
303 && node_text(attribute, source) == "Literal"
304 }
305 "member_type" => {
306 let mut identifiers = Vec::new();
307 let mut stack = vec![value];
308 while let Some(node) = stack.pop() {
309 if node.kind() == "identifier" {
310 identifiers.push(node_text(node, source));
311 continue;
312 }
313 for index in (0..node.named_child_count()).rev() {
314 if let Some(child) = node.named_child(index) {
315 stack.push(child);
316 }
317 }
318 }
319 matches!(
320 identifiers.as_slice(),
321 ["typing", "Literal"] | ["typing_extensions", "Literal"]
322 )
323 }
324 _ => false,
325 }
326}