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_module_or_class_scope_binds_name_bounded(
807 scope: Node<'_>,
808 target_name: &str,
809 source: &str,
810 mut scope_step: impl FnMut() -> bool,
811) -> Option<bool> {
812 assert!(matches!(scope.kind(), "module" | "class_definition"));
813 let body = scope.child_by_field_name("body").unwrap_or(scope);
814 let mut stack = vec![body];
815 while let Some(node) = stack.pop() {
816 if !scope_step() {
817 return None;
818 }
819 if node.kind() == "wildcard_import" {
820 return Some(true);
821 }
822 if python_direct_scope_bindings_bounded(node, source, &mut scope_step)?
823 .into_iter()
824 .any(|binding| node_text(binding.declaration, source) == target_name)
825 {
826 return Some(true);
827 }
828 let excluded_body = matches!(
829 node.kind(),
830 "class_definition" | "function_definition" | "lambda"
831 )
832 .then(|| node.child_by_field_name("body").map(|body| body.id()))
833 .flatten();
834 let mut cursor = node.walk();
835 stack.extend(
836 node.named_children(&mut cursor)
837 .filter(|child| Some(child.id()) != excluded_body),
838 );
839 }
840 Some(false)
841}
842
843pub fn python_unambiguous_module_class_binding_bounded(
844 root: Node<'_>,
845 source: &str,
846 target_name: &str,
847 mut scope_step: impl FnMut() -> bool,
848) -> Option<bool> {
849 let mut matched = None;
850 let mut stack = vec![root];
851 while let Some(node) = stack.pop() {
852 if !scope_step() {
853 return None;
854 }
855 if node.kind() == "wildcard_import" && matched.is_some() {
858 return Some(false);
859 }
860 for binding in python_direct_scope_bindings_bounded(node, source, &mut scope_step)? {
861 if node_text(binding.declaration, source) != target_name {
862 continue;
863 }
864 if matched.is_some() {
865 return Some(false);
866 }
867 matched = Some(binding.kind);
868 if binding.kind == PythonDirectScopeBindingKind::Other {
869 return Some(false);
870 }
871 }
872
873 let body = matches!(
874 node.kind(),
875 "function_definition" | "class_definition" | "lambda"
876 )
877 .then(|| node.child_by_field_name("body").map(|child| child.id()))
878 .flatten();
879 let name = matches!(node.kind(), "function_definition" | "class_definition")
880 .then(|| node.child_by_field_name("name").map(|child| child.id()))
881 .flatten();
882 for child in named_children_bounded(node, &mut scope_step)?
883 .into_iter()
884 .filter(|child| Some(child.id()) != body && Some(child.id()) != name)
885 .rev()
886 {
887 stack.push(child);
888 }
889 }
890 Some(matched == Some(PythonDirectScopeBindingKind::ClassDeclaration))
891}
892
893fn is_direct_module_definition_bounded(
894 node: Node<'_>,
895 scope_step: &mut impl FnMut() -> bool,
896) -> Option<bool> {
897 if !scope_step() {
898 return None;
899 }
900 let Some(mut parent) = node.parent() else {
901 return Some(false);
902 };
903 if parent.kind() == "decorated_definition" {
904 if !scope_step() {
905 return None;
906 }
907 let Some(grandparent) = parent.parent() else {
908 return Some(false);
909 };
910 parent = grandparent;
911 }
912 Some(parent.kind() == "module")
913}
914
915fn collect_direct_identifier_names(
916 node: Node<'_>,
917 source: &str,
918 scope_step: &mut impl FnMut() -> bool,
919 mut record: impl FnMut(&str),
920) -> Option<()> {
921 for child in named_children_bounded(node, scope_step)? {
922 if child.kind() == "identifier" {
923 let name = node_text(child, source);
924 if !name.is_empty() {
925 record(name);
926 }
927 }
928 }
929 Some(())
930}
931
932fn collect_import_bindings<'tree>(
933 statement: Node<'tree>,
934 scope_step: &mut impl FnMut() -> bool,
935 mut record: impl FnMut(Node<'tree>),
936) -> Option<()> {
937 let mut cursor = statement.walk();
938 let mut imports = Vec::new();
939 for imported in statement.children_by_field_name("name", &mut cursor) {
940 if !scope_step() {
941 return None;
942 }
943 imports.push(imported);
944 }
945 for imported in imports {
946 if let Some(alias) = imported.child_by_field_name("alias") {
947 record(alias);
948 continue;
949 }
950 let name = imported.child_by_field_name("name").unwrap_or(imported);
951 let binding = if statement.kind() == "import_statement" {
952 first_identifier_bounded(name, scope_step)?
953 } else {
954 last_identifier_bounded(name, scope_step)?
955 };
956 if let Some(binding) = binding {
957 record(binding);
958 }
959 }
960 Some(())
961}
962
963fn first_identifier_bounded<'tree>(
964 root: Node<'tree>,
965 scope_step: &mut impl FnMut() -> bool,
966) -> Option<Option<Node<'tree>>> {
967 let mut stack = vec![root];
968 while let Some(node) = stack.pop() {
969 if !scope_step() {
970 return None;
971 }
972 if node.kind() == "identifier" {
973 return Some(Some(node));
974 }
975 let children = named_children_bounded(node, scope_step)?;
976 stack.extend(children.into_iter().rev());
977 }
978 Some(None)
979}
980
981fn last_identifier_bounded<'tree>(
982 root: Node<'tree>,
983 scope_step: &mut impl FnMut() -> bool,
984) -> Option<Option<Node<'tree>>> {
985 let mut result = None;
986 let mut stack = vec![root];
987 while let Some(node) = stack.pop() {
988 if !scope_step() {
989 return None;
990 }
991 if node.kind() == "identifier" {
992 result = Some(node);
993 continue;
994 }
995 let children = named_children_bounded(node, scope_step)?;
996 stack.extend(children.into_iter().rev());
997 }
998 Some(result)
999}
1000
1001fn collect_binding_targets<'tree>(
1002 target: Node<'tree>,
1003 source: &str,
1004 scope_step: &mut impl FnMut() -> bool,
1005 mut record: impl FnMut(&str, Node<'tree>),
1006) -> Option<()> {
1007 let mut stack = vec![target];
1008 while let Some(node) = stack.pop() {
1009 if !scope_step() {
1010 return None;
1011 }
1012 match node.kind() {
1013 "attribute" | "subscript" => continue,
1016 "identifier" | "keyword_identifier" => {
1017 let name = node_text(node, source);
1018 if !name.is_empty() {
1019 record(name, node);
1020 }
1021 continue;
1022 }
1023 _ => {}
1024 }
1025 let children = named_children_bounded(node, scope_step)?;
1026 if node.kind() == "as_pattern_target" && children.is_empty() {
1027 let name = node_text(node, source);
1028 if !name.is_empty() {
1029 record(name, node);
1030 }
1031 continue;
1032 }
1033 stack.extend(children.into_iter().rev());
1034 }
1035 Some(())
1036}
1037
1038fn collect_match_pattern_bindings<'tree>(
1039 pattern: Node<'tree>,
1040 source: &str,
1041 scope_step: &mut impl FnMut() -> bool,
1042 mut record: impl FnMut(&str, Node<'tree>),
1043) -> Option<()> {
1044 let mut stack = vec![pattern];
1045 while let Some(node) = stack.pop() {
1046 if !scope_step() {
1047 return None;
1048 }
1049 match node.kind() {
1050 "dotted_name" => {
1051 let identifiers = named_children_bounded(node, scope_step)?
1052 .into_iter()
1053 .filter(|child| child.kind() == "identifier")
1054 .collect::<Vec<_>>();
1055 if let [binding] = identifiers.as_slice() {
1056 let name = node_text(*binding, source);
1057 if !name.is_empty() {
1058 record(name, *binding);
1059 }
1060 }
1061 continue;
1062 }
1063 "splat_pattern" => {
1064 for child in named_children_bounded(node, scope_step)? {
1065 if child.kind() == "identifier" {
1066 let name = node_text(child, source);
1067 if !name.is_empty() {
1068 record(name, child);
1069 }
1070 }
1071 }
1072 continue;
1073 }
1074 "class_pattern" => {
1075 let mut children = named_children_bounded(node, scope_step)?;
1076 if children
1077 .first()
1078 .is_some_and(|child| child.kind() == "dotted_name")
1079 {
1080 children.remove(0);
1081 }
1082 stack.extend(children.into_iter().rev());
1083 continue;
1084 }
1085 "keyword_pattern" => {
1086 let mut children = named_children_bounded(node, scope_step)?;
1087 if children
1088 .first()
1089 .is_some_and(|child| child.kind() == "identifier")
1090 {
1091 children.remove(0);
1092 }
1093 stack.extend(children.into_iter().rev());
1094 continue;
1095 }
1096 "dict_pattern" => {
1097 let key_ids = children_by_field_name_bounded(node, "key", scope_step)?
1098 .into_iter()
1099 .map(|key| key.id())
1100 .collect::<HashSet<_>>();
1101 let children = named_children_bounded(node, scope_step)?;
1102 stack.extend(
1103 children
1104 .into_iter()
1105 .filter(|child| !key_ids.contains(&child.id()))
1106 .rev(),
1107 );
1108 continue;
1109 }
1110 "as_pattern" => {
1111 if let Some(alias) = node.child_by_field_name("alias") {
1112 collect_binding_targets(alias, source, scope_step, &mut record)?;
1113 let children = named_children_bounded(node, scope_step)?;
1114 stack.extend(
1115 children
1116 .into_iter()
1117 .filter(|child| child.id() != alias.id())
1118 .rev(),
1119 );
1120 continue;
1121 }
1122 let mut children = named_children_bounded(node, scope_step)?;
1123 if let Some(alias) = children
1124 .last()
1125 .copied()
1126 .filter(|child| child.kind() == "identifier")
1127 {
1128 let name = node_text(alias, source);
1129 if !name.is_empty() {
1130 record(name, alias);
1131 }
1132 children.pop();
1133 }
1134 stack.extend(children.into_iter().rev());
1135 continue;
1136 }
1137 "identifier" => {
1138 continue;
1141 }
1142 kind if is_pattern_literal(kind) => continue,
1143 _ => {}
1144 }
1145 let children = named_children_bounded(node, scope_step)?;
1146 stack.extend(children.into_iter().rev());
1147 }
1148 Some(())
1149}
1150
1151fn push_named_children<'tree>(
1152 stack: &mut Vec<ScanFrame<'tree>>,
1153 node: Node<'tree>,
1154 in_comprehension: bool,
1155 scope_step: &mut impl FnMut() -> bool,
1156) -> Option<()> {
1157 for child in named_children_bounded(node, scope_step)?.into_iter().rev() {
1158 stack.push(ScanFrame {
1159 node: child,
1160 in_comprehension,
1161 });
1162 }
1163 Some(())
1164}
1165
1166fn push_named_children_except<'tree>(
1167 stack: &mut Vec<ScanFrame<'tree>>,
1168 node: Node<'tree>,
1169 excluded: Node<'tree>,
1170 in_comprehension: bool,
1171 scope_step: &mut impl FnMut() -> bool,
1172) -> Option<()> {
1173 for child in named_children_bounded(node, scope_step)?
1174 .into_iter()
1175 .filter(|child| child.id() != excluded.id())
1176 .rev()
1177 {
1178 stack.push(ScanFrame {
1179 node: child,
1180 in_comprehension,
1181 });
1182 }
1183 Some(())
1184}
1185
1186fn push_nested_scope_header_children<'tree>(
1187 stack: &mut Vec<ScanFrame<'tree>>,
1188 node: Node<'tree>,
1189 in_comprehension: bool,
1190 scope_step: &mut impl FnMut() -> bool,
1191) -> Option<()> {
1192 let body = node.child_by_field_name("body").map(|child| child.id());
1193 let name = node.child_by_field_name("name").map(|child| child.id());
1194 for child in named_children_bounded(node, scope_step)?
1195 .into_iter()
1196 .filter(|child| Some(child.id()) != body && Some(child.id()) != name)
1197 .rev()
1198 {
1199 stack.push(ScanFrame {
1200 node: child,
1201 in_comprehension,
1202 });
1203 }
1204 Some(())
1205}
1206
1207fn named_children_bounded<'tree>(
1208 node: Node<'tree>,
1209 scope_step: &mut impl FnMut() -> bool,
1210) -> Option<Vec<Node<'tree>>> {
1211 let mut cursor = node.walk();
1212 let mut children = Vec::new();
1213 for child in node.named_children(&mut cursor) {
1214 if !scope_step() {
1215 return None;
1216 }
1217 children.push(child);
1218 }
1219 Some(children)
1220}
1221
1222fn children_by_field_name_bounded<'tree>(
1223 node: Node<'tree>,
1224 field: &str,
1225 scope_step: &mut impl FnMut() -> bool,
1226) -> Option<Vec<Node<'tree>>> {
1227 let mut cursor = node.walk();
1228 let mut children = Vec::new();
1229 for child in node.children_by_field_name(field, &mut cursor) {
1230 if !scope_step() {
1231 return None;
1232 }
1233 children.push(child);
1234 }
1235 Some(children)
1236}
1237
1238fn is_comprehension(kind: &str) -> bool {
1239 matches!(
1240 kind,
1241 "list_comprehension"
1242 | "set_comprehension"
1243 | "dictionary_comprehension"
1244 | "generator_expression"
1245 )
1246}
1247
1248fn is_pattern_literal(kind: &str) -> bool {
1249 matches!(
1250 kind,
1251 "string"
1252 | "concatenated_string"
1253 | "integer"
1254 | "float"
1255 | "complex_pattern"
1256 | "true"
1257 | "false"
1258 | "none"
1259 )
1260}
1261
1262fn node_text<'a>(node: Node<'_>, source: &'a str) -> &'a str {
1263 brokk_bifrost_core::analyzer::common::node_source_text_trimmed(node, source)
1264}