1use tree_sitter::Node;
2
3use brokk_bifrost_core::hash::{HashMap, HashSet};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum PythonLexicalNameResolution {
7 Unbound,
8 Local,
9 Nonlocal,
10 Global,
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum PythonDirectScopeBindingKind {
15 ClassDeclaration,
16 Other,
17}
18
19pub fn python_comprehension_binds_name_at(name: &str, reference: Node<'_>, source: &str) -> bool {
27 let mut current = reference;
28 while let Some(parent) = current.parent() {
29 if is_comprehension(parent.kind()) {
30 let mut cursor = parent.walk();
31 for clause in parent
32 .named_children(&mut cursor)
33 .filter(|child| child.kind() == "for_in_clause")
34 {
35 let Some(target) = clause.child_by_field_name("left") else {
36 continue;
37 };
38 let mut bound = false;
39 let _ = collect_binding_targets(target, source, &mut || true, |candidate, _| {
40 bound |= candidate == name;
41 });
42 if !bound {
43 continue;
44 }
45 let inside_own_iterable =
46 clause.child_by_field_name("right").is_some_and(|right| {
47 right.start_byte() <= reference.start_byte()
48 && reference.end_byte() <= right.end_byte()
49 });
50 if !inside_own_iterable {
51 return true;
52 }
53 }
54 }
55 if matches!(
56 parent.kind(),
57 "function_definition" | "lambda" | "class_definition" | "module"
58 ) {
59 break;
60 }
61 current = parent;
62 }
63 false
64}
65
66pub fn python_type_parameter_binds_name_at(name: &str, reference: Node<'_>, source: &str) -> bool {
73 let mut current = reference;
74 let mut enclosing_functions = Vec::new();
75 while let Some(parent) = current.parent() {
76 if parent.kind() == "function_definition" {
77 enclosing_functions.push(parent);
78 }
79 if matches!(
80 parent.kind(),
81 "class_definition" | "function_definition" | "type_alias_statement"
82 ) && let Some(parameters) = parent.child_by_field_name("type_parameters")
83 && !(parameters.start_byte() <= reference.start_byte()
84 && reference.end_byte() <= parameters.end_byte())
85 {
86 let mut cursor = parameters.walk();
87 for parameter in parameters.named_children(&mut cursor) {
88 let binder = if parameter.kind() == "identifier" {
89 Some(parameter)
90 } else {
91 first_identifier_bounded(parameter, &mut || true).flatten()
92 };
93 if binder.is_some_and(|binder| node_text(binder, source) == name) {
94 return !python_name_declared_global_at_in_functions(
99 name,
100 reference,
101 source,
102 enclosing_functions,
103 );
104 }
105 }
106 }
107 current = parent;
108 }
109 false
110}
111
112pub fn python_name_resolution_at(
116 name: &str,
117 reference: Node<'_>,
118 source: &str,
119) -> PythonLexicalNameResolution {
120 let mut functions = Vec::new();
121 let mut current = reference;
122 while let Some(parent) = current.parent() {
123 if parent.kind() == "function_definition" {
124 functions.push(parent);
125 }
126 current = parent;
127 }
128 python_name_resolution_at_in_functions(name, reference, source, functions)
129}
130
131fn python_name_declared_global_at_in_functions(
132 name: &str,
133 reference: Node<'_>,
134 source: &str,
135 functions: Vec<Node<'_>>,
136) -> bool {
137 python_name_resolution_at_in_functions(name, reference, source, functions)
138 == PythonLexicalNameResolution::Global
139}
140
141fn python_name_resolution_at_in_functions(
142 name: &str,
143 reference: Node<'_>,
144 source: &str,
145 functions: Vec<Node<'_>>,
146) -> PythonLexicalNameResolution {
147 for function in functions {
148 let parameter_names = function
149 .child_by_field_name("parameters")
150 .into_iter()
151 .flat_map(|parameters| {
152 let mut cursor = parameters.walk();
153 parameters
154 .named_children(&mut cursor)
155 .filter_map(|parameter| python_parameter_name(parameter, source))
156 .collect::<Vec<_>>()
157 })
158 .collect::<Vec<_>>();
159 let Some(inventory) =
160 PythonLexicalScopeInventory::collect_bounded(function, source, parameter_names, || {
161 true
162 })
163 else {
164 return PythonLexicalNameResolution::Unbound;
165 };
166 let resolution = inventory.name_resolution_at(name, reference);
167 match resolution {
168 PythonLexicalNameResolution::Global => return PythonLexicalNameResolution::Global,
169 PythonLexicalNameResolution::Local | PythonLexicalNameResolution::Nonlocal => {
170 return resolution;
171 }
172 PythonLexicalNameResolution::Unbound => {}
173 }
174 }
175 PythonLexicalNameResolution::Unbound
176}
177
178fn python_parameter_name(node: Node<'_>, source: &str) -> Option<String> {
179 match node.kind() {
180 "identifier" => Some(node_text(node, source).trim().to_string()),
181 "typed_parameter"
182 | "typed_default_parameter"
183 | "default_parameter"
184 | "list_splat_pattern"
185 | "dictionary_splat_pattern" => node
186 .child_by_field_name("name")
187 .or_else(|| {
188 let mut cursor = node.walk();
189 node.named_children(&mut cursor)
190 .find(|child| child.kind() == "identifier")
191 })
192 .and_then(|name| python_parameter_name(name, source)),
193 _ => None,
194 }
195 .filter(|name| !name.is_empty())
196}
197
198pub fn python_is_type_parameter_binder(node: Node<'_>) -> bool {
201 if node.kind() != "identifier" {
202 return false;
203 }
204 let mut current = node;
205 while let Some(parent) = current.parent() {
206 if matches!(
207 parent.kind(),
208 "class_definition" | "function_definition" | "type_alias_statement"
209 ) && let Some(parameters) = parent.child_by_field_name("type_parameters")
210 && parameters.start_byte() <= node.start_byte()
211 && node.end_byte() <= parameters.end_byte()
212 {
213 let mut cursor = parameters.walk();
214 return parameters.named_children(&mut cursor).any(|parameter| {
215 let binder = if parameter.kind() == "identifier" {
216 Some(parameter)
217 } else {
218 first_identifier_bounded(parameter, &mut || true).flatten()
219 };
220 binder.is_some_and(|binder| binder.id() == node.id())
221 });
222 }
223 if matches!(
224 parent.kind(),
225 "module" | "class_definition" | "function_definition" | "type_alias_statement"
226 ) {
227 return false;
228 }
229 current = parent;
230 }
231 false
232}
233
234#[derive(Clone, Copy, Debug)]
235pub struct PythonDirectScopeBinding<'tree> {
236 pub declaration: Node<'tree>,
237 pub kind: PythonDirectScopeBindingKind,
238}
239
240#[derive(Clone, Debug)]
241struct PythonLocalBinding<'tree> {
242 name: Box<str>,
243 declaration: Node<'tree>,
244}
245
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247enum PythonLocalBindingKind {
248 FunctionOnly,
249 Other,
250}
251
252#[derive(Clone, Debug)]
253struct PythonComprehensionBinding {
254 name: Box<str>,
255 start_byte: usize,
256 end_byte: usize,
257 enclosing_iterable_ranges: Vec<(usize, usize)>,
258}
259
260pub struct PythonLexicalScopeInventory<'tree> {
267 parameters: HashSet<Box<str>>,
268 locals: Vec<PythonLocalBinding<'tree>>,
269 local_names: HashMap<Box<str>, PythonLocalBindingKind>,
270 binding_writes: HashSet<Box<str>>,
271 globals: HashSet<Box<str>>,
272 nonlocals: HashSet<Box<str>>,
273 comprehensions: Vec<PythonComprehensionBinding>,
274}
275
276#[derive(Clone, Copy)]
277struct ScanFrame<'tree> {
278 node: Node<'tree>,
279 in_comprehension: bool,
280}
281
282impl<'tree> PythonLexicalScopeInventory<'tree> {
283 pub fn collect_bounded(
290 callable: Node<'tree>,
291 source: &str,
292 parameter_names: impl IntoIterator<Item = String>,
293 mut scope_step: impl FnMut() -> bool,
294 ) -> Option<Self> {
295 let mut inventory = Self {
296 parameters: parameter_names.into_iter().map(Box::<str>::from).collect(),
297 locals: Vec::new(),
298 local_names: HashMap::default(),
299 binding_writes: HashSet::default(),
300 globals: HashSet::default(),
301 nonlocals: HashSet::default(),
302 comprehensions: Vec::new(),
303 };
304 let Some(body) = callable.child_by_field_name("body") else {
305 return Some(inventory);
306 };
307 let mut stack = vec![ScanFrame {
308 node: body,
309 in_comprehension: false,
310 }];
311
312 while let Some(frame) = stack.pop() {
313 if !scope_step() {
314 return None;
315 }
316 let node = frame.node;
317 let nested_scope = node != body
318 && matches!(
319 node.kind(),
320 "function_definition" | "lambda" | "class_definition"
321 );
322 if nested_scope {
323 if matches!(node.kind(), "function_definition" | "class_definition")
324 && let Some(name) = node.child_by_field_name("name")
325 {
326 inventory.record_local_node(name, source);
327 }
328 push_nested_scope_header_children(
333 &mut stack,
334 node,
335 frame.in_comprehension,
336 &mut scope_step,
337 )?;
338 continue;
339 }
340
341 match node.kind() {
342 "global_statement" => {
343 collect_direct_identifier_names(node, source, &mut scope_step, |name| {
344 inventory.globals.insert(name.into());
345 })?;
346 continue;
347 }
348 "nonlocal_statement" => {
349 collect_direct_identifier_names(node, source, &mut scope_step, |name| {
350 inventory.nonlocals.insert(name.into());
351 })?;
352 continue;
353 }
354 "import_statement" | "import_from_statement" => {
355 collect_import_bindings(node, &mut scope_step, |binding| {
356 inventory.record_local_node(binding, source)
357 })?;
358 continue;
359 }
360 "assignment" | "augmented_assignment" => {
361 if let Some(target) = node.child_by_field_name("left") {
362 collect_binding_targets(
363 target,
364 source,
365 &mut scope_step,
366 |name, declaration| {
367 inventory.record_binding_write(name, declaration);
368 },
369 )?;
370 }
371 }
372 "type_alias_statement" => {
373 if let Some(target) = node.child_by_field_name("left")
374 && let Some(binding) = first_identifier_bounded(target, &mut scope_step)?
375 {
376 inventory.record_local_node(binding, source);
377 }
378 }
379 "named_expression" => {
380 if let Some(target) = node.child_by_field_name("name") {
383 collect_binding_targets(
384 target,
385 source,
386 &mut scope_step,
387 |name, declaration| {
388 inventory.record_binding_write(name, declaration);
389 },
390 )?;
391 }
392 }
393 "for_statement" => {
394 if let Some(target) = node.child_by_field_name("left") {
395 collect_binding_targets(
396 target,
397 source,
398 &mut scope_step,
399 |name, declaration| {
400 inventory.record_binding_write(name, declaration);
401 },
402 )?;
403 }
404 }
405 "for_in_clause" => {
406 }
409 "delete_statement" => {
410 for target in named_children_bounded(node, &mut scope_step)? {
411 collect_binding_targets(
412 target,
413 source,
414 &mut scope_step,
415 |name, declaration| {
416 inventory.record_binding_write(name, declaration);
417 },
418 )?;
419 }
420 continue;
421 }
422 "as_pattern" => {
423 if let Some(alias) = node.child_by_field_name("alias") {
424 collect_binding_targets(
425 alias,
426 source,
427 &mut scope_step,
428 |name, declaration| {
429 inventory.record_binding_write(name, declaration);
430 },
431 )?;
432 push_named_children_except(
433 &mut stack,
434 node,
435 alias,
436 frame.in_comprehension,
437 &mut scope_step,
438 )?;
439 continue;
440 }
441 }
442 "except_clause" => {
443 if let Some(alias) = node.child_by_field_name("alias") {
447 collect_binding_targets(
448 alias,
449 source,
450 &mut scope_step,
451 |name, declaration| {
452 inventory.record_binding_write(name, declaration);
453 },
454 )?;
455 }
456 }
457 "case_clause" => {
458 let children = named_children_bounded(node, &mut scope_step)?;
459 for child in children.iter().copied() {
460 if child.kind() == "case_pattern" {
461 collect_match_pattern_bindings(
462 child,
463 source,
464 &mut scope_step,
465 |name, declaration| {
466 inventory.record_binding_write(name, declaration);
467 },
468 )?;
469 }
470 }
471 for child in children.into_iter().rev() {
472 if child.kind() != "case_pattern" {
473 stack.push(ScanFrame {
474 node: child,
475 in_comprehension: frame.in_comprehension,
476 });
477 }
478 }
479 continue;
480 }
481 kind if is_comprehension(kind) => {
482 let range = (node.start_byte(), node.end_byte());
483 let children = named_children_bounded(node, &mut scope_step)?;
484 let enclosing_iterable_ranges = if let Some(first_clause) = children
485 .iter()
486 .copied()
487 .find(|child| child.kind() == "for_in_clause")
488 {
489 children_by_field_name_bounded(first_clause, "right", &mut scope_step)?
490 .into_iter()
491 .map(|iterable| (iterable.start_byte(), iterable.end_byte()))
492 .collect()
493 } else {
494 Vec::new()
495 };
496 for clause in children
497 .iter()
498 .copied()
499 .filter(|child| child.kind() == "for_in_clause")
500 {
501 if let Some(target) = clause.child_by_field_name("left") {
502 collect_binding_targets(target, source, &mut scope_step, |name, _| {
503 inventory.comprehensions.push(PythonComprehensionBinding {
504 name: name.into(),
505 start_byte: range.0,
506 end_byte: range.1,
507 enclosing_iterable_ranges: enclosing_iterable_ranges.clone(),
508 });
509 })?;
510 }
511 }
512 for child in children.into_iter().rev() {
513 stack.push(ScanFrame {
514 node: child,
515 in_comprehension: true,
516 });
517 }
518 continue;
519 }
520 _ => {}
521 }
522
523 push_named_children(&mut stack, node, frame.in_comprehension, &mut scope_step)?;
524 }
525
526 inventory.locals.retain(|binding| {
529 !inventory.globals.contains(binding.name.as_ref())
530 && !inventory.nonlocals.contains(binding.name.as_ref())
531 });
532 inventory.local_names.retain(|name, _| {
533 !inventory.globals.contains(name.as_ref())
534 && !inventory.nonlocals.contains(name.as_ref())
535 });
536 Some(inventory)
537 }
538
539 pub fn name_resolution_at(
540 &self,
541 name: &str,
542 reference: Node<'_>,
543 ) -> PythonLexicalNameResolution {
544 let reference_byte = reference.start_byte();
545 if self.comprehensions.iter().any(|binding| {
546 binding.name.as_ref() == name
547 && binding.start_byte <= reference_byte
548 && reference_byte < binding.end_byte
549 && !binding
550 .enclosing_iterable_ranges
551 .iter()
552 .any(|(start, end)| *start <= reference_byte && reference_byte < *end)
553 }) {
554 return PythonLexicalNameResolution::Local;
555 }
556 if self.nonlocals.contains(name) {
557 return PythonLexicalNameResolution::Nonlocal;
558 }
559 if self.globals.contains(name) {
560 return PythonLexicalNameResolution::Global;
561 }
562 if self.parameters.contains(name) || self.local_names.contains_key(name) {
563 PythonLexicalNameResolution::Local
564 } else {
565 PythonLexicalNameResolution::Unbound
566 }
567 }
568
569 pub fn resolves_to_local_function(&self, name: &str, reference: Node<'_>) -> bool {
570 let reference_byte = reference.start_byte();
571 !self.parameters.contains(name)
572 && !self.comprehensions.iter().any(|binding| {
573 binding.name.as_ref() == name
574 && binding.start_byte <= reference_byte
575 && reference_byte < binding.end_byte
576 && !binding
577 .enclosing_iterable_ranges
578 .iter()
579 .any(|(start, end)| *start <= reference_byte && reference_byte < *end)
580 })
581 && self.local_names.get(name) == Some(&PythonLocalBindingKind::FunctionOnly)
582 }
583
584 pub fn has_runtime_callable_binding_at(&self, name: &str, reference: Node<'_>) -> bool {
587 let reference_byte = reference.start_byte();
588 self.parameters.contains(name)
589 || self.binding_writes.contains(name)
590 || self.comprehensions.iter().any(|binding| {
591 binding.name.as_ref() == name
592 && binding.start_byte <= reference_byte
593 && reference_byte < binding.end_byte
594 && !binding
595 .enclosing_iterable_ranges
596 .iter()
597 .any(|(start, end)| *start <= reference_byte && reference_byte < *end)
598 })
599 }
600
601 pub fn local_function_declaration(
602 &self,
603 name: &str,
604 reference: Node<'_>,
605 ) -> Option<Node<'tree>> {
606 self.resolves_to_local_function(name, reference)
607 .then(|| {
608 self.locals
609 .iter()
610 .find(|binding| binding.name.as_ref() == name)
611 .and_then(|binding| binding.declaration.parent())
612 .filter(|declaration| declaration.kind() == "function_definition")
613 })
614 .flatten()
615 }
616
617 pub fn local_bindings(&self) -> impl Iterator<Item = (&str, Node<'tree>)> + '_ {
618 self.locals
619 .iter()
620 .map(|binding| (binding.name.as_ref(), binding.declaration))
621 }
622
623 fn record_local_node(&mut self, node: Node<'tree>, source: &str) {
624 let name = node_text(node, source);
625 self.record_local(name, node);
626 }
627
628 fn record_local(&mut self, name: &str, declaration: Node<'tree>) {
629 if name.is_empty() {
630 return;
631 }
632 let binding_kind = if is_function_declaration_name(declaration) {
633 PythonLocalBindingKind::FunctionOnly
634 } else {
635 PythonLocalBindingKind::Other
636 };
637 match self.local_names.entry(name.into()) {
638 std::collections::hash_map::Entry::Vacant(entry) => {
639 entry.insert(binding_kind);
640 self.locals.push(PythonLocalBinding {
641 name: name.into(),
642 declaration,
643 });
644 }
645 std::collections::hash_map::Entry::Occupied(mut entry) => {
646 entry.insert(PythonLocalBindingKind::Other);
647 }
648 }
649 }
650
651 fn record_binding_write(&mut self, name: &str, declaration: Node<'tree>) {
652 if !name.is_empty() {
653 self.binding_writes.insert(name.into());
654 }
655 self.record_local(name, declaration);
656 }
657}
658
659fn is_function_declaration_name(node: Node<'_>) -> bool {
660 node.parent().is_some_and(|parent| {
661 parent.kind() == "function_definition"
662 && parent
663 .child_by_field_name("name")
664 .is_some_and(|name| name.id() == node.id())
665 })
666}
667
668pub fn python_direct_scope_bindings_bounded<'tree>(
675 node: Node<'tree>,
676 source: &str,
677 mut scope_step: impl FnMut() -> bool,
678) -> Option<Vec<PythonDirectScopeBinding<'tree>>> {
679 let mut bindings = Vec::new();
680
681 match node.kind() {
682 "function_definition" => {
683 if let Some(name) = node.child_by_field_name("name") {
684 bindings.push(PythonDirectScopeBinding {
685 declaration: name,
686 kind: PythonDirectScopeBindingKind::Other,
687 });
688 }
689 }
690 "class_definition" => {
691 if let Some(name) = node.child_by_field_name("name") {
692 bindings.push(PythonDirectScopeBinding {
693 declaration: name,
694 kind: if is_direct_module_definition_bounded(node, &mut scope_step)? {
695 PythonDirectScopeBindingKind::ClassDeclaration
696 } else {
697 PythonDirectScopeBindingKind::Other
698 },
699 });
700 }
701 }
702 "import_statement" | "import_from_statement" => {
703 collect_import_bindings(node, &mut scope_step, |declaration| {
704 bindings.push(PythonDirectScopeBinding {
705 declaration,
706 kind: PythonDirectScopeBindingKind::Other,
707 });
708 })?;
709 }
710 "assignment" | "augmented_assignment" => {
711 if let Some(target) = node.child_by_field_name("left") {
712 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
713 bindings.push(PythonDirectScopeBinding {
714 declaration,
715 kind: PythonDirectScopeBindingKind::Other,
716 });
717 })?;
718 }
719 }
720 "type_alias_statement" => {
721 if let Some(target) = node.child_by_field_name("left")
722 && let Some(declaration) = first_identifier_bounded(target, &mut scope_step)?
723 {
724 bindings.push(PythonDirectScopeBinding {
725 declaration,
726 kind: PythonDirectScopeBindingKind::Other,
727 });
728 }
729 }
730 "named_expression" => {
731 if let Some(target) = node.child_by_field_name("name") {
732 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
733 bindings.push(PythonDirectScopeBinding {
734 declaration,
735 kind: PythonDirectScopeBindingKind::Other,
736 });
737 })?;
738 }
739 }
740 "for_statement" => {
741 if let Some(target) = node.child_by_field_name("left") {
742 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
743 bindings.push(PythonDirectScopeBinding {
744 declaration,
745 kind: PythonDirectScopeBindingKind::Other,
746 });
747 })?;
748 }
749 }
750 "delete_statement" => {
751 for target in named_children_bounded(node, &mut scope_step)? {
752 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
753 bindings.push(PythonDirectScopeBinding {
754 declaration,
755 kind: PythonDirectScopeBindingKind::Other,
756 });
757 })?;
758 }
759 }
760 "as_pattern" => {
761 if let Some(alias) = node.child_by_field_name("alias") {
762 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
763 bindings.push(PythonDirectScopeBinding {
764 declaration,
765 kind: PythonDirectScopeBindingKind::Other,
766 });
767 })?;
768 }
769 }
770 "except_clause" => {
771 if let Some(alias) = node.child_by_field_name("alias") {
772 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
773 bindings.push(PythonDirectScopeBinding {
774 declaration,
775 kind: PythonDirectScopeBindingKind::Other,
776 });
777 })?;
778 }
779 }
780 "case_clause" => {
781 for child in named_children_bounded(node, &mut scope_step)? {
782 if child.kind() == "case_pattern" {
783 collect_match_pattern_bindings(
784 child,
785 source,
786 &mut scope_step,
787 |_, declaration| {
788 bindings.push(PythonDirectScopeBinding {
789 declaration,
790 kind: PythonDirectScopeBindingKind::Other,
791 });
792 },
793 )?;
794 }
795 }
796 }
797 _ => {}
798 }
799 Some(bindings)
800}
801
802pub fn python_unambiguous_module_class_binding_bounded(
803 root: Node<'_>,
804 source: &str,
805 target_name: &str,
806 mut scope_step: impl FnMut() -> bool,
807) -> Option<bool> {
808 let mut matched = None;
809 let mut stack = vec![root];
810 while let Some(node) = stack.pop() {
811 if !scope_step() {
812 return None;
813 }
814 for binding in python_direct_scope_bindings_bounded(node, source, &mut scope_step)? {
815 if node_text(binding.declaration, source) != target_name {
816 continue;
817 }
818 if matched.is_some() {
819 return Some(false);
820 }
821 matched = Some(binding.kind);
822 if binding.kind == PythonDirectScopeBindingKind::Other {
823 return Some(false);
824 }
825 }
826
827 let body = matches!(
828 node.kind(),
829 "function_definition" | "class_definition" | "lambda"
830 )
831 .then(|| node.child_by_field_name("body").map(|child| child.id()))
832 .flatten();
833 let name = matches!(node.kind(), "function_definition" | "class_definition")
834 .then(|| node.child_by_field_name("name").map(|child| child.id()))
835 .flatten();
836 for child in named_children_bounded(node, &mut scope_step)?
837 .into_iter()
838 .filter(|child| Some(child.id()) != body && Some(child.id()) != name)
839 .rev()
840 {
841 stack.push(child);
842 }
843 }
844 Some(matched == Some(PythonDirectScopeBindingKind::ClassDeclaration))
845}
846
847fn is_direct_module_definition_bounded(
848 node: Node<'_>,
849 scope_step: &mut impl FnMut() -> bool,
850) -> Option<bool> {
851 if !scope_step() {
852 return None;
853 }
854 let Some(mut parent) = node.parent() else {
855 return Some(false);
856 };
857 if parent.kind() == "decorated_definition" {
858 if !scope_step() {
859 return None;
860 }
861 let Some(grandparent) = parent.parent() else {
862 return Some(false);
863 };
864 parent = grandparent;
865 }
866 Some(parent.kind() == "module")
867}
868
869fn collect_direct_identifier_names(
870 node: Node<'_>,
871 source: &str,
872 scope_step: &mut impl FnMut() -> bool,
873 mut record: impl FnMut(&str),
874) -> Option<()> {
875 for child in named_children_bounded(node, scope_step)? {
876 if child.kind() == "identifier" {
877 let name = node_text(child, source);
878 if !name.is_empty() {
879 record(name);
880 }
881 }
882 }
883 Some(())
884}
885
886fn collect_import_bindings<'tree>(
887 statement: Node<'tree>,
888 scope_step: &mut impl FnMut() -> bool,
889 mut record: impl FnMut(Node<'tree>),
890) -> Option<()> {
891 let mut cursor = statement.walk();
892 let mut imports = Vec::new();
893 for imported in statement.children_by_field_name("name", &mut cursor) {
894 if !scope_step() {
895 return None;
896 }
897 imports.push(imported);
898 }
899 for imported in imports {
900 if let Some(alias) = imported.child_by_field_name("alias") {
901 record(alias);
902 continue;
903 }
904 let name = imported.child_by_field_name("name").unwrap_or(imported);
905 let binding = if statement.kind() == "import_statement" {
906 first_identifier_bounded(name, scope_step)?
907 } else {
908 last_identifier_bounded(name, scope_step)?
909 };
910 if let Some(binding) = binding {
911 record(binding);
912 }
913 }
914 Some(())
915}
916
917fn first_identifier_bounded<'tree>(
918 root: Node<'tree>,
919 scope_step: &mut impl FnMut() -> bool,
920) -> Option<Option<Node<'tree>>> {
921 let mut stack = vec![root];
922 while let Some(node) = stack.pop() {
923 if !scope_step() {
924 return None;
925 }
926 if node.kind() == "identifier" {
927 return Some(Some(node));
928 }
929 let children = named_children_bounded(node, scope_step)?;
930 stack.extend(children.into_iter().rev());
931 }
932 Some(None)
933}
934
935fn last_identifier_bounded<'tree>(
936 root: Node<'tree>,
937 scope_step: &mut impl FnMut() -> bool,
938) -> Option<Option<Node<'tree>>> {
939 let mut result = None;
940 let mut stack = vec![root];
941 while let Some(node) = stack.pop() {
942 if !scope_step() {
943 return None;
944 }
945 if node.kind() == "identifier" {
946 result = Some(node);
947 continue;
948 }
949 let children = named_children_bounded(node, scope_step)?;
950 stack.extend(children.into_iter().rev());
951 }
952 Some(result)
953}
954
955fn collect_binding_targets<'tree>(
956 target: Node<'tree>,
957 source: &str,
958 scope_step: &mut impl FnMut() -> bool,
959 mut record: impl FnMut(&str, Node<'tree>),
960) -> Option<()> {
961 let mut stack = vec![target];
962 while let Some(node) = stack.pop() {
963 if !scope_step() {
964 return None;
965 }
966 match node.kind() {
967 "attribute" | "subscript" => continue,
970 "identifier" | "keyword_identifier" => {
971 let name = node_text(node, source);
972 if !name.is_empty() {
973 record(name, node);
974 }
975 continue;
976 }
977 _ => {}
978 }
979 let children = named_children_bounded(node, scope_step)?;
980 if node.kind() == "as_pattern_target" && children.is_empty() {
981 let name = node_text(node, source);
982 if !name.is_empty() {
983 record(name, node);
984 }
985 continue;
986 }
987 stack.extend(children.into_iter().rev());
988 }
989 Some(())
990}
991
992fn collect_match_pattern_bindings<'tree>(
993 pattern: Node<'tree>,
994 source: &str,
995 scope_step: &mut impl FnMut() -> bool,
996 mut record: impl FnMut(&str, Node<'tree>),
997) -> Option<()> {
998 let mut stack = vec![pattern];
999 while let Some(node) = stack.pop() {
1000 if !scope_step() {
1001 return None;
1002 }
1003 match node.kind() {
1004 "dotted_name" => {
1005 let identifiers = named_children_bounded(node, scope_step)?
1006 .into_iter()
1007 .filter(|child| child.kind() == "identifier")
1008 .collect::<Vec<_>>();
1009 if let [binding] = identifiers.as_slice() {
1010 let name = node_text(*binding, source);
1011 if !name.is_empty() {
1012 record(name, *binding);
1013 }
1014 }
1015 continue;
1016 }
1017 "splat_pattern" => {
1018 for child in named_children_bounded(node, scope_step)? {
1019 if child.kind() == "identifier" {
1020 let name = node_text(child, source);
1021 if !name.is_empty() {
1022 record(name, child);
1023 }
1024 }
1025 }
1026 continue;
1027 }
1028 "class_pattern" => {
1029 let mut children = named_children_bounded(node, scope_step)?;
1030 if children
1031 .first()
1032 .is_some_and(|child| child.kind() == "dotted_name")
1033 {
1034 children.remove(0);
1035 }
1036 stack.extend(children.into_iter().rev());
1037 continue;
1038 }
1039 "keyword_pattern" => {
1040 let mut children = named_children_bounded(node, scope_step)?;
1041 if children
1042 .first()
1043 .is_some_and(|child| child.kind() == "identifier")
1044 {
1045 children.remove(0);
1046 }
1047 stack.extend(children.into_iter().rev());
1048 continue;
1049 }
1050 "dict_pattern" => {
1051 let key_ids = children_by_field_name_bounded(node, "key", scope_step)?
1052 .into_iter()
1053 .map(|key| key.id())
1054 .collect::<HashSet<_>>();
1055 let children = named_children_bounded(node, scope_step)?;
1056 stack.extend(
1057 children
1058 .into_iter()
1059 .filter(|child| !key_ids.contains(&child.id()))
1060 .rev(),
1061 );
1062 continue;
1063 }
1064 "as_pattern" => {
1065 if let Some(alias) = node.child_by_field_name("alias") {
1066 collect_binding_targets(alias, source, scope_step, &mut record)?;
1067 let children = named_children_bounded(node, scope_step)?;
1068 stack.extend(
1069 children
1070 .into_iter()
1071 .filter(|child| child.id() != alias.id())
1072 .rev(),
1073 );
1074 continue;
1075 }
1076 let mut children = named_children_bounded(node, scope_step)?;
1077 if let Some(alias) = children
1078 .last()
1079 .copied()
1080 .filter(|child| child.kind() == "identifier")
1081 {
1082 let name = node_text(alias, source);
1083 if !name.is_empty() {
1084 record(name, alias);
1085 }
1086 children.pop();
1087 }
1088 stack.extend(children.into_iter().rev());
1089 continue;
1090 }
1091 "identifier" => {
1092 continue;
1095 }
1096 kind if is_pattern_literal(kind) => continue,
1097 _ => {}
1098 }
1099 let children = named_children_bounded(node, scope_step)?;
1100 stack.extend(children.into_iter().rev());
1101 }
1102 Some(())
1103}
1104
1105fn push_named_children<'tree>(
1106 stack: &mut Vec<ScanFrame<'tree>>,
1107 node: Node<'tree>,
1108 in_comprehension: bool,
1109 scope_step: &mut impl FnMut() -> bool,
1110) -> Option<()> {
1111 for child in named_children_bounded(node, scope_step)?.into_iter().rev() {
1112 stack.push(ScanFrame {
1113 node: child,
1114 in_comprehension,
1115 });
1116 }
1117 Some(())
1118}
1119
1120fn push_named_children_except<'tree>(
1121 stack: &mut Vec<ScanFrame<'tree>>,
1122 node: Node<'tree>,
1123 excluded: Node<'tree>,
1124 in_comprehension: bool,
1125 scope_step: &mut impl FnMut() -> bool,
1126) -> Option<()> {
1127 for child in named_children_bounded(node, scope_step)?
1128 .into_iter()
1129 .filter(|child| child.id() != excluded.id())
1130 .rev()
1131 {
1132 stack.push(ScanFrame {
1133 node: child,
1134 in_comprehension,
1135 });
1136 }
1137 Some(())
1138}
1139
1140fn push_nested_scope_header_children<'tree>(
1141 stack: &mut Vec<ScanFrame<'tree>>,
1142 node: Node<'tree>,
1143 in_comprehension: bool,
1144 scope_step: &mut impl FnMut() -> bool,
1145) -> Option<()> {
1146 let body = node.child_by_field_name("body").map(|child| child.id());
1147 let name = node.child_by_field_name("name").map(|child| child.id());
1148 for child in named_children_bounded(node, scope_step)?
1149 .into_iter()
1150 .filter(|child| Some(child.id()) != body && Some(child.id()) != name)
1151 .rev()
1152 {
1153 stack.push(ScanFrame {
1154 node: child,
1155 in_comprehension,
1156 });
1157 }
1158 Some(())
1159}
1160
1161fn named_children_bounded<'tree>(
1162 node: Node<'tree>,
1163 scope_step: &mut impl FnMut() -> bool,
1164) -> Option<Vec<Node<'tree>>> {
1165 let mut cursor = node.walk();
1166 let mut children = Vec::new();
1167 for child in node.named_children(&mut cursor) {
1168 if !scope_step() {
1169 return None;
1170 }
1171 children.push(child);
1172 }
1173 Some(children)
1174}
1175
1176fn children_by_field_name_bounded<'tree>(
1177 node: Node<'tree>,
1178 field: &str,
1179 scope_step: &mut impl FnMut() -> bool,
1180) -> Option<Vec<Node<'tree>>> {
1181 let mut cursor = node.walk();
1182 let mut children = Vec::new();
1183 for child in node.children_by_field_name(field, &mut cursor) {
1184 if !scope_step() {
1185 return None;
1186 }
1187 children.push(child);
1188 }
1189 Some(children)
1190}
1191
1192fn is_comprehension(kind: &str) -> bool {
1193 matches!(
1194 kind,
1195 "list_comprehension"
1196 | "set_comprehension"
1197 | "dictionary_comprehension"
1198 | "generator_expression"
1199 )
1200}
1201
1202fn is_pattern_literal(kind: &str) -> bool {
1203 matches!(
1204 kind,
1205 "string"
1206 | "concatenated_string"
1207 | "integer"
1208 | "float"
1209 | "complex_pattern"
1210 | "true"
1211 | "false"
1212 | "none"
1213 )
1214}
1215
1216fn node_text<'a>(node: Node<'_>, source: &'a str) -> &'a str {
1217 brokk_bifrost_core::analyzer::common::node_source_text_trimmed(node, source)
1218}