1use brokk_bifrost_core::analyzer::common::node_source_text_trimmed as node_text;
2use brokk_bifrost_core::analyzer::tree_walk::{named_children_iter, push_bounded_nodes};
3use tree_sitter::Node;
4
5use brokk_bifrost_core::hash::{HashMap, HashSet};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum PythonLexicalNameResolution {
9 Unbound,
10 Local,
11 Nonlocal,
12 Global,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum PythonDirectScopeBindingKind {
17 ClassDeclaration,
18 FunctionDeclaration,
19 Import,
20 Other,
21}
22
23pub fn python_comprehension_binds_name_at(name: &str, reference: Node<'_>, source: &str) -> bool {
31 python_comprehension_binds_name_at_bounded(name, reference, source, || true)
32 .expect("unbounded comprehension binding lookup completes")
33}
34
35pub fn python_comprehension_binds_name_at_bounded(
38 name: &str,
39 reference: Node<'_>,
40 source: &str,
41 mut scope_step: impl FnMut() -> bool,
42) -> Option<bool> {
43 if !scope_step() {
44 return None;
45 }
46 let mut current = reference;
47 while let Some(parent) = current.parent() {
48 if !scope_step() {
49 return None;
50 }
51 if is_comprehension(parent.kind()) {
52 let mut cursor = parent.walk();
53 let mut clauses = Vec::new();
54 for child in parent.named_children(&mut cursor) {
55 if !scope_step() {
56 return None;
57 }
58 if child.kind() == "for_in_clause" {
59 clauses.push(child);
60 }
61 }
62 let mut in_outer_iterable = false;
63 if let Some(clause) = clauses.first() {
64 let mut cursor = clause.walk();
65 for right in clause.children_by_field_name("right", &mut cursor) {
66 if !scope_step() {
67 return None;
68 }
69 if right.start_byte() <= reference.start_byte()
70 && reference.end_byte() <= right.end_byte()
71 {
72 in_outer_iterable = true;
73 break;
74 }
75 }
76 }
77 if in_outer_iterable {
78 current = parent;
79 continue;
80 }
81 for clause in clauses {
82 if !scope_step() {
83 return None;
84 }
85 let Some(target) = clause.child_by_field_name("left") else {
86 continue;
87 };
88 let mut bound = false;
89 collect_binding_targets(target, source, &mut scope_step, |candidate, _| {
90 bound |= candidate == name;
91 })?;
92 if bound {
93 return Some(true);
94 }
95 }
96 }
97 if matches!(
98 parent.kind(),
99 "function_definition" | "lambda" | "class_definition" | "module"
100 ) {
101 break;
102 }
103 current = parent;
104 }
105 Some(false)
106}
107
108pub fn python_type_parameter_binds_name_at(name: &str, reference: Node<'_>, source: &str) -> bool {
115 python_type_parameter_binds_name_at_bounded(name, reference, source, || true)
116 .expect("unbounded type parameter lookup completes")
117}
118
119pub fn python_type_parameter_binds_name_at_bounded(
122 name: &str,
123 reference: Node<'_>,
124 source: &str,
125 mut scope_step: impl FnMut() -> bool,
126) -> Option<bool> {
127 let mut current = reference;
128 let mut enclosing_functions = Vec::new();
129 while let Some(parent) = current.parent() {
130 if !scope_step() {
131 return None;
132 }
133 if parent.kind() == "function_definition" {
134 enclosing_functions.push(parent);
135 }
136 if matches!(
137 parent.kind(),
138 "class_definition" | "function_definition" | "type_alias_statement"
139 ) && let Some(parameters) = parent.child_by_field_name("type_parameters")
140 && !(parameters.start_byte() <= reference.start_byte()
141 && reference.end_byte() <= parameters.end_byte())
142 {
143 let mut cursor = parameters.walk();
144 for parameter in parameters.named_children(&mut cursor) {
145 if !scope_step() {
146 return None;
147 }
148 let binder = first_identifier_bounded(parameter, &mut scope_step)?;
149 if binder.is_some_and(|binder| node_text(binder, source) == name) {
150 return Some(
152 python_name_resolution_at_in_functions_bounded(
153 name,
154 reference,
155 source,
156 enclosing_functions,
157 &mut scope_step,
158 )? != PythonLexicalNameResolution::Global,
159 );
160 }
161 }
162 }
163 current = parent;
164 }
165 Some(false)
166}
167
168pub fn python_name_resolution_at(
172 name: &str,
173 reference: Node<'_>,
174 source: &str,
175) -> PythonLexicalNameResolution {
176 let mut functions = Vec::new();
177 let mut current = reference;
178 while let Some(parent) = current.parent() {
179 if parent.kind() == "function_definition" {
180 functions.push(parent);
181 }
182 current = parent;
183 }
184 python_name_resolution_at_in_functions(name, reference, source, functions)
185}
186
187fn python_name_resolution_at_in_functions(
188 name: &str,
189 reference: Node<'_>,
190 source: &str,
191 functions: Vec<Node<'_>>,
192) -> PythonLexicalNameResolution {
193 python_name_resolution_at_in_functions_bounded(name, reference, source, functions, || true)
194 .expect("unbounded function binding lookup completes")
195}
196
197fn python_name_resolution_at_in_functions_bounded(
198 name: &str,
199 reference: Node<'_>,
200 source: &str,
201 functions: Vec<Node<'_>>,
202 mut scope_step: impl FnMut() -> bool,
203) -> Option<PythonLexicalNameResolution> {
204 for function in functions {
205 if !scope_step() {
206 return None;
207 }
208 let mut parameter_names = Vec::new();
209 if let Some(parameters) = function.child_by_field_name("parameters") {
210 let mut cursor = parameters.walk();
211 for parameter in parameters.named_children(&mut cursor) {
212 if !scope_step() {
213 return None;
214 }
215 if let Some(name) = crate::syntax::python_parameter_name_bounded(
216 parameter,
217 source,
218 &mut scope_step,
219 )? {
220 parameter_names.push(name);
221 }
222 }
223 }
224 let inventory = PythonLexicalScopeInventory::collect_bounded(
225 function,
226 source,
227 parameter_names,
228 &mut scope_step,
229 )?;
230 let resolution = inventory.name_resolution_at_bounded(name, reference, &mut scope_step)?;
231 match resolution {
232 PythonLexicalNameResolution::Global
233 | PythonLexicalNameResolution::Local
234 | PythonLexicalNameResolution::Nonlocal => return Some(resolution),
235 PythonLexicalNameResolution::Unbound => {}
236 }
237 }
238 Some(PythonLexicalNameResolution::Unbound)
239}
240
241pub fn python_is_type_parameter_binder(node: Node<'_>) -> bool {
244 if node.kind() != "identifier" {
245 return false;
246 }
247 let mut current = node;
248 while let Some(parent) = current.parent() {
249 if matches!(
250 parent.kind(),
251 "class_definition" | "function_definition" | "type_alias_statement"
252 ) && let Some(parameters) = parent.child_by_field_name("type_parameters")
253 && parameters.start_byte() <= node.start_byte()
254 && node.end_byte() <= parameters.end_byte()
255 {
256 let mut cursor = parameters.walk();
257 return parameters.named_children(&mut cursor).any(|parameter| {
258 let binder = python_type_parameter_binder_node(parameter);
259 binder.is_some_and(|binder| binder.id() == node.id())
260 });
261 }
262 if matches!(
263 parent.kind(),
264 "module" | "class_definition" | "function_definition" | "type_alias_statement"
265 ) {
266 return false;
267 }
268 current = parent;
269 }
270 false
271}
272
273fn python_type_parameter_binder_node(parameter: Node<'_>) -> Option<Node<'_>> {
274 if parameter.kind() == "identifier" {
275 Some(parameter)
276 } else {
277 first_identifier_bounded(parameter, &mut || true).flatten()
278 }
279}
280
281#[derive(Clone, Copy, Debug)]
282pub struct PythonDirectScopeBinding<'tree> {
283 pub declaration: Node<'tree>,
284 pub kind: PythonDirectScopeBindingKind,
285}
286
287#[derive(Clone, Debug)]
288struct PythonLocalBinding<'tree> {
289 name: Box<str>,
290 declaration: Node<'tree>,
291}
292
293#[derive(Clone, Copy, Debug, PartialEq, Eq)]
294enum PythonLocalBindingKind {
295 FunctionOnly,
296 ClassOnly,
297 ImportOnly,
298 Other,
299}
300
301#[derive(Clone, Debug)]
302struct PythonComprehensionBinding {
303 name: Box<str>,
304 start_byte: usize,
305 end_byte: usize,
306 enclosing_iterable_ranges: Vec<(usize, usize)>,
307}
308
309pub struct PythonLexicalScopeInventory<'tree> {
316 binding_nodes: Vec<Node<'tree>>,
317 locals: Vec<PythonLocalBinding<'tree>>,
318 bindings: PythonLexicalScopeBindings,
319}
320
321pub struct PythonLexicalScopeBindings {
324 parameters: HashSet<Box<str>>,
325 local_names: HashMap<Box<str>, PythonLocalBindingKind>,
326 binding_writes: HashSet<Box<str>>,
327 global_binding_events: HashSet<Box<str>>,
329 globals: HashSet<Box<str>>,
330 nonlocals: HashSet<Box<str>>,
331 comprehensions: Vec<PythonComprehensionBinding>,
332}
333
334impl PythonLexicalScopeBindings {
335 pub fn name_resolution_at(
336 &self,
337 name: &str,
338 reference: Node<'_>,
339 ) -> PythonLexicalNameResolution {
340 self.name_resolution_at_bounded(name, reference, || true)
341 .expect("unbounded lexical name lookup completes")
342 }
343
344 pub fn name_resolution_at_bounded(
347 &self,
348 name: &str,
349 reference: Node<'_>,
350 mut scope_step: impl FnMut() -> bool,
351 ) -> Option<PythonLexicalNameResolution> {
352 if !scope_step() {
353 return None;
354 }
355 if self.comprehension_binds_name_at_bounded(name, reference, &mut scope_step)? {
356 return Some(PythonLexicalNameResolution::Local);
357 }
358 Some(if self.nonlocals.contains(name) {
359 PythonLexicalNameResolution::Nonlocal
360 } else if self.globals.contains(name) {
361 PythonLexicalNameResolution::Global
362 } else if self.parameters.contains(name) || self.local_names.contains_key(name) {
363 PythonLexicalNameResolution::Local
364 } else {
365 PythonLexicalNameResolution::Unbound
366 })
367 }
368
369 fn comprehension_binds_name_at_bounded(
370 &self,
371 name: &str,
372 reference: Node<'_>,
373 mut scope_step: impl FnMut() -> bool,
374 ) -> Option<bool> {
375 let reference_byte = reference.start_byte();
376 for binding in &self.comprehensions {
377 if !scope_step() {
378 return None;
379 }
380 if binding.name.as_ref() == name
381 && binding.start_byte <= reference_byte
382 && reference_byte < binding.end_byte
383 {
384 let mut in_outer_iterable = false;
385 for (start, end) in &binding.enclosing_iterable_ranges {
386 if !scope_step() {
387 return None;
388 }
389 in_outer_iterable |= *start <= reference_byte && reference_byte < *end;
390 }
391 if !in_outer_iterable {
392 return Some(true);
393 }
394 }
395 }
396 Some(false)
397 }
398
399 pub fn has_global_binding_event(&self, name: &str) -> bool {
402 self.global_binding_events.contains(name)
403 }
404
405 pub fn global_binding_events(&self) -> impl Iterator<Item = &str> {
408 self.global_binding_events.iter().map(|name| name.as_ref())
409 }
410
411 pub fn resolves_to_local_function_bounded(
412 &self,
413 name: &str,
414 reference: Node<'_>,
415 mut scope_step: impl FnMut() -> bool,
416 ) -> Option<bool> {
417 if !scope_step() {
418 return None;
419 }
420 Some(
421 !self.parameters.contains(name)
422 && !self.comprehension_binds_name_at_bounded(name, reference, &mut scope_step)?
423 && self.local_names.get(name) == Some(&PythonLocalBindingKind::FunctionOnly),
424 )
425 }
426
427 pub fn has_runtime_callable_binding_at(&self, name: &str, reference: Node<'_>) -> bool {
430 self.has_runtime_callable_binding_at_bounded(name, reference, || true)
431 .expect("unbounded runtime binding lookup completes")
432 }
433
434 pub fn has_runtime_callable_binding_at_bounded(
435 &self,
436 name: &str,
437 reference: Node<'_>,
438 mut scope_step: impl FnMut() -> bool,
439 ) -> Option<bool> {
440 if !scope_step() {
441 return None;
442 }
443 Some(
444 self.parameters.contains(name)
445 || self.binding_writes.contains(name)
446 || self.comprehension_binds_name_at_bounded(name, reference, &mut scope_step)?,
447 )
448 }
449}
450
451pub struct PythonClassReferenceScope<'tree> {
456 scope: Node<'tree>,
457 pending_name: Option<Node<'tree>>,
458}
459
460impl<'tree> PythonClassReferenceScope<'tree> {
461 pub fn new_bounded(
462 scope: Node<'tree>,
463 reference: Node<'tree>,
464 mut scope_step: impl FnMut() -> bool,
465 ) -> Option<Self> {
466 assert_eq!(scope.kind(), "class_definition");
467 let mut current = reference;
468 let mut pending_name = None;
469 while current != scope {
470 if !scope_step() {
471 return None;
472 }
473 if current.kind() == "class_definition"
474 && let Some(superclasses) = current.child_by_field_name("superclasses")
475 {
476 for expression in named_children_iter(superclasses) {
477 if !scope_step() {
478 return None;
479 }
480 if expression.start_byte() <= reference.start_byte()
481 && reference.end_byte() <= expression.end_byte()
482 && let Some(path) = crate::syntax::python_static_value_path_bounded(
483 expression,
484 &mut scope_step,
485 )?
486 && (path.first().copied() == Some(reference) || expression == reference)
487 {
488 pending_name = Some(current.child_by_field_name("name")?);
489 }
490 }
491 }
492 current = current.parent()?;
493 }
494 Some(Self {
495 scope,
496 pending_name,
497 })
498 }
499
500 fn includes_binding(&self, declaration: Node<'tree>) -> bool {
501 Some(declaration) != self.pending_name
502 }
503
504 pub fn collect_bounded(
505 &self,
506 source: &str,
507 scope_step: impl FnMut() -> bool,
508 ) -> Option<PythonLexicalScopeInventory<'tree>> {
509 PythonLexicalScopeInventory::collect_with_declaration_filter_bounded(
510 self.scope,
511 source,
512 std::iter::empty::<String>(),
513 |declaration| self.includes_binding(declaration),
514 scope_step,
515 )
516 }
517
518 pub fn binds_name_bounded(
519 &self,
520 name: &str,
521 source: &str,
522 scope_step: impl FnMut() -> bool,
523 ) -> Option<bool> {
524 python_scope_binds_name_with_filter_bounded(
525 self.scope,
526 name,
527 source,
528 |declaration| self.includes_binding(declaration),
529 scope_step,
530 )
531 }
532}
533
534impl<'tree> PythonLexicalScopeInventory<'tree> {
535 pub fn collect_bounded(
542 callable: Node<'tree>,
543 source: &str,
544 parameter_names: impl IntoIterator<Item = String>,
545 scope_step: impl FnMut() -> bool,
546 ) -> Option<Self> {
547 Self::collect_with_declaration_filter_bounded(
548 callable,
549 source,
550 parameter_names,
551 |_| true,
552 scope_step,
553 )
554 }
555
556 fn collect_with_declaration_filter_bounded(
557 callable: Node<'tree>,
558 source: &str,
559 parameter_names: impl IntoIterator<Item = String>,
560 include_declaration: impl Fn(Node<'tree>) -> bool,
561 mut scope_step: impl FnMut() -> bool,
562 ) -> Option<Self> {
563 let mut inventory = Self {
564 binding_nodes: Vec::new(),
565 locals: Vec::new(),
566 bindings: PythonLexicalScopeBindings {
567 parameters: parameter_names.into_iter().map(Box::<str>::from).collect(),
568 local_names: HashMap::default(),
569 binding_writes: HashSet::default(),
570 global_binding_events: HashSet::default(),
571 globals: HashSet::default(),
572 nonlocals: HashSet::default(),
573 comprehensions: Vec::new(),
574 },
575 };
576 let body = if callable.kind() == "module" {
577 callable
578 } else if let Some(body) = callable.child_by_field_name("body") {
579 body
580 } else {
581 return Some(inventory);
582 };
583 let mut stack = vec![body];
584
585 while let Some(node) = stack.pop() {
586 if !scope_step() {
587 return None;
588 }
589 let nested_scope = node != body
590 && matches!(
591 node.kind(),
592 "function_definition" | "lambda" | "class_definition"
593 );
594 if nested_scope {
595 if matches!(node.kind(), "function_definition" | "class_definition")
596 && let Some(name) = node.child_by_field_name("name")
597 && include_declaration(name)
598 {
599 inventory.record_local_node(name, source);
600 }
601 push_nested_scope_header_children(&mut stack, node, &mut scope_step)?;
606 continue;
607 }
608
609 match node.kind() {
610 "global_statement" => {
611 if callable.kind() == "module" {
612 continue;
613 }
614 collect_direct_identifier_names(node, source, &mut scope_step, |name| {
615 inventory.bindings.globals.insert(name.into());
616 })?;
617 continue;
618 }
619 "nonlocal_statement" => {
620 collect_direct_identifier_names(node, source, &mut scope_step, |name| {
621 inventory.bindings.nonlocals.insert(name.into());
622 })?;
623 continue;
624 }
625 "import_statement" | "import_from_statement" => {
626 collect_import_bindings(node, &mut scope_step, |binding| {
627 inventory.record_local_kind(
628 node_text(binding, source),
629 binding,
630 PythonLocalBindingKind::ImportOnly,
631 )
632 })?;
633 continue;
634 }
635 "assignment" | "augmented_assignment" => {
636 if let Some(target) = node.child_by_field_name("left") {
637 collect_binding_targets(
638 target,
639 source,
640 &mut scope_step,
641 |name, declaration| {
642 inventory.record_binding_write(name, declaration);
643 },
644 )?;
645 }
646 }
647 "type_alias_statement" => {
648 if let Some(target) = node.child_by_field_name("left")
649 && let Some(binding) = first_identifier_bounded(target, &mut scope_step)?
650 {
651 inventory.record_local_node(binding, source);
652 }
653 }
654 "named_expression" => {
655 if let Some(target) = node.child_by_field_name("name") {
658 collect_binding_targets(
659 target,
660 source,
661 &mut scope_step,
662 |name, declaration| {
663 inventory.record_binding_write(name, declaration);
664 },
665 )?;
666 }
667 }
668 "for_statement" => {
669 if let Some(target) = node.child_by_field_name("left") {
670 collect_binding_targets(
671 target,
672 source,
673 &mut scope_step,
674 |name, declaration| {
675 inventory.record_binding_write(name, declaration);
676 },
677 )?;
678 }
679 }
680 "for_in_clause" => {
681 }
684 "delete_statement" => {
685 for target in named_children_bounded(node, &mut scope_step)? {
686 collect_binding_targets(
687 target,
688 source,
689 &mut scope_step,
690 |name, declaration| {
691 inventory.record_binding_write(name, declaration);
692 },
693 )?;
694 }
695 continue;
696 }
697 "as_pattern" => {
698 if let Some(alias) = node.child_by_field_name("alias") {
699 collect_binding_targets(
700 alias,
701 source,
702 &mut scope_step,
703 |name, declaration| {
704 inventory.record_binding_write(name, declaration);
705 },
706 )?;
707 push_named_children_except(&mut stack, node, alias, &mut scope_step)?;
708 continue;
709 }
710 }
711 "except_clause" => {
712 if let Some(alias) = node.child_by_field_name("alias") {
716 collect_binding_targets(
717 alias,
718 source,
719 &mut scope_step,
720 |name, declaration| {
721 inventory.record_binding_write(name, declaration);
722 },
723 )?;
724 }
725 }
726 "case_clause" => {
727 let children = named_children_bounded(node, &mut scope_step)?;
728 for child in children.iter().copied() {
729 if child.kind() == "case_pattern" {
730 collect_match_pattern_bindings(
731 child,
732 source,
733 &mut scope_step,
734 |name, declaration| {
735 inventory.record_binding_write(name, declaration);
736 },
737 )?;
738 }
739 }
740 for child in children.into_iter().rev() {
741 if child.kind() != "case_pattern" {
742 stack.push(child);
743 }
744 }
745 continue;
746 }
747 kind if is_comprehension(kind) => {
748 let range = (node.start_byte(), node.end_byte());
749 let children = named_children_bounded(node, &mut scope_step)?;
750 let enclosing_iterable_ranges = if let Some(first_clause) = children
751 .iter()
752 .copied()
753 .find(|child| child.kind() == "for_in_clause")
754 {
755 children_by_field_name_bounded(first_clause, "right", &mut scope_step)?
756 .into_iter()
757 .map(|iterable| (iterable.start_byte(), iterable.end_byte()))
758 .collect()
759 } else {
760 Vec::new()
761 };
762 for clause in children
763 .iter()
764 .copied()
765 .filter(|child| child.kind() == "for_in_clause")
766 {
767 if let Some(target) = clause.child_by_field_name("left") {
768 collect_binding_targets(target, source, &mut scope_step, |name, _| {
769 inventory.bindings.comprehensions.push(
770 PythonComprehensionBinding {
771 name: name.into(),
772 start_byte: range.0,
773 end_byte: range.1,
774 enclosing_iterable_ranges: enclosing_iterable_ranges
775 .clone(),
776 },
777 );
778 })?;
779 }
780 }
781 for child in children.into_iter().rev() {
782 stack.push(child);
783 }
784 continue;
785 }
786 _ => {}
787 }
788
789 push_named_children(&mut stack, node, &mut scope_step)?;
790 }
791
792 for name in inventory.bindings.local_names.keys() {
796 if !scope_step() {
797 return None;
798 }
799 if inventory.bindings.globals.contains(name.as_ref()) {
800 inventory
801 .bindings
802 .global_binding_events
803 .insert(name.clone());
804 }
805 }
806
807 inventory.locals.retain(|binding| {
810 !inventory.bindings.globals.contains(binding.name.as_ref())
811 && !inventory.bindings.nonlocals.contains(binding.name.as_ref())
812 });
813 inventory.bindings.local_names.retain(|name, _| {
814 !inventory.bindings.globals.contains(name.as_ref())
815 && !inventory.bindings.nonlocals.contains(name.as_ref())
816 });
817 Some(inventory)
818 }
819
820 pub fn into_bindings(self) -> PythonLexicalScopeBindings {
821 self.bindings
822 }
823
824 pub fn name_resolution_at(
825 &self,
826 name: &str,
827 reference: Node<'_>,
828 ) -> PythonLexicalNameResolution {
829 self.bindings.name_resolution_at(name, reference)
830 }
831
832 pub fn name_resolution_at_bounded(
833 &self,
834 name: &str,
835 reference: Node<'_>,
836 scope_step: impl FnMut() -> bool,
837 ) -> Option<PythonLexicalNameResolution> {
838 self.bindings
839 .name_resolution_at_bounded(name, reference, scope_step)
840 }
841
842 pub fn has_runtime_callable_binding_at(&self, name: &str, reference: Node<'_>) -> bool {
843 self.bindings
844 .has_runtime_callable_binding_at(name, reference)
845 }
846
847 pub fn has_runtime_callable_binding_at_bounded(
848 &self,
849 name: &str,
850 reference: Node<'_>,
851 scope_step: impl FnMut() -> bool,
852 ) -> Option<bool> {
853 self.bindings
854 .has_runtime_callable_binding_at_bounded(name, reference, scope_step)
855 }
856
857 pub fn local_function_declaration(
858 &self,
859 name: &str,
860 reference: Node<'_>,
861 ) -> Option<Node<'tree>> {
862 self.local_function_declaration_bounded(name, reference, || true)
863 }
864
865 pub fn local_function_declaration_bounded(
866 &self,
867 name: &str,
868 reference: Node<'_>,
869 mut scope_step: impl FnMut() -> bool,
870 ) -> Option<Node<'tree>> {
871 if !self
872 .bindings
873 .resolves_to_local_function_bounded(name, reference, &mut scope_step)?
874 {
875 return None;
876 }
877 for binding in &self.locals {
878 if !scope_step() {
879 return None;
880 }
881 if binding.name.as_ref() == name {
882 return binding
883 .declaration
884 .parent()
885 .filter(|declaration| declaration.kind() == "function_definition");
886 }
887 }
888 None
889 }
890
891 pub fn local_class_declaration_bounded(
894 &self,
895 name: &str,
896 reference: Node<'_>,
897 mut scope_step: impl FnMut() -> bool,
898 ) -> Option<Node<'tree>> {
899 if self.name_resolution_at_bounded(name, reference, &mut scope_step)?
900 != PythonLexicalNameResolution::Local
901 || self.bindings.has_runtime_callable_binding_at_bounded(
902 name,
903 reference,
904 &mut scope_step,
905 )?
906 || self.bindings.local_names.get(name) != Some(&PythonLocalBindingKind::ClassOnly)
907 {
908 return None;
909 }
910 for binding in &self.locals {
911 if !scope_step() {
912 return None;
913 }
914 if binding.name.as_ref() == name {
915 return binding
916 .declaration
917 .parent()
918 .filter(|node| node.kind() == "class_definition");
919 }
920 }
921 None
922 }
923
924 pub fn unique_import_declaration_bounded(
928 &self,
929 name: &str,
930 reference: Node<'_>,
931 mut scope_step: impl FnMut() -> bool,
932 ) -> Option<Option<Node<'tree>>> {
933 if self.name_resolution_at_bounded(name, reference, &mut scope_step)?
934 != PythonLexicalNameResolution::Local
935 || self.bindings.has_runtime_callable_binding_at_bounded(
936 name,
937 reference,
938 &mut scope_step,
939 )?
940 || self.bindings.local_names.get(name) != Some(&PythonLocalBindingKind::ImportOnly)
941 {
942 return Some(None);
943 }
944 for binding in &self.locals {
945 if !scope_step() {
946 return None;
947 }
948 if binding.name.as_ref() == name {
949 return Some(Some(binding.declaration));
950 }
951 }
952 unreachable!("ImportOnly is constructed with its declaration token")
953 }
954
955 pub fn binding_nodes(&self) -> impl Iterator<Item = Node<'tree>> + '_ {
958 self.binding_nodes.iter().copied()
959 }
960
961 pub fn global_names(&self) -> impl Iterator<Item = &str> {
962 self.bindings.globals.iter().map(AsRef::as_ref)
963 }
964
965 pub fn nonlocal_names(&self) -> impl Iterator<Item = &str> {
966 self.bindings.nonlocals.iter().map(AsRef::as_ref)
967 }
968
969 pub fn local_bindings(&self) -> impl Iterator<Item = (&str, Node<'tree>)> + '_ {
970 self.locals
971 .iter()
972 .map(|binding| (binding.name.as_ref(), binding.declaration))
973 }
974
975 fn record_local_node(&mut self, node: Node<'tree>, source: &str) {
976 let name = node_text(node, source);
977 self.record_local(name, node);
978 }
979
980 fn record_local(&mut self, name: &str, declaration: Node<'tree>) {
981 if name.is_empty() {
982 return;
983 }
984 let binding_kind = if is_function_declaration_name(declaration) {
985 PythonLocalBindingKind::FunctionOnly
986 } else if declaration.parent().is_some_and(|parent| {
987 parent.kind() == "class_definition"
988 && brokk_bifrost_core::analyzer::tree_walk::is_field_child(
989 parent,
990 declaration,
991 "name",
992 )
993 }) {
994 PythonLocalBindingKind::ClassOnly
995 } else {
996 PythonLocalBindingKind::Other
997 };
998 self.record_local_kind(name, declaration, binding_kind);
999 }
1000
1001 fn record_local_kind(
1002 &mut self,
1003 name: &str,
1004 declaration: Node<'tree>,
1005 binding_kind: PythonLocalBindingKind,
1006 ) {
1007 if name.is_empty() {
1008 return;
1009 }
1010 self.binding_nodes.push(declaration);
1011 match self.bindings.local_names.entry(name.into()) {
1012 std::collections::hash_map::Entry::Vacant(entry) => {
1013 entry.insert(binding_kind);
1014 self.locals.push(PythonLocalBinding {
1015 name: name.into(),
1016 declaration,
1017 });
1018 }
1019 std::collections::hash_map::Entry::Occupied(mut entry) => {
1020 entry.insert(PythonLocalBindingKind::Other);
1021 }
1022 }
1023 }
1024
1025 fn record_binding_write(&mut self, name: &str, declaration: Node<'tree>) {
1026 if !name.is_empty() {
1027 self.bindings.binding_writes.insert(name.into());
1028 }
1029 self.record_local(name, declaration);
1030 }
1031}
1032
1033pub fn python_global_binding_is_untouched_bounded(
1039 name: &str,
1040 reference: Node<'_>,
1041 global_scope: Node<'_>,
1042 source: &str,
1043 mut scope_step: impl FnMut() -> bool,
1044) -> Option<bool> {
1045 assert!(matches!(
1046 global_scope.kind(),
1047 "function_definition" | "lambda" | "class_definition"
1048 ));
1049 let mut current = Some(global_scope);
1050 while let Some(scope) = current {
1051 if !scope_step() {
1052 return None;
1053 }
1054 if matches!(
1055 scope.kind(),
1056 "function_definition" | "lambda" | "class_definition"
1057 ) && scope.child_by_field_name("body").is_some_and(|body| {
1058 body.start_byte() <= reference.start_byte() && reference.end_byte() <= body.end_byte()
1059 }) {
1060 let inventory = if scope.kind() == "class_definition" {
1061 PythonClassReferenceScope::new_bounded(scope, reference, &mut scope_step)?
1062 .collect_bounded(source, &mut scope_step)?
1063 } else {
1064 PythonLexicalScopeInventory::collect_bounded(
1065 scope,
1066 source,
1067 std::iter::empty::<String>(),
1068 &mut scope_step,
1069 )?
1070 };
1071 if inventory.bindings.has_global_binding_event(name) {
1072 return Some(false);
1073 }
1074 }
1075 current = scope.parent();
1076 }
1077 Some(true)
1078}
1079
1080fn is_function_declaration_name(node: Node<'_>) -> bool {
1081 node.parent().is_some_and(|parent| {
1082 parent.kind() == "function_definition"
1083 && brokk_bifrost_core::analyzer::tree_walk::is_field_child(parent, node, "name")
1084 })
1085}
1086
1087pub fn python_binding_targets_bounded<'tree>(
1091 target: Node<'tree>,
1092 source: &str,
1093 mut scope_step: impl FnMut() -> bool,
1094) -> Option<Vec<PythonDirectScopeBinding<'tree>>> {
1095 let mut bindings = Vec::new();
1096 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
1097 bindings.push(PythonDirectScopeBinding {
1098 declaration,
1099 kind: PythonDirectScopeBindingKind::Other,
1100 });
1101 })?;
1102 Some(bindings)
1103}
1104
1105pub fn python_direct_scope_bindings_bounded<'tree>(
1112 node: Node<'tree>,
1113 source: &str,
1114 mut scope_step: impl FnMut() -> bool,
1115) -> Option<Vec<PythonDirectScopeBinding<'tree>>> {
1116 let mut bindings = Vec::new();
1117
1118 match node.kind() {
1119 "function_definition" => {
1120 if let Some(name) = node.child_by_field_name("name") {
1121 bindings.push(PythonDirectScopeBinding {
1122 declaration: name,
1123 kind: if is_direct_module_definition_bounded(node, &mut scope_step)? {
1124 PythonDirectScopeBindingKind::FunctionDeclaration
1125 } else {
1126 PythonDirectScopeBindingKind::Other
1127 },
1128 });
1129 }
1130 }
1131 "class_definition" => {
1132 if let Some(name) = node.child_by_field_name("name") {
1133 bindings.push(PythonDirectScopeBinding {
1134 declaration: name,
1135 kind: if is_direct_module_definition_bounded(node, &mut scope_step)? {
1136 PythonDirectScopeBindingKind::ClassDeclaration
1137 } else {
1138 PythonDirectScopeBindingKind::Other
1139 },
1140 });
1141 }
1142 }
1143 "import_statement" | "import_from_statement" => {
1144 let module_level = is_direct_module_definition_bounded(node, &mut scope_step)?;
1145 collect_import_bindings(node, &mut scope_step, |declaration| {
1146 bindings.push(PythonDirectScopeBinding {
1147 declaration,
1148 kind: if module_level {
1149 PythonDirectScopeBindingKind::Import
1150 } else {
1151 PythonDirectScopeBindingKind::Other
1152 },
1153 });
1154 })?;
1155 }
1156 "assignment" | "annotated_assignment" | "augmented_assignment" => {
1157 if let Some(target) = node.child_by_field_name("left") {
1158 bindings.extend(python_binding_targets_bounded(
1159 target,
1160 source,
1161 &mut scope_step,
1162 )?);
1163 }
1164 }
1165 "type_alias_statement" => {
1166 if let Some(target) = node.child_by_field_name("left")
1167 && let Some(declaration) = first_identifier_bounded(target, &mut scope_step)?
1168 {
1169 bindings.push(PythonDirectScopeBinding {
1170 declaration,
1171 kind: PythonDirectScopeBindingKind::Other,
1172 });
1173 }
1174 }
1175 "named_expression" => {
1176 if let Some(target) = node.child_by_field_name("name") {
1177 bindings.extend(python_binding_targets_bounded(
1178 target,
1179 source,
1180 &mut scope_step,
1181 )?);
1182 }
1183 }
1184 "for_statement" | "async_for_statement" => {
1185 if let Some(target) = node.child_by_field_name("left") {
1186 bindings.extend(python_binding_targets_bounded(
1187 target,
1188 source,
1189 &mut scope_step,
1190 )?);
1191 }
1192 }
1193 "with_statement" => {
1194 let mut cursor = node.walk();
1195 for item in node
1196 .named_children(&mut cursor)
1197 .filter(|item| item.kind() == "with_item")
1198 {
1199 if let Some(alias) = item.child_by_field_name("alias") {
1200 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
1201 bindings.push(PythonDirectScopeBinding {
1202 declaration,
1203 kind: PythonDirectScopeBindingKind::Other,
1204 });
1205 })?;
1206 }
1207 }
1208 }
1209 "delete_statement" => {
1210 for target in named_children_bounded(node, &mut scope_step)? {
1211 bindings.extend(python_binding_targets_bounded(
1212 target,
1213 source,
1214 &mut scope_step,
1215 )?);
1216 }
1217 }
1218 "as_pattern" => {
1219 if let Some(alias) = node.child_by_field_name("alias") {
1220 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
1221 bindings.push(PythonDirectScopeBinding {
1222 declaration,
1223 kind: PythonDirectScopeBindingKind::Other,
1224 });
1225 })?;
1226 }
1227 }
1228 "except_clause" => {
1229 if let Some(alias) = node.child_by_field_name("alias") {
1230 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
1231 bindings.push(PythonDirectScopeBinding {
1232 declaration,
1233 kind: PythonDirectScopeBindingKind::Other,
1234 });
1235 })?;
1236 }
1237 }
1238 "case_clause" => {
1239 for child in named_children_bounded(node, &mut scope_step)? {
1240 if child.kind() == "case_pattern" {
1241 collect_match_pattern_bindings(
1242 child,
1243 source,
1244 &mut scope_step,
1245 |_, declaration| {
1246 bindings.push(PythonDirectScopeBinding {
1247 declaration,
1248 kind: PythonDirectScopeBindingKind::Other,
1249 });
1250 },
1251 )?;
1252 }
1253 }
1254 }
1255 _ => {}
1256 }
1257 Some(bindings)
1258}
1259
1260pub fn python_module_or_class_scope_binds_name_bounded(
1265 scope: Node<'_>,
1266 target_name: &str,
1267 source: &str,
1268 scope_step: impl FnMut() -> bool,
1269) -> Option<bool> {
1270 python_scope_binds_name_with_filter_bounded(scope, target_name, source, |_| true, scope_step)
1271}
1272
1273fn python_scope_binds_name_with_filter_bounded<'tree>(
1274 scope: Node<'tree>,
1275 target_name: &str,
1276 source: &str,
1277 include_binding: impl Fn(Node<'tree>) -> bool,
1278 mut scope_step: impl FnMut() -> bool,
1279) -> Option<bool> {
1280 assert!(matches!(scope.kind(), "module" | "class_definition"));
1281 let body = scope.child_by_field_name("body").unwrap_or(scope);
1282 let mut stack = vec![body];
1283 while let Some(node) = stack.pop() {
1284 if !scope_step() {
1285 return None;
1286 }
1287 if node.kind() == "wildcard_import" {
1288 return Some(true);
1289 }
1290 if python_direct_scope_bindings_bounded(node, source, &mut scope_step)?
1291 .into_iter()
1292 .any(|binding| {
1293 include_binding(binding.declaration)
1294 && node_text(binding.declaration, source) == target_name
1295 })
1296 {
1297 return Some(true);
1298 }
1299 stack.extend(python_executable_scope_children(node));
1300 }
1301 Some(false)
1302}
1303
1304pub fn python_executable_scope_children<'tree>(
1306 node: Node<'tree>,
1307) -> impl Iterator<Item = Node<'tree>> {
1308 let excluded_body = matches!(
1309 node.kind(),
1310 "class_definition" | "function_definition" | "lambda"
1311 )
1312 .then(|| node.child_by_field_name("body").map(|body| body.id()))
1313 .flatten();
1314 named_children_iter(node).filter(move |child| Some(child.id()) != excluded_body)
1315}
1316
1317pub fn python_unambiguous_module_class_binding_bounded(
1318 root: Node<'_>,
1319 source: &str,
1320 target_name: &str,
1321 scope_step: impl FnMut() -> bool,
1322) -> Option<bool> {
1323 Some(
1324 python_unambiguous_module_binding_bounded(root, source, target_name, scope_step)?
1325 == Some(PythonDirectScopeBindingKind::ClassDeclaration),
1326 )
1327}
1328
1329pub fn python_unambiguous_module_binding_bounded(
1336 root: Node<'_>,
1337 source: &str,
1338 target_name: &str,
1339 scope_step: impl FnMut() -> bool,
1340) -> Option<Option<PythonDirectScopeBindingKind>> {
1341 Some(
1342 python_unambiguous_module_binding_node_bounded(root, source, target_name, scope_step)?
1343 .map(|binding| binding.kind),
1344 )
1345}
1346
1347pub fn python_unambiguous_module_binding_node_bounded<'tree>(
1354 root: Node<'tree>,
1355 source: &str,
1356 target_name: &str,
1357 mut scope_step: impl FnMut() -> bool,
1358) -> Option<Option<PythonDirectScopeBinding<'tree>>> {
1359 let mut matched = None;
1360 let mut stack = vec![root];
1361 while let Some(node) = stack.pop() {
1362 if !scope_step() {
1363 return None;
1364 }
1365 if node.kind() == "wildcard_import" && matched.is_some() {
1368 return Some(None);
1369 }
1370 for binding in python_direct_scope_bindings_bounded(node, source, &mut scope_step)? {
1371 if node_text(binding.declaration, source) != target_name {
1372 continue;
1373 }
1374 if matched.is_some() {
1375 return Some(None);
1376 }
1377 matched = Some(binding);
1378 }
1379
1380 let body = matches!(
1381 node.kind(),
1382 "function_definition" | "class_definition" | "lambda"
1383 )
1384 .then(|| node.child_by_field_name("body").map(|child| child.id()))
1385 .flatten();
1386 let name = matches!(node.kind(), "function_definition" | "class_definition")
1387 .then(|| node.child_by_field_name("name").map(|child| child.id()))
1388 .flatten();
1389 for child in named_children_bounded(node, &mut scope_step)?
1390 .into_iter()
1391 .filter(|child| Some(child.id()) != body && Some(child.id()) != name)
1392 .rev()
1393 {
1394 stack.push(child);
1395 }
1396 }
1397 Some(matched)
1398}
1399
1400fn is_direct_module_definition_bounded(
1401 node: Node<'_>,
1402 scope_step: &mut impl FnMut() -> bool,
1403) -> Option<bool> {
1404 if !scope_step() {
1405 return None;
1406 }
1407 let Some(mut parent) = node.parent() else {
1408 return Some(false);
1409 };
1410 if parent.kind() == "decorated_definition" {
1411 if !scope_step() {
1412 return None;
1413 }
1414 let Some(grandparent) = parent.parent() else {
1415 return Some(false);
1416 };
1417 parent = grandparent;
1418 }
1419 Some(parent.kind() == "module")
1420}
1421
1422fn collect_direct_identifier_names(
1423 node: Node<'_>,
1424 source: &str,
1425 scope_step: &mut impl FnMut() -> bool,
1426 mut record: impl FnMut(&str),
1427) -> Option<()> {
1428 for child in named_children_bounded(node, scope_step)? {
1429 if child.kind() == "identifier" {
1430 let name = node_text(child, source);
1431 if !name.is_empty() {
1432 record(name);
1433 }
1434 }
1435 }
1436 Some(())
1437}
1438
1439fn collect_import_bindings<'tree>(
1440 statement: Node<'tree>,
1441 scope_step: &mut impl FnMut() -> bool,
1442 mut record: impl FnMut(Node<'tree>),
1443) -> Option<()> {
1444 let mut cursor = statement.walk();
1445 let mut imports = Vec::new();
1446 for imported in statement.children_by_field_name("name", &mut cursor) {
1447 if !scope_step() {
1448 return None;
1449 }
1450 imports.push(imported);
1451 }
1452 for imported in imports {
1453 if let Some(alias) = imported.child_by_field_name("alias") {
1454 record(alias);
1455 continue;
1456 }
1457 let name = imported.child_by_field_name("name").unwrap_or(imported);
1458 let binding = if statement.kind() == "import_statement" {
1459 first_identifier_bounded(name, scope_step)?
1460 } else {
1461 last_identifier_bounded(name, scope_step)?
1462 };
1463 if let Some(binding) = binding {
1464 record(binding);
1465 }
1466 }
1467 Some(())
1468}
1469
1470fn first_identifier_bounded<'tree>(
1471 root: Node<'tree>,
1472 scope_step: &mut impl FnMut() -> bool,
1473) -> Option<Option<Node<'tree>>> {
1474 let mut stack = vec![root];
1475 while let Some(node) = stack.pop() {
1476 if !scope_step() {
1477 return None;
1478 }
1479 if node.kind() == "identifier" {
1480 return Some(Some(node));
1481 }
1482 let children = named_children_bounded(node, scope_step)?;
1483 stack.extend(children.into_iter().rev());
1484 }
1485 Some(None)
1486}
1487
1488fn last_identifier_bounded<'tree>(
1489 root: Node<'tree>,
1490 scope_step: &mut impl FnMut() -> bool,
1491) -> Option<Option<Node<'tree>>> {
1492 let mut result = None;
1493 let mut stack = vec![root];
1494 while let Some(node) = stack.pop() {
1495 if !scope_step() {
1496 return None;
1497 }
1498 if node.kind() == "identifier" {
1499 result = Some(node);
1500 continue;
1501 }
1502 let children = named_children_bounded(node, scope_step)?;
1503 stack.extend(children.into_iter().rev());
1504 }
1505 Some(result)
1506}
1507
1508pub(crate) fn collect_binding_targets<'tree>(
1509 target: Node<'tree>,
1510 source: &str,
1511 scope_step: &mut impl FnMut() -> bool,
1512 mut record: impl FnMut(&str, Node<'tree>),
1513) -> Option<()> {
1514 let mut stack = vec![target];
1515 while let Some(node) = stack.pop() {
1516 if !scope_step() {
1517 return None;
1518 }
1519 match node.kind() {
1520 "attribute" | "subscript" => continue,
1523 "identifier" | "keyword_identifier" => {
1524 let name = node_text(node, source);
1525 if !name.is_empty() {
1526 record(name, node);
1527 }
1528 continue;
1529 }
1530 _ => {}
1531 }
1532 let children = named_children_bounded(node, scope_step)?;
1533 if node.kind() == "as_pattern_target" && children.is_empty() {
1534 let name = node_text(node, source);
1535 if !name.is_empty() {
1536 record(name, node);
1537 }
1538 continue;
1539 }
1540 stack.extend(children.into_iter().rev());
1541 }
1542 Some(())
1543}
1544
1545fn collect_match_pattern_bindings<'tree>(
1546 pattern: Node<'tree>,
1547 source: &str,
1548 scope_step: &mut impl FnMut() -> bool,
1549 mut record: impl FnMut(&str, Node<'tree>),
1550) -> Option<()> {
1551 let mut stack = vec![pattern];
1552 while let Some(node) = stack.pop() {
1553 if !scope_step() {
1554 return None;
1555 }
1556 match node.kind() {
1557 "dotted_name" => {
1558 let identifiers = named_children_bounded(node, scope_step)?
1559 .into_iter()
1560 .filter(|child| child.kind() == "identifier")
1561 .collect::<Vec<_>>();
1562 if let [binding] = identifiers.as_slice() {
1563 let name = node_text(*binding, source);
1564 if !name.is_empty() {
1565 record(name, *binding);
1566 }
1567 }
1568 continue;
1569 }
1570 "splat_pattern" => {
1571 for child in named_children_bounded(node, scope_step)? {
1572 if child.kind() == "identifier" {
1573 let name = node_text(child, source);
1574 if !name.is_empty() {
1575 record(name, child);
1576 }
1577 }
1578 }
1579 continue;
1580 }
1581 "class_pattern" => {
1582 let mut children = named_children_bounded(node, scope_step)?;
1583 if children
1584 .first()
1585 .is_some_and(|child| child.kind() == "dotted_name")
1586 {
1587 children.remove(0);
1588 }
1589 stack.extend(children.into_iter().rev());
1590 continue;
1591 }
1592 "keyword_pattern" => {
1593 let mut children = named_children_bounded(node, scope_step)?;
1594 if children
1595 .first()
1596 .is_some_and(|child| child.kind() == "identifier")
1597 {
1598 children.remove(0);
1599 }
1600 stack.extend(children.into_iter().rev());
1601 continue;
1602 }
1603 "dict_pattern" => {
1604 let key_ids = children_by_field_name_bounded(node, "key", scope_step)?
1605 .into_iter()
1606 .map(|key| key.id())
1607 .collect::<HashSet<_>>();
1608 let children = named_children_bounded(node, scope_step)?;
1609 stack.extend(
1610 children
1611 .into_iter()
1612 .filter(|child| !key_ids.contains(&child.id()))
1613 .rev(),
1614 );
1615 continue;
1616 }
1617 "as_pattern" => {
1618 if let Some(alias) = node.child_by_field_name("alias") {
1619 collect_binding_targets(alias, source, scope_step, &mut record)?;
1620 let children = named_children_bounded(node, scope_step)?;
1621 stack.extend(
1622 children
1623 .into_iter()
1624 .filter(|child| child.id() != alias.id())
1625 .rev(),
1626 );
1627 continue;
1628 }
1629 let mut children = named_children_bounded(node, scope_step)?;
1630 if let Some(alias) = children
1631 .last()
1632 .copied()
1633 .filter(|child| child.kind() == "identifier")
1634 {
1635 let name = node_text(alias, source);
1636 if !name.is_empty() {
1637 record(name, alias);
1638 }
1639 children.pop();
1640 }
1641 stack.extend(children.into_iter().rev());
1642 continue;
1643 }
1644 "identifier" => {
1645 continue;
1648 }
1649 kind if is_pattern_literal(kind) => continue,
1650 _ => {}
1651 }
1652 let children = named_children_bounded(node, scope_step)?;
1653 stack.extend(children.into_iter().rev());
1654 }
1655 Some(())
1656}
1657
1658fn push_named_children<'tree>(
1659 stack: &mut Vec<Node<'tree>>,
1660 node: Node<'tree>,
1661 scope_step: &mut impl FnMut() -> bool,
1662) -> Option<()> {
1663 let children = named_children_bounded(node, scope_step)?;
1664 push_bounded_children(stack, children.into_iter());
1665 Some(())
1666}
1667
1668fn push_named_children_except<'tree>(
1669 stack: &mut Vec<Node<'tree>>,
1670 node: Node<'tree>,
1671 excluded: Node<'tree>,
1672 scope_step: &mut impl FnMut() -> bool,
1673) -> Option<()> {
1674 let children = named_children_bounded(node, scope_step)?;
1675 push_bounded_children(
1676 stack,
1677 children
1678 .into_iter()
1679 .filter(|child| child.id() != excluded.id()),
1680 );
1681 Some(())
1682}
1683
1684fn push_nested_scope_header_children<'tree>(
1685 stack: &mut Vec<Node<'tree>>,
1686 node: Node<'tree>,
1687 scope_step: &mut impl FnMut() -> bool,
1688) -> Option<()> {
1689 let body = node.child_by_field_name("body").map(|child| child.id());
1690 let name = node.child_by_field_name("name").map(|child| child.id());
1691 let mut cursor = node.walk();
1694 let children = node
1695 .named_children(&mut cursor)
1696 .filter(|child| Some(child.id()) != body);
1697 let mut headers = Vec::new();
1698 push_bounded_nodes(children, &mut headers, scope_step).then_some(())?;
1699 push_bounded_children(
1700 stack,
1701 headers.into_iter().filter(|child| Some(child.id()) != name),
1702 );
1703 Some(())
1704}
1705
1706fn push_bounded_children<'tree>(
1707 stack: &mut Vec<Node<'tree>>,
1708 children: impl DoubleEndedIterator<Item = Node<'tree>>,
1709) {
1710 for child in children.rev() {
1711 stack.push(child);
1712 }
1713}
1714
1715fn named_children_bounded<'tree>(
1716 node: Node<'tree>,
1717 scope_step: &mut impl FnMut() -> bool,
1718) -> Option<Vec<Node<'tree>>> {
1719 let mut cursor = node.walk();
1720 let mut children = Vec::new();
1721 push_bounded_nodes(node.named_children(&mut cursor), &mut children, scope_step)
1722 .then_some(children)
1723}
1724
1725fn children_by_field_name_bounded<'tree>(
1726 node: Node<'tree>,
1727 field: &str,
1728 scope_step: &mut impl FnMut() -> bool,
1729) -> Option<Vec<Node<'tree>>> {
1730 let mut cursor = node.walk();
1731 let mut children = Vec::new();
1732 push_bounded_nodes(
1733 node.children_by_field_name(field, &mut cursor),
1734 &mut children,
1735 scope_step,
1736 )
1737 .then_some(children)
1738}
1739
1740pub(crate) fn is_comprehension(kind: &str) -> bool {
1741 matches!(
1742 kind,
1743 "list_comprehension"
1744 | "set_comprehension"
1745 | "dictionary_comprehension"
1746 | "generator_expression"
1747 )
1748}
1749
1750fn is_pattern_literal(kind: &str) -> bool {
1751 matches!(
1752 kind,
1753 "string"
1754 | "concatenated_string"
1755 | "integer"
1756 | "float"
1757 | "complex_pattern"
1758 | "true"
1759 | "false"
1760 | "none"
1761 )
1762}
1763
1764#[cfg(test)]
1765mod tests {
1766 use brokk_bifrost_test_macros::hardwon;
1767
1768 #[test]
1769 fn only_the_first_comprehension_iterable_uses_the_enclosing_scope() {
1770 let source = "def run(first, second):\n return [second for first in first for second in second]\n";
1771 let mut parser = tree_sitter::Parser::new();
1772 parser
1773 .set_language(&tree_sitter_python::LANGUAGE.into())
1774 .unwrap();
1775 let tree = parser.parse(source, None).unwrap();
1776 let mut stack = vec![tree.root_node()];
1777 let comprehension = loop {
1778 let node = stack.pop().expect("fixture contains a comprehension");
1779 if node.kind() == "list_comprehension" {
1780 break node;
1781 }
1782 let mut cursor = node.walk();
1783 stack.extend(node.named_children(&mut cursor));
1784 };
1785 let mut cursor = comprehension.walk();
1786 let clauses = comprehension
1787 .named_children(&mut cursor)
1788 .filter(|node| node.kind() == "for_in_clause")
1789 .collect::<Vec<_>>();
1790 let outer = clauses[0].child_by_field_name("right").unwrap();
1791 let inner = clauses[1].child_by_field_name("right").unwrap();
1792 assert!(!super::python_comprehension_binds_name_at(
1793 "first", outer, source
1794 ));
1795 assert!(super::python_comprehension_binds_name_at(
1796 "second", inner, source
1797 ));
1798 }
1799
1800 #[test]
1801 #[hardwon(
1802 "#5044+#5511+#5751: bounded lexical proofs bypassed stops, lazy bindings or competing events"
1803 )]
1804 fn bounded_comprehension_lookup_preserves_exhaustion() {
1805 let source = "def run(xs):\n class Base:\n pass\n def helper():\n pass\n class Derived(Base):\n pass\n helper()\n return [target for target in xs]\n";
1806 let tree = crate::declarations::parse_python_tree(source).unwrap();
1807 let function = tree.root_node().named_child(0).unwrap();
1808 let inventory = super::PythonLexicalScopeInventory::collect_bounded(
1809 function,
1810 source,
1811 ["xs".to_owned()],
1812 || true,
1813 )
1814 .unwrap();
1815 let mut stack = vec![function];
1816 let comprehension = loop {
1817 let node = stack.pop().expect("fixture contains a comprehension");
1818 if node.kind() == "list_comprehension" {
1819 break node;
1820 }
1821 stack.extend(brokk_bifrost_core::analyzer::tree_walk::named_children_iter(node));
1822 };
1823 let reference = comprehension.named_child(0).unwrap();
1824 for mut remaining in [0_usize, 1] {
1825 assert_eq!(
1826 inventory.name_resolution_at_bounded("target", reference, || {
1827 let available = remaining > 0;
1828 remaining = remaining.saturating_sub(1);
1829 available
1830 }),
1831 None
1832 );
1833 }
1834 assert_eq!(
1835 inventory.name_resolution_at_bounded("target", reference, || true),
1836 Some(super::PythonLexicalNameResolution::Local)
1837 );
1838 let body = function.child_by_field_name("body").unwrap();
1839 let derived = brokk_bifrost_core::analyzer::tree_walk::named_children_iter(body)
1840 .find(|node| node.child_by_field_name("superclasses").is_some())
1841 .unwrap();
1842 let base_reference = derived
1843 .child_by_field_name("superclasses")
1844 .unwrap()
1845 .named_child(0)
1846 .unwrap();
1847 let call = brokk_bifrost_core::analyzer::tree_walk::named_children_iter(body)
1848 .filter(|node| node.kind() == "expression_statement")
1849 .find_map(|node| node.named_child(0).filter(|node| node.kind() == "call"))
1850 .unwrap();
1851 let helper_reference = call.child_by_field_name("function").unwrap();
1852 let one_step = || {
1855 let mut available = true;
1856 move || std::mem::take(&mut available)
1857 };
1858 assert!(
1859 inventory
1860 .has_runtime_callable_binding_at_bounded("helper", helper_reference, one_step())
1861 .is_none()
1862 );
1863 assert!(
1864 inventory
1865 .local_function_declaration_bounded("helper", helper_reference, one_step())
1866 .is_none()
1867 );
1868 assert!(
1869 inventory
1870 .local_class_declaration_bounded("Base", base_reference, one_step())
1871 .is_none()
1872 );
1873 assert_eq!(
1874 inventory
1875 .local_function_declaration_bounded("helper", helper_reference, || true)
1876 .unwrap()
1877 .kind(),
1878 "function_definition"
1879 );
1880 assert_eq!(
1881 inventory
1882 .local_class_declaration_bounded("Base", base_reference, || true)
1883 .unwrap()
1884 .kind(),
1885 "class_definition"
1886 );
1887 let bindings = inventory.into_bindings();
1888 assert_eq!(
1889 bindings.name_resolution_at_bounded("missing", reference, one_step()),
1890 None,
1891 "stopped lookup must not manufacture Unbound"
1892 );
1893 assert_eq!(
1894 bindings.name_resolution_at_bounded("missing", reference, || true),
1895 Some(super::PythonLexicalNameResolution::Unbound)
1896 );
1897 assert_eq!(
1898 bindings.resolves_to_local_function_bounded("helper", helper_reference, one_step()),
1899 None
1900 );
1901 assert_eq!(
1902 bindings.resolves_to_local_function_bounded("helper", helper_reference, || true),
1903 Some(true)
1904 );
1905 assert_eq!(
1906 super::python_comprehension_binds_name_at_bounded(
1907 "target",
1908 reference,
1909 source,
1910 one_step()
1911 ),
1912 None
1913 );
1914 assert_eq!(
1915 super::python_comprehension_binds_name_at_bounded("target", reference, source, || true),
1916 Some(true)
1917 );
1918 let clause = brokk_bifrost_core::analyzer::tree_walk::named_children_iter(comprehension)
1919 .find(|node| node.kind() == "for_in_clause")
1920 .unwrap();
1921 let targets = super::python_binding_targets_bounded(
1922 clause.child_by_field_name("left").unwrap(),
1923 source,
1924 || true,
1925 )
1926 .unwrap();
1927 assert_eq!(targets.len(), 1);
1928 assert_eq!(
1929 Some(targets[0].declaration),
1930 clause.child_by_field_name("left")
1931 );
1932
1933 use super::PythonDirectScopeBindingKind::{ClassDeclaration, Other};
1937 for (source, expected_kind) in [
1938 ("Alias = Base\ntrailing = None\n", Some(Other)),
1939 ("Alias = Base\nAlias = Other\n", None),
1940 ("Alias = Base\ndel Alias\n", None),
1941 ("Alias = Base\nfrom pkg import Target as Alias\n", None),
1942 ("Alias = Base\nfrom pkg import *\n", None),
1943 ("from pkg import *\nAlias = Base\n", Some(Other)),
1944 (
1945 "Alias = Base\ndef inner():\n Alias = Other\nclass Container:\n Alias = Other\n",
1946 Some(Other),
1947 ),
1948 (
1949 "Alias = Base\ndef inner(value=(Alias := Other)):\n pass\n",
1950 None,
1951 ),
1952 ("class Alias:\n pass\n", Some(ClassDeclaration)),
1953 (
1954 "from pkg import *\nclass Alias:\n pass\n",
1955 Some(ClassDeclaration),
1956 ),
1957 ("class Alias:\n pass\nfrom pkg import *\n", None),
1958 ("Other = Base\n", None),
1959 ] {
1960 let tree = crate::declarations::parse_python_tree(source).unwrap();
1961 let root = tree.root_node();
1962 assert!(!root.has_error(), "{source}");
1963 let mut steps = 0_usize;
1964 let binding = super::python_unambiguous_module_binding_node_bounded(
1965 root,
1966 source,
1967 "Alias",
1968 || {
1969 steps += 1;
1970 true
1971 },
1972 )
1973 .expect("uninterrupted binding proof completes");
1974 assert_eq!(
1975 binding.map(|binding| binding.kind),
1976 expected_kind,
1977 "{source}"
1978 );
1979 if let Some(binding) = binding {
1980 assert_eq!(binding.declaration.kind(), "identifier");
1981 assert_eq!(super::node_text(binding.declaration, source), "Alias");
1982 let parent = binding.declaration.parent().unwrap();
1983 let field = if binding.kind == ClassDeclaration {
1984 "name"
1985 } else {
1986 "left"
1987 };
1988 assert_eq!(parent.child_by_field_name(field), Some(binding.declaration));
1989 let statement = if binding.kind == ClassDeclaration {
1990 parent
1991 } else {
1992 parent.parent().unwrap()
1993 };
1994 assert_eq!(statement.parent(), Some(root), "exact module-owned binder");
1995 }
1996 assert_eq!(
1997 super::python_unambiguous_module_binding_bounded(root, source, "Alias", || true),
1998 Some(expected_kind),
1999 "kind-only consumers retain the same positive requirement: {source}"
2000 );
2001 assert_eq!(
2002 super::python_unambiguous_module_class_binding_bounded(
2003 root,
2004 source,
2005 "Alias",
2006 || true
2007 ),
2008 Some(expected_kind == Some(ClassDeclaration))
2009 );
2010 for mut remaining in 0..steps {
2011 assert!(
2012 super::python_unambiguous_module_binding_node_bounded(
2013 root,
2014 source,
2015 "Alias",
2016 || {
2017 let available = remaining > 0;
2018 remaining = remaining.saturating_sub(1);
2019 available
2020 },
2021 )
2022 .is_none(),
2023 "a stopped proof cannot publish its early binder: {source}"
2024 );
2025 }
2026 }
2027
2028 let source = "import ns_source as ns\nclass Outer:\n class ns[T: ns.Target]((ns.Target)):\n pass\n";
2031 let tree = crate::declarations::parse_python_tree(source).unwrap();
2032 assert!(!tree.root_node().has_error());
2033 let outer = brokk_bifrost_core::analyzer::tree_walk::first_named_child_of_kind(
2034 tree.root_node(),
2035 "class_definition",
2036 )
2037 .unwrap();
2038 let inner = brokk_bifrost_core::analyzer::tree_walk::first_named_child_of_kind(
2039 outer.child_by_field_name("body").unwrap(),
2040 "class_definition",
2041 )
2042 .unwrap();
2043 let base = inner
2044 .child_by_field_name("superclasses")
2045 .unwrap()
2046 .named_child(0)
2047 .unwrap();
2048 let immediate = crate::syntax::python_static_value_path_bounded(base, || true)
2049 .unwrap()
2050 .unwrap()[0];
2051 let bound =
2052 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(
2053 inner.child_by_field_name("type_parameters").unwrap(),
2054 "attribute",
2055 )
2056 .unwrap()
2057 .child_by_field_name("object")
2058 .unwrap();
2059 for (reference, resolution, binds) in [
2060 (
2061 immediate,
2062 super::PythonLexicalNameResolution::Unbound,
2063 false,
2064 ),
2065 (bound, super::PythonLexicalNameResolution::Local, true),
2066 ] {
2067 assert!(
2068 super::PythonClassReferenceScope::new_bounded(outer, reference, || false).is_none()
2069 );
2070 let scope =
2071 super::PythonClassReferenceScope::new_bounded(outer, reference, || true).unwrap();
2072 let inventory = scope.collect_bounded(source, || true).unwrap();
2073 assert_eq!(
2074 inventory.name_resolution_at_bounded("ns", reference, || true),
2075 Some(resolution)
2076 );
2077 assert_eq!(scope.binds_name_bounded("ns", source, || true), Some(binds));
2078 }
2079 }
2080}