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