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 module_scope = callable.parent().is_none();
579 let body = if module_scope {
580 callable
581 } else if let Some(body) = callable.child_by_field_name("body") {
582 body
583 } else {
584 return Some(inventory);
585 };
586 let mut stack = vec![body];
587
588 while let Some(node) = stack.pop() {
589 if !scope_step() {
590 return None;
591 }
592 let nested_scope = node != body
593 && matches!(
594 node.kind(),
595 "function_definition" | "lambda" | "class_definition"
596 );
597 if nested_scope {
598 if matches!(node.kind(), "function_definition" | "class_definition")
599 && let Some(name) = node.child_by_field_name("name")
600 && include_declaration(name)
601 {
602 inventory.record_local_node(name, source);
603 }
604 push_nested_scope_header_children(&mut stack, node, &mut scope_step)?;
609 continue;
610 }
611
612 match node.kind() {
613 "global_statement" => {
614 if module_scope {
615 continue;
616 }
617 collect_direct_identifier_names(node, source, &mut scope_step, |name| {
618 inventory.bindings.globals.insert(name.into());
619 })?;
620 continue;
621 }
622 "nonlocal_statement" => {
623 collect_direct_identifier_names(node, source, &mut scope_step, |name| {
624 inventory.bindings.nonlocals.insert(name.into());
625 })?;
626 continue;
627 }
628 "import_statement" | "import_from_statement" => {
629 collect_import_bindings(node, &mut scope_step, |binding| {
630 inventory.record_local_kind(
631 node_text(binding, source),
632 binding,
633 PythonLocalBindingKind::ImportOnly,
634 )
635 })?;
636 continue;
637 }
638 "assignment" | "augmented_assignment" => {
639 if let Some(target) = node.child_by_field_name("left") {
640 collect_binding_targets(
641 target,
642 source,
643 &mut scope_step,
644 |name, declaration| {
645 inventory.record_binding_write(name, declaration);
646 },
647 )?;
648 }
649 }
650 "type_alias_statement" => {
651 if let Some(target) = node.child_by_field_name("left")
652 && let Some(binding) = first_identifier_bounded(target, &mut scope_step)?
653 {
654 inventory.record_local_node(binding, source);
655 }
656 }
657 "named_expression" => {
658 if let Some(target) = node.child_by_field_name("name") {
661 collect_binding_targets(
662 target,
663 source,
664 &mut scope_step,
665 |name, declaration| {
666 inventory.record_binding_write(name, declaration);
667 },
668 )?;
669 }
670 }
671 "for_statement" => {
672 if let Some(target) = node.child_by_field_name("left") {
673 collect_binding_targets(
674 target,
675 source,
676 &mut scope_step,
677 |name, declaration| {
678 inventory.record_binding_write(name, declaration);
679 },
680 )?;
681 }
682 }
683 "for_in_clause" => {
684 }
687 "delete_statement" => {
688 for target in named_children_bounded(node, &mut scope_step)? {
689 collect_binding_targets(
690 target,
691 source,
692 &mut scope_step,
693 |name, declaration| {
694 inventory.record_binding_write(name, declaration);
695 },
696 )?;
697 }
698 continue;
699 }
700 "as_pattern" => {
701 if let Some(alias) = node.child_by_field_name("alias") {
702 collect_binding_targets(
703 alias,
704 source,
705 &mut scope_step,
706 |name, declaration| {
707 inventory.record_binding_write(name, declaration);
708 },
709 )?;
710 push_named_children_except(&mut stack, node, alias, &mut scope_step)?;
711 continue;
712 }
713 }
714 "except_clause" => {
715 if let Some(alias) = node.child_by_field_name("alias") {
719 collect_binding_targets(
720 alias,
721 source,
722 &mut scope_step,
723 |name, declaration| {
724 inventory.record_binding_write(name, declaration);
725 },
726 )?;
727 }
728 }
729 "case_clause" => {
730 let children = named_children_bounded(node, &mut scope_step)?;
731 for child in children.iter().copied() {
732 if child.kind() == "case_pattern" {
733 collect_match_pattern_bindings(
734 child,
735 source,
736 &mut scope_step,
737 |name, declaration| {
738 inventory.record_binding_write(name, declaration);
739 },
740 )?;
741 }
742 }
743 for child in children.into_iter().rev() {
744 if child.kind() != "case_pattern" {
745 stack.push(child);
746 }
747 }
748 continue;
749 }
750 kind if is_comprehension(kind) => {
751 let range = (node.start_byte(), node.end_byte());
752 let children = named_children_bounded(node, &mut scope_step)?;
753 let enclosing_iterable_ranges = if let Some(first_clause) = children
754 .iter()
755 .copied()
756 .find(|child| child.kind() == "for_in_clause")
757 {
758 children_by_field_name_bounded(first_clause, "right", &mut scope_step)?
759 .into_iter()
760 .map(|iterable| (iterable.start_byte(), iterable.end_byte()))
761 .collect()
762 } else {
763 Vec::new()
764 };
765 for clause in children
766 .iter()
767 .copied()
768 .filter(|child| child.kind() == "for_in_clause")
769 {
770 if let Some(target) = clause.child_by_field_name("left") {
771 collect_binding_targets(target, source, &mut scope_step, |name, _| {
772 inventory.bindings.comprehensions.push(
773 PythonComprehensionBinding {
774 name: name.into(),
775 start_byte: range.0,
776 end_byte: range.1,
777 enclosing_iterable_ranges: enclosing_iterable_ranges
778 .clone(),
779 },
780 );
781 })?;
782 }
783 }
784 for child in children.into_iter().rev() {
785 stack.push(child);
786 }
787 continue;
788 }
789 _ => {}
790 }
791
792 push_named_children(&mut stack, node, &mut scope_step)?;
793 }
794
795 for name in inventory.bindings.local_names.keys() {
799 if !scope_step() {
800 return None;
801 }
802 if inventory.bindings.globals.contains(name.as_ref()) {
803 inventory
804 .bindings
805 .global_binding_events
806 .insert(name.clone());
807 }
808 }
809
810 inventory.locals.retain(|binding| {
813 !inventory.bindings.globals.contains(binding.name.as_ref())
814 && !inventory.bindings.nonlocals.contains(binding.name.as_ref())
815 });
816 inventory.bindings.local_names.retain(|name, _| {
817 !inventory.bindings.globals.contains(name.as_ref())
818 && !inventory.bindings.nonlocals.contains(name.as_ref())
819 });
820 Some(inventory)
821 }
822
823 pub fn into_bindings(self) -> PythonLexicalScopeBindings {
824 self.bindings
825 }
826
827 pub fn name_resolution_at(
828 &self,
829 name: &str,
830 reference: Node<'_>,
831 ) -> PythonLexicalNameResolution {
832 self.bindings.name_resolution_at(name, reference)
833 }
834
835 pub fn name_resolution_at_bounded(
836 &self,
837 name: &str,
838 reference: Node<'_>,
839 scope_step: impl FnMut() -> bool,
840 ) -> Option<PythonLexicalNameResolution> {
841 self.bindings
842 .name_resolution_at_bounded(name, reference, scope_step)
843 }
844
845 pub fn has_runtime_callable_binding_at(&self, name: &str, reference: Node<'_>) -> bool {
846 self.bindings
847 .has_runtime_callable_binding_at(name, reference)
848 }
849
850 pub fn has_runtime_callable_binding_at_bounded(
851 &self,
852 name: &str,
853 reference: Node<'_>,
854 scope_step: impl FnMut() -> bool,
855 ) -> Option<bool> {
856 self.bindings
857 .has_runtime_callable_binding_at_bounded(name, reference, scope_step)
858 }
859
860 pub fn local_function_declaration(
861 &self,
862 name: &str,
863 reference: Node<'_>,
864 ) -> Option<Node<'tree>> {
865 self.local_function_declaration_bounded(name, reference, || true)
866 }
867
868 pub fn local_function_declaration_bounded(
869 &self,
870 name: &str,
871 reference: Node<'_>,
872 mut scope_step: impl FnMut() -> bool,
873 ) -> Option<Node<'tree>> {
874 if !self
875 .bindings
876 .resolves_to_local_function_bounded(name, reference, &mut scope_step)?
877 {
878 return None;
879 }
880 for binding in &self.locals {
881 if !scope_step() {
882 return None;
883 }
884 if binding.name.as_ref() == name {
885 return binding
886 .declaration
887 .parent()
888 .filter(|declaration| declaration.kind() == "function_definition");
889 }
890 }
891 None
892 }
893
894 pub fn local_class_declaration_bounded(
897 &self,
898 name: &str,
899 reference: Node<'_>,
900 mut scope_step: impl FnMut() -> bool,
901 ) -> Option<Node<'tree>> {
902 if self.name_resolution_at_bounded(name, reference, &mut scope_step)?
903 != PythonLexicalNameResolution::Local
904 || self.bindings.has_runtime_callable_binding_at_bounded(
905 name,
906 reference,
907 &mut scope_step,
908 )?
909 || self.bindings.local_names.get(name) != Some(&PythonLocalBindingKind::ClassOnly)
910 {
911 return None;
912 }
913 for binding in &self.locals {
914 if !scope_step() {
915 return None;
916 }
917 if binding.name.as_ref() == name {
918 return binding
919 .declaration
920 .parent()
921 .filter(|node| node.kind() == "class_definition");
922 }
923 }
924 None
925 }
926
927 pub fn unique_import_declaration_bounded(
931 &self,
932 name: &str,
933 reference: Node<'_>,
934 mut scope_step: impl FnMut() -> bool,
935 ) -> Option<Option<Node<'tree>>> {
936 if self.name_resolution_at_bounded(name, reference, &mut scope_step)?
937 != PythonLexicalNameResolution::Local
938 || self.bindings.has_runtime_callable_binding_at_bounded(
939 name,
940 reference,
941 &mut scope_step,
942 )?
943 || self.bindings.local_names.get(name) != Some(&PythonLocalBindingKind::ImportOnly)
944 {
945 return Some(None);
946 }
947 for binding in &self.locals {
948 if !scope_step() {
949 return None;
950 }
951 if binding.name.as_ref() == name {
952 return Some(Some(binding.declaration));
953 }
954 }
955 unreachable!("ImportOnly is constructed with its declaration token")
956 }
957
958 pub fn binding_nodes(&self) -> impl Iterator<Item = Node<'tree>> + '_ {
961 self.binding_nodes.iter().copied()
962 }
963
964 pub fn global_names(&self) -> impl Iterator<Item = &str> {
965 self.bindings.globals.iter().map(AsRef::as_ref)
966 }
967
968 pub fn nonlocal_names(&self) -> impl Iterator<Item = &str> {
969 self.bindings.nonlocals.iter().map(AsRef::as_ref)
970 }
971
972 pub fn local_bindings(&self) -> impl Iterator<Item = (&str, Node<'tree>)> + '_ {
973 self.locals
974 .iter()
975 .map(|binding| (binding.name.as_ref(), binding.declaration))
976 }
977
978 fn record_local_node(&mut self, node: Node<'tree>, source: &str) {
979 let name = node_text(node, source);
980 self.record_local(name, node);
981 }
982
983 fn record_local(&mut self, name: &str, declaration: Node<'tree>) {
984 if name.is_empty() {
985 return;
986 }
987 let binding_kind = if is_function_declaration_name(declaration) {
988 PythonLocalBindingKind::FunctionOnly
989 } else if declaration.parent().is_some_and(|parent| {
990 parent.kind() == "class_definition"
991 && brokk_bifrost_core::analyzer::tree_walk::is_field_child(
992 parent,
993 declaration,
994 "name",
995 )
996 }) {
997 PythonLocalBindingKind::ClassOnly
998 } else {
999 PythonLocalBindingKind::Other
1000 };
1001 self.record_local_kind(name, declaration, binding_kind);
1002 }
1003
1004 fn record_local_kind(
1005 &mut self,
1006 name: &str,
1007 declaration: Node<'tree>,
1008 binding_kind: PythonLocalBindingKind,
1009 ) {
1010 if name.is_empty() {
1011 return;
1012 }
1013 self.binding_nodes.push(declaration);
1014 match self.bindings.local_names.entry(name.into()) {
1015 std::collections::hash_map::Entry::Vacant(entry) => {
1016 entry.insert(binding_kind);
1017 self.locals.push(PythonLocalBinding {
1018 name: name.into(),
1019 declaration,
1020 });
1021 }
1022 std::collections::hash_map::Entry::Occupied(mut entry) => {
1023 entry.insert(PythonLocalBindingKind::Other);
1024 }
1025 }
1026 }
1027
1028 fn record_binding_write(&mut self, name: &str, declaration: Node<'tree>) {
1029 if !name.is_empty() {
1030 self.bindings.binding_writes.insert(name.into());
1031 }
1032 self.record_local(name, declaration);
1033 }
1034}
1035
1036pub fn python_global_binding_is_untouched_bounded(
1042 name: &str,
1043 reference: Node<'_>,
1044 global_scope: Node<'_>,
1045 source: &str,
1046 mut scope_step: impl FnMut() -> bool,
1047) -> Option<bool> {
1048 assert!(matches!(
1049 global_scope.kind(),
1050 "function_definition" | "lambda" | "class_definition"
1051 ));
1052 let mut current = Some(global_scope);
1053 while let Some(scope) = current {
1054 if !scope_step() {
1055 return None;
1056 }
1057 if matches!(
1058 scope.kind(),
1059 "function_definition" | "lambda" | "class_definition"
1060 ) && scope.child_by_field_name("body").is_some_and(|body| {
1061 body.start_byte() <= reference.start_byte() && reference.end_byte() <= body.end_byte()
1062 }) {
1063 let inventory = if scope.kind() == "class_definition" {
1064 PythonClassReferenceScope::new_bounded(scope, reference, &mut scope_step)?
1065 .collect_bounded(source, &mut scope_step)?
1066 } else {
1067 PythonLexicalScopeInventory::collect_bounded(
1068 scope,
1069 source,
1070 std::iter::empty::<String>(),
1071 &mut scope_step,
1072 )?
1073 };
1074 if inventory.bindings.has_global_binding_event(name) {
1075 return Some(false);
1076 }
1077 }
1078 current = scope.parent();
1079 }
1080 Some(true)
1081}
1082
1083fn is_function_declaration_name(node: Node<'_>) -> bool {
1084 node.parent().is_some_and(|parent| {
1085 parent.kind() == "function_definition"
1086 && brokk_bifrost_core::analyzer::tree_walk::is_field_child(parent, node, "name")
1087 })
1088}
1089
1090pub fn python_binding_targets_bounded<'tree>(
1094 target: Node<'tree>,
1095 source: &str,
1096 mut scope_step: impl FnMut() -> bool,
1097) -> Option<Vec<PythonDirectScopeBinding<'tree>>> {
1098 let mut bindings = Vec::new();
1099 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
1100 bindings.push(PythonDirectScopeBinding {
1101 declaration,
1102 kind: PythonDirectScopeBindingKind::Other,
1103 });
1104 })?;
1105 Some(bindings)
1106}
1107
1108pub fn python_direct_scope_bindings_bounded<'tree>(
1115 node: Node<'tree>,
1116 source: &str,
1117 mut scope_step: impl FnMut() -> bool,
1118) -> Option<Vec<PythonDirectScopeBinding<'tree>>> {
1119 let mut bindings = Vec::new();
1120
1121 match node.kind() {
1122 "function_definition" => {
1123 if let Some(name) = node.child_by_field_name("name") {
1124 bindings.push(PythonDirectScopeBinding {
1125 declaration: name,
1126 kind: if is_direct_module_definition_bounded(node, &mut scope_step)? {
1127 PythonDirectScopeBindingKind::FunctionDeclaration
1128 } else {
1129 PythonDirectScopeBindingKind::Other
1130 },
1131 });
1132 }
1133 }
1134 "class_definition" => {
1135 if let Some(name) = node.child_by_field_name("name") {
1136 bindings.push(PythonDirectScopeBinding {
1137 declaration: name,
1138 kind: if is_direct_module_definition_bounded(node, &mut scope_step)? {
1139 PythonDirectScopeBindingKind::ClassDeclaration
1140 } else {
1141 PythonDirectScopeBindingKind::Other
1142 },
1143 });
1144 }
1145 }
1146 "import_statement" | "import_from_statement" => {
1147 let module_level = is_direct_module_definition_bounded(node, &mut scope_step)?;
1148 collect_import_bindings(node, &mut scope_step, |declaration| {
1149 bindings.push(PythonDirectScopeBinding {
1150 declaration,
1151 kind: if module_level {
1152 PythonDirectScopeBindingKind::Import
1153 } else {
1154 PythonDirectScopeBindingKind::Other
1155 },
1156 });
1157 })?;
1158 }
1159 "assignment" | "annotated_assignment" | "augmented_assignment" => {
1160 if let Some(target) = node.child_by_field_name("left") {
1161 bindings.extend(python_binding_targets_bounded(
1162 target,
1163 source,
1164 &mut scope_step,
1165 )?);
1166 }
1167 }
1168 "type_alias_statement" => {
1169 if let Some(target) = node.child_by_field_name("left")
1170 && let Some(declaration) = first_identifier_bounded(target, &mut scope_step)?
1171 {
1172 bindings.push(PythonDirectScopeBinding {
1173 declaration,
1174 kind: PythonDirectScopeBindingKind::Other,
1175 });
1176 }
1177 }
1178 "named_expression" => {
1179 if let Some(target) = node.child_by_field_name("name") {
1180 bindings.extend(python_binding_targets_bounded(
1181 target,
1182 source,
1183 &mut scope_step,
1184 )?);
1185 }
1186 }
1187 "for_statement" | "async_for_statement" => {
1188 if let Some(target) = node.child_by_field_name("left") {
1189 bindings.extend(python_binding_targets_bounded(
1190 target,
1191 source,
1192 &mut scope_step,
1193 )?);
1194 }
1195 }
1196 "with_statement" => {
1197 let mut cursor = node.walk();
1198 for item in node
1199 .named_children(&mut cursor)
1200 .filter(|item| item.kind() == "with_item")
1201 {
1202 if let Some(alias) = item.child_by_field_name("alias") {
1203 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
1204 bindings.push(PythonDirectScopeBinding {
1205 declaration,
1206 kind: PythonDirectScopeBindingKind::Other,
1207 });
1208 })?;
1209 }
1210 }
1211 }
1212 "delete_statement" => {
1213 for target in named_children_bounded(node, &mut scope_step)? {
1214 bindings.extend(python_binding_targets_bounded(
1215 target,
1216 source,
1217 &mut scope_step,
1218 )?);
1219 }
1220 }
1221 "as_pattern" => {
1222 if let Some(alias) = node.child_by_field_name("alias") {
1223 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
1224 bindings.push(PythonDirectScopeBinding {
1225 declaration,
1226 kind: PythonDirectScopeBindingKind::Other,
1227 });
1228 })?;
1229 }
1230 }
1231 "except_clause" => {
1232 if let Some(alias) = node.child_by_field_name("alias") {
1233 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
1234 bindings.push(PythonDirectScopeBinding {
1235 declaration,
1236 kind: PythonDirectScopeBindingKind::Other,
1237 });
1238 })?;
1239 }
1240 }
1241 "case_clause" => {
1242 for child in named_children_bounded(node, &mut scope_step)? {
1243 if child.kind() == "case_pattern" {
1244 collect_match_pattern_bindings(
1245 child,
1246 source,
1247 &mut scope_step,
1248 |_, declaration| {
1249 bindings.push(PythonDirectScopeBinding {
1250 declaration,
1251 kind: PythonDirectScopeBindingKind::Other,
1252 });
1253 },
1254 )?;
1255 }
1256 }
1257 }
1258 _ => {}
1259 }
1260 Some(bindings)
1261}
1262
1263pub fn python_module_or_class_scope_binds_name_bounded(
1268 scope: Node<'_>,
1269 target_name: &str,
1270 source: &str,
1271 scope_step: impl FnMut() -> bool,
1272) -> Option<bool> {
1273 python_scope_binds_name_with_filter_bounded(scope, target_name, source, |_| true, scope_step)
1274}
1275
1276fn python_scope_binds_name_with_filter_bounded<'tree>(
1277 scope: Node<'tree>,
1278 target_name: &str,
1279 source: &str,
1280 include_binding: impl Fn(Node<'tree>) -> bool,
1281 mut scope_step: impl FnMut() -> bool,
1282) -> Option<bool> {
1283 assert!(matches!(scope.kind(), "module" | "class_definition"));
1284 let body = scope.child_by_field_name("body").unwrap_or(scope);
1285 let mut stack = vec![body];
1286 while let Some(node) = stack.pop() {
1287 if !scope_step() {
1288 return None;
1289 }
1290 if node.kind() == "wildcard_import" {
1291 return Some(true);
1292 }
1293 if python_direct_scope_bindings_bounded(node, source, &mut scope_step)?
1294 .into_iter()
1295 .any(|binding| {
1296 include_binding(binding.declaration)
1297 && node_text(binding.declaration, source) == target_name
1298 })
1299 {
1300 return Some(true);
1301 }
1302 stack.extend(python_executable_scope_children(node));
1303 }
1304 Some(false)
1305}
1306
1307pub fn python_executable_scope_children<'tree>(
1309 node: Node<'tree>,
1310) -> impl Iterator<Item = Node<'tree>> {
1311 let excluded_body = matches!(
1312 node.kind(),
1313 "class_definition" | "function_definition" | "lambda"
1314 )
1315 .then(|| node.child_by_field_name("body").map(|body| body.id()))
1316 .flatten();
1317 named_children_iter(node).filter(move |child| Some(child.id()) != excluded_body)
1318}
1319
1320pub fn python_unambiguous_module_class_binding_bounded(
1321 root: Node<'_>,
1322 source: &str,
1323 target_name: &str,
1324 scope_step: impl FnMut() -> bool,
1325) -> Option<bool> {
1326 Some(
1327 python_unambiguous_module_binding_bounded(root, source, target_name, scope_step)?
1328 == Some(PythonDirectScopeBindingKind::ClassDeclaration),
1329 )
1330}
1331
1332pub fn python_unambiguous_module_binding_bounded(
1339 root: Node<'_>,
1340 source: &str,
1341 target_name: &str,
1342 scope_step: impl FnMut() -> bool,
1343) -> Option<Option<PythonDirectScopeBindingKind>> {
1344 Some(
1345 python_unambiguous_module_binding_node_bounded(root, source, target_name, scope_step)?
1346 .map(|binding| binding.kind),
1347 )
1348}
1349
1350pub fn python_unambiguous_module_binding_node_bounded<'tree>(
1357 root: Node<'tree>,
1358 source: &str,
1359 target_name: &str,
1360 mut scope_step: impl FnMut() -> bool,
1361) -> Option<Option<PythonDirectScopeBinding<'tree>>> {
1362 let mut matched = None;
1363 let mut stack = vec![root];
1364 while let Some(node) = stack.pop() {
1365 if !scope_step() {
1366 return None;
1367 }
1368 if node.kind() == "wildcard_import" && matched.is_some() {
1371 return Some(None);
1372 }
1373 for binding in python_direct_scope_bindings_bounded(node, source, &mut scope_step)? {
1374 if node_text(binding.declaration, source) != target_name {
1375 continue;
1376 }
1377 if matched.is_some() {
1378 return Some(None);
1379 }
1380 matched = Some(binding);
1381 }
1382
1383 let body = matches!(
1384 node.kind(),
1385 "function_definition" | "class_definition" | "lambda"
1386 )
1387 .then(|| node.child_by_field_name("body").map(|child| child.id()))
1388 .flatten();
1389 let name = matches!(node.kind(), "function_definition" | "class_definition")
1390 .then(|| node.child_by_field_name("name").map(|child| child.id()))
1391 .flatten();
1392 for child in named_children_bounded(node, &mut scope_step)?
1393 .into_iter()
1394 .filter(|child| Some(child.id()) != body && Some(child.id()) != name)
1395 .rev()
1396 {
1397 stack.push(child);
1398 }
1399 }
1400 Some(matched)
1401}
1402
1403fn is_direct_module_definition_bounded(
1404 node: Node<'_>,
1405 scope_step: &mut impl FnMut() -> bool,
1406) -> Option<bool> {
1407 if !scope_step() {
1408 return None;
1409 }
1410 let Some(mut parent) = node.parent() else {
1411 return Some(false);
1412 };
1413 if parent.kind() == "decorated_definition" {
1414 if !scope_step() {
1415 return None;
1416 }
1417 let Some(grandparent) = parent.parent() else {
1418 return Some(false);
1419 };
1420 parent = grandparent;
1421 }
1422 Some(parent.kind() == "module")
1423}
1424
1425fn collect_direct_identifier_names(
1426 node: Node<'_>,
1427 source: &str,
1428 scope_step: &mut impl FnMut() -> bool,
1429 mut record: impl FnMut(&str),
1430) -> Option<()> {
1431 for child in named_children_bounded(node, scope_step)? {
1432 if child.kind() == "identifier" {
1433 let name = node_text(child, source);
1434 if !name.is_empty() {
1435 record(name);
1436 }
1437 }
1438 }
1439 Some(())
1440}
1441
1442fn collect_import_bindings<'tree>(
1443 statement: Node<'tree>,
1444 scope_step: &mut impl FnMut() -> bool,
1445 mut record: impl FnMut(Node<'tree>),
1446) -> Option<()> {
1447 let mut cursor = statement.walk();
1448 let mut imports = Vec::new();
1449 for imported in statement.children_by_field_name("name", &mut cursor) {
1450 if !scope_step() {
1451 return None;
1452 }
1453 imports.push(imported);
1454 }
1455 for imported in imports {
1456 if let Some(alias) = imported.child_by_field_name("alias") {
1457 record(alias);
1458 continue;
1459 }
1460 let name = imported.child_by_field_name("name").unwrap_or(imported);
1461 let binding = if statement.kind() == "import_statement" {
1462 first_identifier_bounded(name, scope_step)?
1463 } else {
1464 last_identifier_bounded(name, scope_step)?
1465 };
1466 if let Some(binding) = binding {
1467 record(binding);
1468 }
1469 }
1470 Some(())
1471}
1472
1473fn first_identifier_bounded<'tree>(
1474 root: Node<'tree>,
1475 scope_step: &mut impl FnMut() -> bool,
1476) -> Option<Option<Node<'tree>>> {
1477 let mut stack = vec![root];
1478 while let Some(node) = stack.pop() {
1479 if !scope_step() {
1480 return None;
1481 }
1482 if node.kind() == "identifier" {
1483 return Some(Some(node));
1484 }
1485 let children = named_children_bounded(node, scope_step)?;
1486 stack.extend(children.into_iter().rev());
1487 }
1488 Some(None)
1489}
1490
1491fn last_identifier_bounded<'tree>(
1492 root: Node<'tree>,
1493 scope_step: &mut impl FnMut() -> bool,
1494) -> Option<Option<Node<'tree>>> {
1495 let mut result = None;
1496 let mut stack = vec![root];
1497 while let Some(node) = stack.pop() {
1498 if !scope_step() {
1499 return None;
1500 }
1501 if node.kind() == "identifier" {
1502 result = Some(node);
1503 continue;
1504 }
1505 let children = named_children_bounded(node, scope_step)?;
1506 stack.extend(children.into_iter().rev());
1507 }
1508 Some(result)
1509}
1510
1511pub(crate) fn collect_binding_targets<'tree>(
1512 target: Node<'tree>,
1513 source: &str,
1514 scope_step: &mut impl FnMut() -> bool,
1515 mut record: impl FnMut(&str, Node<'tree>),
1516) -> Option<()> {
1517 let mut stack = vec![target];
1518 while let Some(node) = stack.pop() {
1519 if !scope_step() {
1520 return None;
1521 }
1522 match node.kind() {
1523 "attribute" | "subscript" => continue,
1526 "identifier" | "keyword_identifier" => {
1527 let name = node_text(node, source);
1528 if !name.is_empty() {
1529 record(name, node);
1530 }
1531 continue;
1532 }
1533 _ => {}
1534 }
1535 let children = named_children_bounded(node, scope_step)?;
1536 if node.kind() == "as_pattern_target" && children.is_empty() {
1537 let name = node_text(node, source);
1538 if !name.is_empty() {
1539 record(name, node);
1540 }
1541 continue;
1542 }
1543 stack.extend(children.into_iter().rev());
1544 }
1545 Some(())
1546}
1547
1548fn collect_match_pattern_bindings<'tree>(
1549 pattern: Node<'tree>,
1550 source: &str,
1551 scope_step: &mut impl FnMut() -> bool,
1552 mut record: impl FnMut(&str, Node<'tree>),
1553) -> Option<()> {
1554 let mut stack = vec![pattern];
1555 while let Some(node) = stack.pop() {
1556 if !scope_step() {
1557 return None;
1558 }
1559 match node.kind() {
1560 "dotted_name" => {
1561 let identifiers = named_children_bounded(node, scope_step)?
1562 .into_iter()
1563 .filter(|child| child.kind() == "identifier")
1564 .collect::<Vec<_>>();
1565 if let [binding] = identifiers.as_slice() {
1566 let name = node_text(*binding, source);
1567 if !name.is_empty() {
1568 record(name, *binding);
1569 }
1570 }
1571 continue;
1572 }
1573 "splat_pattern" => {
1574 for child in named_children_bounded(node, scope_step)? {
1575 if child.kind() == "identifier" {
1576 let name = node_text(child, source);
1577 if !name.is_empty() {
1578 record(name, child);
1579 }
1580 }
1581 }
1582 continue;
1583 }
1584 "class_pattern" => {
1585 let mut children = named_children_bounded(node, scope_step)?;
1586 if children
1587 .first()
1588 .is_some_and(|child| child.kind() == "dotted_name")
1589 {
1590 children.remove(0);
1591 }
1592 stack.extend(children.into_iter().rev());
1593 continue;
1594 }
1595 "keyword_pattern" => {
1596 let mut children = named_children_bounded(node, scope_step)?;
1597 if children
1598 .first()
1599 .is_some_and(|child| child.kind() == "identifier")
1600 {
1601 children.remove(0);
1602 }
1603 stack.extend(children.into_iter().rev());
1604 continue;
1605 }
1606 "dict_pattern" => {
1607 let key_ids = children_by_field_name_bounded(node, "key", scope_step)?
1608 .into_iter()
1609 .map(|key| key.id())
1610 .collect::<HashSet<_>>();
1611 let children = named_children_bounded(node, scope_step)?;
1612 stack.extend(
1613 children
1614 .into_iter()
1615 .filter(|child| !key_ids.contains(&child.id()))
1616 .rev(),
1617 );
1618 continue;
1619 }
1620 "as_pattern" => {
1621 if let Some(alias) = node.child_by_field_name("alias") {
1622 collect_binding_targets(alias, source, scope_step, &mut record)?;
1623 let children = named_children_bounded(node, scope_step)?;
1624 stack.extend(
1625 children
1626 .into_iter()
1627 .filter(|child| child.id() != alias.id())
1628 .rev(),
1629 );
1630 continue;
1631 }
1632 let mut children = named_children_bounded(node, scope_step)?;
1633 if let Some(alias) = children
1634 .last()
1635 .copied()
1636 .filter(|child| child.kind() == "identifier")
1637 {
1638 let name = node_text(alias, source);
1639 if !name.is_empty() {
1640 record(name, alias);
1641 }
1642 children.pop();
1643 }
1644 stack.extend(children.into_iter().rev());
1645 continue;
1646 }
1647 "identifier" => {
1648 continue;
1651 }
1652 kind if is_pattern_literal(kind) => continue,
1653 _ => {}
1654 }
1655 let children = named_children_bounded(node, scope_step)?;
1656 stack.extend(children.into_iter().rev());
1657 }
1658 Some(())
1659}
1660
1661fn push_named_children<'tree>(
1662 stack: &mut Vec<Node<'tree>>,
1663 node: Node<'tree>,
1664 scope_step: &mut impl FnMut() -> bool,
1665) -> Option<()> {
1666 let children = named_children_bounded(node, scope_step)?;
1667 push_bounded_children(stack, children.into_iter());
1668 Some(())
1669}
1670
1671fn push_named_children_except<'tree>(
1672 stack: &mut Vec<Node<'tree>>,
1673 node: Node<'tree>,
1674 excluded: Node<'tree>,
1675 scope_step: &mut impl FnMut() -> bool,
1676) -> Option<()> {
1677 let children = named_children_bounded(node, scope_step)?;
1678 push_bounded_children(
1679 stack,
1680 children
1681 .into_iter()
1682 .filter(|child| child.id() != excluded.id()),
1683 );
1684 Some(())
1685}
1686
1687fn push_nested_scope_header_children<'tree>(
1688 stack: &mut Vec<Node<'tree>>,
1689 node: Node<'tree>,
1690 scope_step: &mut impl FnMut() -> bool,
1691) -> Option<()> {
1692 let body = node.child_by_field_name("body").map(|child| child.id());
1693 let name = node.child_by_field_name("name").map(|child| child.id());
1694 let mut cursor = node.walk();
1697 let children = node
1698 .named_children(&mut cursor)
1699 .filter(|child| Some(child.id()) != body);
1700 let mut headers = Vec::new();
1701 push_bounded_nodes(children, &mut headers, scope_step).then_some(())?;
1702 push_bounded_children(
1703 stack,
1704 headers.into_iter().filter(|child| Some(child.id()) != name),
1705 );
1706 Some(())
1707}
1708
1709fn push_bounded_children<'tree>(
1710 stack: &mut Vec<Node<'tree>>,
1711 children: impl DoubleEndedIterator<Item = Node<'tree>>,
1712) {
1713 for child in children.rev() {
1714 stack.push(child);
1715 }
1716}
1717
1718fn named_children_bounded<'tree>(
1719 node: Node<'tree>,
1720 scope_step: &mut impl FnMut() -> bool,
1721) -> Option<Vec<Node<'tree>>> {
1722 let mut cursor = node.walk();
1723 let mut children = Vec::new();
1724 push_bounded_nodes(node.named_children(&mut cursor), &mut children, scope_step)
1725 .then_some(children)
1726}
1727
1728fn children_by_field_name_bounded<'tree>(
1729 node: Node<'tree>,
1730 field: &str,
1731 scope_step: &mut impl FnMut() -> bool,
1732) -> Option<Vec<Node<'tree>>> {
1733 let mut cursor = node.walk();
1734 let mut children = Vec::new();
1735 push_bounded_nodes(
1736 node.children_by_field_name(field, &mut cursor),
1737 &mut children,
1738 scope_step,
1739 )
1740 .then_some(children)
1741}
1742
1743pub(crate) fn is_comprehension(kind: &str) -> bool {
1744 matches!(
1745 kind,
1746 "list_comprehension"
1747 | "set_comprehension"
1748 | "dictionary_comprehension"
1749 | "generator_expression"
1750 )
1751}
1752
1753fn is_pattern_literal(kind: &str) -> bool {
1754 matches!(
1755 kind,
1756 "string"
1757 | "concatenated_string"
1758 | "integer"
1759 | "float"
1760 | "complex_pattern"
1761 | "true"
1762 | "false"
1763 | "none"
1764 )
1765}
1766
1767#[cfg(test)]
1768mod tests {
1769 use brokk_bifrost_test_macros::hardwon;
1770
1771 #[test]
1772 fn only_the_first_comprehension_iterable_uses_the_enclosing_scope() {
1773 let source = "def run(first, second):\n return [second for first in first for second in second]\n";
1774 let mut parser = tree_sitter::Parser::new();
1775 parser
1776 .set_language(&tree_sitter_python::LANGUAGE.into())
1777 .unwrap();
1778 let tree = parser.parse(source, None).unwrap();
1779 let mut stack = vec![tree.root_node()];
1780 let comprehension = loop {
1781 let node = stack.pop().expect("fixture contains a comprehension");
1782 if node.kind() == "list_comprehension" {
1783 break node;
1784 }
1785 let mut cursor = node.walk();
1786 stack.extend(node.named_children(&mut cursor));
1787 };
1788 let mut cursor = comprehension.walk();
1789 let clauses = comprehension
1790 .named_children(&mut cursor)
1791 .filter(|node| node.kind() == "for_in_clause")
1792 .collect::<Vec<_>>();
1793 let outer = clauses[0].child_by_field_name("right").unwrap();
1794 let inner = clauses[1].child_by_field_name("right").unwrap();
1795 assert!(!super::python_comprehension_binds_name_at(
1796 "first", outer, source
1797 ));
1798 assert!(super::python_comprehension_binds_name_at(
1799 "second", inner, source
1800 ));
1801 }
1802
1803 #[test]
1804 #[hardwon(
1805 "#5044+#5511+#5751: bounded lexical proofs bypassed stops, lazy bindings or competing events"
1806 )]
1807 fn bounded_comprehension_lookup_preserves_exhaustion() {
1808 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";
1809 let tree = crate::declarations::parse_python_tree(source).unwrap();
1810 let function = tree.root_node().named_child(0).unwrap();
1811 let inventory = super::PythonLexicalScopeInventory::collect_bounded(
1812 function,
1813 source,
1814 ["xs".to_owned()],
1815 || true,
1816 )
1817 .unwrap();
1818 let mut stack = vec![function];
1819 let comprehension = loop {
1820 let node = stack.pop().expect("fixture contains a comprehension");
1821 if node.kind() == "list_comprehension" {
1822 break node;
1823 }
1824 stack.extend(brokk_bifrost_core::analyzer::tree_walk::named_children_iter(node));
1825 };
1826 let reference = comprehension.named_child(0).unwrap();
1827 for mut remaining in [0_usize, 1] {
1828 assert_eq!(
1829 inventory.name_resolution_at_bounded("target", reference, || {
1830 let available = remaining > 0;
1831 remaining = remaining.saturating_sub(1);
1832 available
1833 }),
1834 None
1835 );
1836 }
1837 assert_eq!(
1838 inventory.name_resolution_at_bounded("target", reference, || true),
1839 Some(super::PythonLexicalNameResolution::Local)
1840 );
1841 let body = function.child_by_field_name("body").unwrap();
1842 let derived = brokk_bifrost_core::analyzer::tree_walk::named_children_iter(body)
1843 .find(|node| node.child_by_field_name("superclasses").is_some())
1844 .unwrap();
1845 let base_reference = derived
1846 .child_by_field_name("superclasses")
1847 .unwrap()
1848 .named_child(0)
1849 .unwrap();
1850 let call = brokk_bifrost_core::analyzer::tree_walk::named_children_iter(body)
1851 .filter(|node| node.kind() == "expression_statement")
1852 .find_map(|node| node.named_child(0).filter(|node| node.kind() == "call"))
1853 .unwrap();
1854 let helper_reference = call.child_by_field_name("function").unwrap();
1855 let one_step = || {
1858 let mut available = true;
1859 move || std::mem::take(&mut available)
1860 };
1861 assert!(
1862 inventory
1863 .has_runtime_callable_binding_at_bounded("helper", helper_reference, one_step())
1864 .is_none()
1865 );
1866 assert!(
1867 inventory
1868 .local_function_declaration_bounded("helper", helper_reference, one_step())
1869 .is_none()
1870 );
1871 assert!(
1872 inventory
1873 .local_class_declaration_bounded("Base", base_reference, one_step())
1874 .is_none()
1875 );
1876 assert_eq!(
1877 inventory
1878 .local_function_declaration_bounded("helper", helper_reference, || true)
1879 .unwrap()
1880 .kind(),
1881 "function_definition"
1882 );
1883 assert_eq!(
1884 inventory
1885 .local_class_declaration_bounded("Base", base_reference, || true)
1886 .unwrap()
1887 .kind(),
1888 "class_definition"
1889 );
1890 let bindings = inventory.into_bindings();
1891 assert_eq!(
1892 bindings.name_resolution_at_bounded("missing", reference, one_step()),
1893 None,
1894 "stopped lookup must not manufacture Unbound"
1895 );
1896 assert_eq!(
1897 bindings.name_resolution_at_bounded("missing", reference, || true),
1898 Some(super::PythonLexicalNameResolution::Unbound)
1899 );
1900 assert_eq!(
1901 bindings.resolves_to_local_function_bounded("helper", helper_reference, one_step()),
1902 None
1903 );
1904 assert_eq!(
1905 bindings.resolves_to_local_function_bounded("helper", helper_reference, || true),
1906 Some(true)
1907 );
1908 assert_eq!(
1909 super::python_comprehension_binds_name_at_bounded(
1910 "target",
1911 reference,
1912 source,
1913 one_step()
1914 ),
1915 None
1916 );
1917 assert_eq!(
1918 super::python_comprehension_binds_name_at_bounded("target", reference, source, || true),
1919 Some(true)
1920 );
1921 let clause = brokk_bifrost_core::analyzer::tree_walk::named_children_iter(comprehension)
1922 .find(|node| node.kind() == "for_in_clause")
1923 .unwrap();
1924 let targets = super::python_binding_targets_bounded(
1925 clause.child_by_field_name("left").unwrap(),
1926 source,
1927 || true,
1928 )
1929 .unwrap();
1930 assert_eq!(targets.len(), 1);
1931 assert_eq!(
1932 Some(targets[0].declaration),
1933 clause.child_by_field_name("left")
1934 );
1935
1936 use super::PythonDirectScopeBindingKind::{ClassDeclaration, Other};
1940 for (source, expected_kind) in [
1941 ("Alias = Base\ntrailing = None\n", Some(Other)),
1942 ("Alias = Base\nAlias = Other\n", None),
1943 ("Alias = Base\ndel Alias\n", None),
1944 ("Alias = Base\nfrom pkg import Target as Alias\n", None),
1945 ("Alias = Base\nfrom pkg import *\n", None),
1946 ("from pkg import *\nAlias = Base\n", Some(Other)),
1947 (
1948 "Alias = Base\ndef inner():\n Alias = Other\nclass Container:\n Alias = Other\n",
1949 Some(Other),
1950 ),
1951 (
1952 "Alias = Base\ndef inner(value=(Alias := Other)):\n pass\n",
1953 None,
1954 ),
1955 ("class Alias:\n pass\n", Some(ClassDeclaration)),
1956 (
1957 "from pkg import *\nclass Alias:\n pass\n",
1958 Some(ClassDeclaration),
1959 ),
1960 ("class Alias:\n pass\nfrom pkg import *\n", None),
1961 ("Other = Base\n", None),
1962 ] {
1963 let tree = crate::declarations::parse_python_tree(source).unwrap();
1964 let root = tree.root_node();
1965 assert!(!root.has_error(), "{source}");
1966 let mut steps = 0_usize;
1967 let binding = super::python_unambiguous_module_binding_node_bounded(
1968 root,
1969 source,
1970 "Alias",
1971 || {
1972 steps += 1;
1973 true
1974 },
1975 )
1976 .expect("uninterrupted binding proof completes");
1977 assert_eq!(
1978 binding.map(|binding| binding.kind),
1979 expected_kind,
1980 "{source}"
1981 );
1982 if let Some(binding) = binding {
1983 assert_eq!(binding.declaration.kind(), "identifier");
1984 assert_eq!(super::node_text(binding.declaration, source), "Alias");
1985 let parent = binding.declaration.parent().unwrap();
1986 let field = if binding.kind == ClassDeclaration {
1987 "name"
1988 } else {
1989 "left"
1990 };
1991 assert_eq!(parent.child_by_field_name(field), Some(binding.declaration));
1992 let statement = if binding.kind == ClassDeclaration {
1993 parent
1994 } else {
1995 parent.parent().unwrap()
1996 };
1997 assert_eq!(statement.parent(), Some(root), "exact module-owned binder");
1998 }
1999 assert_eq!(
2000 super::python_unambiguous_module_binding_bounded(root, source, "Alias", || true),
2001 Some(expected_kind),
2002 "kind-only consumers retain the same positive requirement: {source}"
2003 );
2004 assert_eq!(
2005 super::python_unambiguous_module_class_binding_bounded(
2006 root,
2007 source,
2008 "Alias",
2009 || true
2010 ),
2011 Some(expected_kind == Some(ClassDeclaration))
2012 );
2013 for mut remaining in 0..steps {
2014 assert!(
2015 super::python_unambiguous_module_binding_node_bounded(
2016 root,
2017 source,
2018 "Alias",
2019 || {
2020 let available = remaining > 0;
2021 remaining = remaining.saturating_sub(1);
2022 available
2023 },
2024 )
2025 .is_none(),
2026 "a stopped proof cannot publish its early binder: {source}"
2027 );
2028 }
2029 }
2030
2031 let source = "import ns_source as ns\nclass Outer:\n class ns[T: ns.Target]((ns.Target)):\n pass\n";
2034 let tree = crate::declarations::parse_python_tree(source).unwrap();
2035 assert!(!tree.root_node().has_error());
2036 let outer = brokk_bifrost_core::analyzer::tree_walk::first_named_child_of_kind(
2037 tree.root_node(),
2038 "class_definition",
2039 )
2040 .unwrap();
2041 let inner = brokk_bifrost_core::analyzer::tree_walk::first_named_child_of_kind(
2042 outer.child_by_field_name("body").unwrap(),
2043 "class_definition",
2044 )
2045 .unwrap();
2046 let base = inner
2047 .child_by_field_name("superclasses")
2048 .unwrap()
2049 .named_child(0)
2050 .unwrap();
2051 let immediate = crate::syntax::python_static_value_path_bounded(base, || true)
2052 .unwrap()
2053 .unwrap()[0];
2054 let bound =
2055 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(
2056 inner.child_by_field_name("type_parameters").unwrap(),
2057 "attribute",
2058 )
2059 .unwrap()
2060 .child_by_field_name("object")
2061 .unwrap();
2062 for (reference, resolution, binds) in [
2063 (
2064 immediate,
2065 super::PythonLexicalNameResolution::Unbound,
2066 false,
2067 ),
2068 (bound, super::PythonLexicalNameResolution::Local, true),
2069 ] {
2070 assert!(
2071 super::PythonClassReferenceScope::new_bounded(outer, reference, || false).is_none()
2072 );
2073 let scope =
2074 super::PythonClassReferenceScope::new_bounded(outer, reference, || true).unwrap();
2075 let inventory = scope.collect_bounded(source, || true).unwrap();
2076 assert_eq!(
2077 inventory.name_resolution_at_bounded("ns", reference, || true),
2078 Some(resolution)
2079 );
2080 assert_eq!(scope.binds_name_bounded("ns", source, || true), Some(binds));
2081 }
2082 }
2083}