1use tree_sitter::Node;
2
3use brokk_bifrost_core::hash::{HashMap, HashSet};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum PythonLexicalNameResolution {
7 Unbound,
8 Local,
9 Nonlocal,
10 Global,
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum PythonDirectScopeBindingKind {
15 ClassDeclaration,
16 Other,
17}
18
19#[derive(Clone, Copy, Debug)]
20pub struct PythonDirectScopeBinding<'tree> {
21 pub declaration: Node<'tree>,
22 pub kind: PythonDirectScopeBindingKind,
23}
24
25#[derive(Clone, Debug)]
26struct PythonLocalBinding<'tree> {
27 name: Box<str>,
28 declaration: Node<'tree>,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32enum PythonLocalBindingKind {
33 FunctionOnly,
34 Other,
35}
36
37#[derive(Clone, Debug)]
38struct PythonComprehensionBinding {
39 name: Box<str>,
40 start_byte: usize,
41 end_byte: usize,
42 enclosing_iterable_ranges: Vec<(usize, usize)>,
43}
44
45pub struct PythonLexicalScopeInventory<'tree> {
52 parameters: HashSet<Box<str>>,
53 locals: Vec<PythonLocalBinding<'tree>>,
54 local_names: HashMap<Box<str>, PythonLocalBindingKind>,
55 binding_writes: HashSet<Box<str>>,
56 globals: HashSet<Box<str>>,
57 nonlocals: HashSet<Box<str>>,
58 comprehensions: Vec<PythonComprehensionBinding>,
59}
60
61#[derive(Clone, Copy)]
62struct ScanFrame<'tree> {
63 node: Node<'tree>,
64 in_comprehension: bool,
65}
66
67impl<'tree> PythonLexicalScopeInventory<'tree> {
68 pub fn collect_bounded(
75 callable: Node<'tree>,
76 source: &str,
77 parameter_names: impl IntoIterator<Item = String>,
78 mut scope_step: impl FnMut() -> bool,
79 ) -> Option<Self> {
80 let mut inventory = Self {
81 parameters: parameter_names.into_iter().map(Box::<str>::from).collect(),
82 locals: Vec::new(),
83 local_names: HashMap::default(),
84 binding_writes: HashSet::default(),
85 globals: HashSet::default(),
86 nonlocals: HashSet::default(),
87 comprehensions: Vec::new(),
88 };
89 let Some(body) = callable.child_by_field_name("body") else {
90 return Some(inventory);
91 };
92 let mut stack = vec![ScanFrame {
93 node: body,
94 in_comprehension: false,
95 }];
96
97 while let Some(frame) = stack.pop() {
98 if !scope_step() {
99 return None;
100 }
101 let node = frame.node;
102 let nested_scope = node != body
103 && matches!(
104 node.kind(),
105 "function_definition" | "lambda" | "class_definition"
106 );
107 if nested_scope {
108 if matches!(node.kind(), "function_definition" | "class_definition")
109 && let Some(name) = node.child_by_field_name("name")
110 {
111 inventory.record_local_node(name, source);
112 }
113 push_nested_scope_header_children(
118 &mut stack,
119 node,
120 frame.in_comprehension,
121 &mut scope_step,
122 )?;
123 continue;
124 }
125
126 match node.kind() {
127 "global_statement" => {
128 collect_direct_identifier_names(node, source, &mut scope_step, |name| {
129 inventory.globals.insert(name.into());
130 })?;
131 continue;
132 }
133 "nonlocal_statement" => {
134 collect_direct_identifier_names(node, source, &mut scope_step, |name| {
135 inventory.nonlocals.insert(name.into());
136 })?;
137 continue;
138 }
139 "import_statement" | "import_from_statement" => {
140 collect_import_bindings(node, &mut scope_step, |binding| {
141 inventory.record_local_node(binding, source)
142 })?;
143 continue;
144 }
145 "assignment" | "augmented_assignment" => {
146 if let Some(target) = node.child_by_field_name("left") {
147 collect_binding_targets(
148 target,
149 source,
150 &mut scope_step,
151 |name, declaration| {
152 inventory.record_binding_write(name, declaration);
153 },
154 )?;
155 }
156 }
157 "type_alias_statement" => {
158 if let Some(target) = node.child_by_field_name("left")
159 && let Some(binding) = first_identifier_bounded(target, &mut scope_step)?
160 {
161 inventory.record_local_node(binding, source);
162 }
163 }
164 "named_expression" => {
165 if let Some(target) = node.child_by_field_name("name") {
168 collect_binding_targets(
169 target,
170 source,
171 &mut scope_step,
172 |name, declaration| {
173 inventory.record_binding_write(name, declaration);
174 },
175 )?;
176 }
177 }
178 "for_statement" => {
179 if let Some(target) = node.child_by_field_name("left") {
180 collect_binding_targets(
181 target,
182 source,
183 &mut scope_step,
184 |name, declaration| {
185 inventory.record_binding_write(name, declaration);
186 },
187 )?;
188 }
189 }
190 "for_in_clause" => {
191 }
194 "delete_statement" => {
195 for target in named_children_bounded(node, &mut scope_step)? {
196 collect_binding_targets(
197 target,
198 source,
199 &mut scope_step,
200 |name, declaration| {
201 inventory.record_binding_write(name, declaration);
202 },
203 )?;
204 }
205 continue;
206 }
207 "as_pattern" => {
208 if let Some(alias) = node.child_by_field_name("alias") {
209 collect_binding_targets(
210 alias,
211 source,
212 &mut scope_step,
213 |name, declaration| {
214 inventory.record_binding_write(name, declaration);
215 },
216 )?;
217 push_named_children_except(
218 &mut stack,
219 node,
220 alias,
221 frame.in_comprehension,
222 &mut scope_step,
223 )?;
224 continue;
225 }
226 }
227 "except_clause" => {
228 if let Some(alias) = node.child_by_field_name("alias") {
232 collect_binding_targets(
233 alias,
234 source,
235 &mut scope_step,
236 |name, declaration| {
237 inventory.record_binding_write(name, declaration);
238 },
239 )?;
240 }
241 }
242 "case_clause" => {
243 let children = named_children_bounded(node, &mut scope_step)?;
244 for child in children.iter().copied() {
245 if child.kind() == "case_pattern" {
246 collect_match_pattern_bindings(
247 child,
248 source,
249 &mut scope_step,
250 |name, declaration| {
251 inventory.record_binding_write(name, declaration);
252 },
253 )?;
254 }
255 }
256 for child in children.into_iter().rev() {
257 if child.kind() != "case_pattern" {
258 stack.push(ScanFrame {
259 node: child,
260 in_comprehension: frame.in_comprehension,
261 });
262 }
263 }
264 continue;
265 }
266 kind if is_comprehension(kind) => {
267 let range = (node.start_byte(), node.end_byte());
268 let children = named_children_bounded(node, &mut scope_step)?;
269 let enclosing_iterable_ranges = if let Some(first_clause) = children
270 .iter()
271 .copied()
272 .find(|child| child.kind() == "for_in_clause")
273 {
274 children_by_field_name_bounded(first_clause, "right", &mut scope_step)?
275 .into_iter()
276 .map(|iterable| (iterable.start_byte(), iterable.end_byte()))
277 .collect()
278 } else {
279 Vec::new()
280 };
281 for clause in children
282 .iter()
283 .copied()
284 .filter(|child| child.kind() == "for_in_clause")
285 {
286 if let Some(target) = clause.child_by_field_name("left") {
287 collect_binding_targets(target, source, &mut scope_step, |name, _| {
288 inventory.comprehensions.push(PythonComprehensionBinding {
289 name: name.into(),
290 start_byte: range.0,
291 end_byte: range.1,
292 enclosing_iterable_ranges: enclosing_iterable_ranges.clone(),
293 });
294 })?;
295 }
296 }
297 for child in children.into_iter().rev() {
298 stack.push(ScanFrame {
299 node: child,
300 in_comprehension: true,
301 });
302 }
303 continue;
304 }
305 _ => {}
306 }
307
308 push_named_children(&mut stack, node, frame.in_comprehension, &mut scope_step)?;
309 }
310
311 inventory.locals.retain(|binding| {
314 !inventory.globals.contains(binding.name.as_ref())
315 && !inventory.nonlocals.contains(binding.name.as_ref())
316 });
317 inventory.local_names.retain(|name, _| {
318 !inventory.globals.contains(name.as_ref())
319 && !inventory.nonlocals.contains(name.as_ref())
320 });
321 Some(inventory)
322 }
323
324 pub fn name_resolution_at(
325 &self,
326 name: &str,
327 reference: Node<'_>,
328 ) -> PythonLexicalNameResolution {
329 let reference_byte = reference.start_byte();
330 if self.comprehensions.iter().any(|binding| {
331 binding.name.as_ref() == name
332 && binding.start_byte <= reference_byte
333 && reference_byte < binding.end_byte
334 && !binding
335 .enclosing_iterable_ranges
336 .iter()
337 .any(|(start, end)| *start <= reference_byte && reference_byte < *end)
338 }) {
339 return PythonLexicalNameResolution::Local;
340 }
341 if self.nonlocals.contains(name) {
342 return PythonLexicalNameResolution::Nonlocal;
343 }
344 if self.globals.contains(name) {
345 return PythonLexicalNameResolution::Global;
346 }
347 if self.parameters.contains(name) || self.local_names.contains_key(name) {
348 PythonLexicalNameResolution::Local
349 } else {
350 PythonLexicalNameResolution::Unbound
351 }
352 }
353
354 pub fn resolves_to_local_function(&self, name: &str, reference: Node<'_>) -> bool {
355 let reference_byte = reference.start_byte();
356 !self.parameters.contains(name)
357 && !self.comprehensions.iter().any(|binding| {
358 binding.name.as_ref() == name
359 && binding.start_byte <= reference_byte
360 && reference_byte < binding.end_byte
361 && !binding
362 .enclosing_iterable_ranges
363 .iter()
364 .any(|(start, end)| *start <= reference_byte && reference_byte < *end)
365 })
366 && self.local_names.get(name) == Some(&PythonLocalBindingKind::FunctionOnly)
367 }
368
369 pub fn has_runtime_callable_binding_at(&self, name: &str, reference: Node<'_>) -> bool {
372 let reference_byte = reference.start_byte();
373 self.parameters.contains(name)
374 || self.binding_writes.contains(name)
375 || self.comprehensions.iter().any(|binding| {
376 binding.name.as_ref() == name
377 && binding.start_byte <= reference_byte
378 && reference_byte < binding.end_byte
379 && !binding
380 .enclosing_iterable_ranges
381 .iter()
382 .any(|(start, end)| *start <= reference_byte && reference_byte < *end)
383 })
384 }
385
386 pub fn local_function_declaration(
387 &self,
388 name: &str,
389 reference: Node<'_>,
390 ) -> Option<Node<'tree>> {
391 self.resolves_to_local_function(name, reference)
392 .then(|| {
393 self.locals
394 .iter()
395 .find(|binding| binding.name.as_ref() == name)
396 .and_then(|binding| binding.declaration.parent())
397 .filter(|declaration| declaration.kind() == "function_definition")
398 })
399 .flatten()
400 }
401
402 pub fn local_bindings(&self) -> impl Iterator<Item = (&str, Node<'tree>)> + '_ {
403 self.locals
404 .iter()
405 .map(|binding| (binding.name.as_ref(), binding.declaration))
406 }
407
408 fn record_local_node(&mut self, node: Node<'tree>, source: &str) {
409 let name = node_text(node, source);
410 self.record_local(name, node);
411 }
412
413 fn record_local(&mut self, name: &str, declaration: Node<'tree>) {
414 if name.is_empty() {
415 return;
416 }
417 let binding_kind = if is_function_declaration_name(declaration) {
418 PythonLocalBindingKind::FunctionOnly
419 } else {
420 PythonLocalBindingKind::Other
421 };
422 match self.local_names.entry(name.into()) {
423 std::collections::hash_map::Entry::Vacant(entry) => {
424 entry.insert(binding_kind);
425 self.locals.push(PythonLocalBinding {
426 name: name.into(),
427 declaration,
428 });
429 }
430 std::collections::hash_map::Entry::Occupied(mut entry) => {
431 entry.insert(PythonLocalBindingKind::Other);
432 }
433 }
434 }
435
436 fn record_binding_write(&mut self, name: &str, declaration: Node<'tree>) {
437 if !name.is_empty() {
438 self.binding_writes.insert(name.into());
439 }
440 self.record_local(name, declaration);
441 }
442}
443
444fn is_function_declaration_name(node: Node<'_>) -> bool {
445 node.parent().is_some_and(|parent| {
446 parent.kind() == "function_definition"
447 && parent
448 .child_by_field_name("name")
449 .is_some_and(|name| name.id() == node.id())
450 })
451}
452
453pub fn python_direct_scope_bindings_bounded<'tree>(
460 node: Node<'tree>,
461 source: &str,
462 mut scope_step: impl FnMut() -> bool,
463) -> Option<Vec<PythonDirectScopeBinding<'tree>>> {
464 let mut bindings = Vec::new();
465
466 match node.kind() {
467 "function_definition" => {
468 if let Some(name) = node.child_by_field_name("name") {
469 bindings.push(PythonDirectScopeBinding {
470 declaration: name,
471 kind: PythonDirectScopeBindingKind::Other,
472 });
473 }
474 }
475 "class_definition" => {
476 if let Some(name) = node.child_by_field_name("name") {
477 bindings.push(PythonDirectScopeBinding {
478 declaration: name,
479 kind: if is_direct_module_definition_bounded(node, &mut scope_step)? {
480 PythonDirectScopeBindingKind::ClassDeclaration
481 } else {
482 PythonDirectScopeBindingKind::Other
483 },
484 });
485 }
486 }
487 "import_statement" | "import_from_statement" => {
488 collect_import_bindings(node, &mut scope_step, |declaration| {
489 bindings.push(PythonDirectScopeBinding {
490 declaration,
491 kind: PythonDirectScopeBindingKind::Other,
492 });
493 })?;
494 }
495 "assignment" | "augmented_assignment" => {
496 if let Some(target) = node.child_by_field_name("left") {
497 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
498 bindings.push(PythonDirectScopeBinding {
499 declaration,
500 kind: PythonDirectScopeBindingKind::Other,
501 });
502 })?;
503 }
504 }
505 "type_alias_statement" => {
506 if let Some(target) = node.child_by_field_name("left")
507 && let Some(declaration) = first_identifier_bounded(target, &mut scope_step)?
508 {
509 bindings.push(PythonDirectScopeBinding {
510 declaration,
511 kind: PythonDirectScopeBindingKind::Other,
512 });
513 }
514 }
515 "named_expression" => {
516 if let Some(target) = node.child_by_field_name("name") {
517 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
518 bindings.push(PythonDirectScopeBinding {
519 declaration,
520 kind: PythonDirectScopeBindingKind::Other,
521 });
522 })?;
523 }
524 }
525 "for_statement" => {
526 if let Some(target) = node.child_by_field_name("left") {
527 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
528 bindings.push(PythonDirectScopeBinding {
529 declaration,
530 kind: PythonDirectScopeBindingKind::Other,
531 });
532 })?;
533 }
534 }
535 "delete_statement" => {
536 for target in named_children_bounded(node, &mut scope_step)? {
537 collect_binding_targets(target, source, &mut scope_step, |_, declaration| {
538 bindings.push(PythonDirectScopeBinding {
539 declaration,
540 kind: PythonDirectScopeBindingKind::Other,
541 });
542 })?;
543 }
544 }
545 "as_pattern" => {
546 if let Some(alias) = node.child_by_field_name("alias") {
547 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
548 bindings.push(PythonDirectScopeBinding {
549 declaration,
550 kind: PythonDirectScopeBindingKind::Other,
551 });
552 })?;
553 }
554 }
555 "except_clause" => {
556 if let Some(alias) = node.child_by_field_name("alias") {
557 collect_binding_targets(alias, source, &mut scope_step, |_, declaration| {
558 bindings.push(PythonDirectScopeBinding {
559 declaration,
560 kind: PythonDirectScopeBindingKind::Other,
561 });
562 })?;
563 }
564 }
565 "case_clause" => {
566 for child in named_children_bounded(node, &mut scope_step)? {
567 if child.kind() == "case_pattern" {
568 collect_match_pattern_bindings(
569 child,
570 source,
571 &mut scope_step,
572 |_, declaration| {
573 bindings.push(PythonDirectScopeBinding {
574 declaration,
575 kind: PythonDirectScopeBindingKind::Other,
576 });
577 },
578 )?;
579 }
580 }
581 }
582 _ => {}
583 }
584 Some(bindings)
585}
586
587pub fn python_unambiguous_module_class_binding_bounded(
588 root: Node<'_>,
589 source: &str,
590 target_name: &str,
591 mut scope_step: impl FnMut() -> bool,
592) -> Option<bool> {
593 let mut matched = None;
594 let mut stack = vec![root];
595 while let Some(node) = stack.pop() {
596 if !scope_step() {
597 return None;
598 }
599 for binding in python_direct_scope_bindings_bounded(node, source, &mut scope_step)? {
600 if node_text(binding.declaration, source) != target_name {
601 continue;
602 }
603 if matched.is_some() {
604 return Some(false);
605 }
606 matched = Some(binding.kind);
607 if binding.kind == PythonDirectScopeBindingKind::Other {
608 return Some(false);
609 }
610 }
611
612 let body = matches!(
613 node.kind(),
614 "function_definition" | "class_definition" | "lambda"
615 )
616 .then(|| node.child_by_field_name("body").map(|child| child.id()))
617 .flatten();
618 let name = matches!(node.kind(), "function_definition" | "class_definition")
619 .then(|| node.child_by_field_name("name").map(|child| child.id()))
620 .flatten();
621 for child in named_children_bounded(node, &mut scope_step)?
622 .into_iter()
623 .filter(|child| Some(child.id()) != body && Some(child.id()) != name)
624 .rev()
625 {
626 stack.push(child);
627 }
628 }
629 Some(matched == Some(PythonDirectScopeBindingKind::ClassDeclaration))
630}
631
632fn is_direct_module_definition_bounded(
633 node: Node<'_>,
634 scope_step: &mut impl FnMut() -> bool,
635) -> Option<bool> {
636 if !scope_step() {
637 return None;
638 }
639 let Some(mut parent) = node.parent() else {
640 return Some(false);
641 };
642 if parent.kind() == "decorated_definition" {
643 if !scope_step() {
644 return None;
645 }
646 let Some(grandparent) = parent.parent() else {
647 return Some(false);
648 };
649 parent = grandparent;
650 }
651 Some(parent.kind() == "module")
652}
653
654fn collect_direct_identifier_names(
655 node: Node<'_>,
656 source: &str,
657 scope_step: &mut impl FnMut() -> bool,
658 mut record: impl FnMut(&str),
659) -> Option<()> {
660 for child in named_children_bounded(node, scope_step)? {
661 if child.kind() == "identifier" {
662 let name = node_text(child, source);
663 if !name.is_empty() {
664 record(name);
665 }
666 }
667 }
668 Some(())
669}
670
671fn collect_import_bindings<'tree>(
672 statement: Node<'tree>,
673 scope_step: &mut impl FnMut() -> bool,
674 mut record: impl FnMut(Node<'tree>),
675) -> Option<()> {
676 let mut cursor = statement.walk();
677 let mut imports = Vec::new();
678 for imported in statement.children_by_field_name("name", &mut cursor) {
679 if !scope_step() {
680 return None;
681 }
682 imports.push(imported);
683 }
684 for imported in imports {
685 if let Some(alias) = imported.child_by_field_name("alias") {
686 record(alias);
687 continue;
688 }
689 let name = imported.child_by_field_name("name").unwrap_or(imported);
690 let binding = if statement.kind() == "import_statement" {
691 first_identifier_bounded(name, scope_step)?
692 } else {
693 last_identifier_bounded(name, scope_step)?
694 };
695 if let Some(binding) = binding {
696 record(binding);
697 }
698 }
699 Some(())
700}
701
702fn first_identifier_bounded<'tree>(
703 root: Node<'tree>,
704 scope_step: &mut impl FnMut() -> bool,
705) -> Option<Option<Node<'tree>>> {
706 let mut stack = vec![root];
707 while let Some(node) = stack.pop() {
708 if !scope_step() {
709 return None;
710 }
711 if node.kind() == "identifier" {
712 return Some(Some(node));
713 }
714 let children = named_children_bounded(node, scope_step)?;
715 stack.extend(children.into_iter().rev());
716 }
717 Some(None)
718}
719
720fn last_identifier_bounded<'tree>(
721 root: Node<'tree>,
722 scope_step: &mut impl FnMut() -> bool,
723) -> Option<Option<Node<'tree>>> {
724 let mut result = None;
725 let mut stack = vec![root];
726 while let Some(node) = stack.pop() {
727 if !scope_step() {
728 return None;
729 }
730 if node.kind() == "identifier" {
731 result = Some(node);
732 continue;
733 }
734 let children = named_children_bounded(node, scope_step)?;
735 stack.extend(children.into_iter().rev());
736 }
737 Some(result)
738}
739
740fn collect_binding_targets<'tree>(
741 target: Node<'tree>,
742 source: &str,
743 scope_step: &mut impl FnMut() -> bool,
744 mut record: impl FnMut(&str, Node<'tree>),
745) -> Option<()> {
746 let mut stack = vec![target];
747 while let Some(node) = stack.pop() {
748 if !scope_step() {
749 return None;
750 }
751 match node.kind() {
752 "attribute" | "subscript" => continue,
755 "identifier" | "keyword_identifier" => {
756 let name = node_text(node, source);
757 if !name.is_empty() {
758 record(name, node);
759 }
760 continue;
761 }
762 _ => {}
763 }
764 let children = named_children_bounded(node, scope_step)?;
765 if node.kind() == "as_pattern_target" && children.is_empty() {
766 let name = node_text(node, source);
767 if !name.is_empty() {
768 record(name, node);
769 }
770 continue;
771 }
772 stack.extend(children.into_iter().rev());
773 }
774 Some(())
775}
776
777fn collect_match_pattern_bindings<'tree>(
778 pattern: Node<'tree>,
779 source: &str,
780 scope_step: &mut impl FnMut() -> bool,
781 mut record: impl FnMut(&str, Node<'tree>),
782) -> Option<()> {
783 let mut stack = vec![pattern];
784 while let Some(node) = stack.pop() {
785 if !scope_step() {
786 return None;
787 }
788 match node.kind() {
789 "dotted_name" => {
790 let identifiers = named_children_bounded(node, scope_step)?
791 .into_iter()
792 .filter(|child| child.kind() == "identifier")
793 .collect::<Vec<_>>();
794 if let [binding] = identifiers.as_slice() {
795 let name = node_text(*binding, source);
796 if !name.is_empty() {
797 record(name, *binding);
798 }
799 }
800 continue;
801 }
802 "splat_pattern" => {
803 for child in named_children_bounded(node, scope_step)? {
804 if child.kind() == "identifier" {
805 let name = node_text(child, source);
806 if !name.is_empty() {
807 record(name, child);
808 }
809 }
810 }
811 continue;
812 }
813 "class_pattern" => {
814 let mut children = named_children_bounded(node, scope_step)?;
815 if children
816 .first()
817 .is_some_and(|child| child.kind() == "dotted_name")
818 {
819 children.remove(0);
820 }
821 stack.extend(children.into_iter().rev());
822 continue;
823 }
824 "keyword_pattern" => {
825 let mut children = named_children_bounded(node, scope_step)?;
826 if children
827 .first()
828 .is_some_and(|child| child.kind() == "identifier")
829 {
830 children.remove(0);
831 }
832 stack.extend(children.into_iter().rev());
833 continue;
834 }
835 "dict_pattern" => {
836 let key_ids = children_by_field_name_bounded(node, "key", scope_step)?
837 .into_iter()
838 .map(|key| key.id())
839 .collect::<HashSet<_>>();
840 let children = named_children_bounded(node, scope_step)?;
841 stack.extend(
842 children
843 .into_iter()
844 .filter(|child| !key_ids.contains(&child.id()))
845 .rev(),
846 );
847 continue;
848 }
849 "as_pattern" => {
850 if let Some(alias) = node.child_by_field_name("alias") {
851 collect_binding_targets(alias, source, scope_step, &mut record)?;
852 let children = named_children_bounded(node, scope_step)?;
853 stack.extend(
854 children
855 .into_iter()
856 .filter(|child| child.id() != alias.id())
857 .rev(),
858 );
859 continue;
860 }
861 let mut children = named_children_bounded(node, scope_step)?;
862 if let Some(alias) = children
863 .last()
864 .copied()
865 .filter(|child| child.kind() == "identifier")
866 {
867 let name = node_text(alias, source);
868 if !name.is_empty() {
869 record(name, alias);
870 }
871 children.pop();
872 }
873 stack.extend(children.into_iter().rev());
874 continue;
875 }
876 "identifier" => {
877 continue;
880 }
881 kind if is_pattern_literal(kind) => continue,
882 _ => {}
883 }
884 let children = named_children_bounded(node, scope_step)?;
885 stack.extend(children.into_iter().rev());
886 }
887 Some(())
888}
889
890fn push_named_children<'tree>(
891 stack: &mut Vec<ScanFrame<'tree>>,
892 node: Node<'tree>,
893 in_comprehension: bool,
894 scope_step: &mut impl FnMut() -> bool,
895) -> Option<()> {
896 for child in named_children_bounded(node, scope_step)?.into_iter().rev() {
897 stack.push(ScanFrame {
898 node: child,
899 in_comprehension,
900 });
901 }
902 Some(())
903}
904
905fn push_named_children_except<'tree>(
906 stack: &mut Vec<ScanFrame<'tree>>,
907 node: Node<'tree>,
908 excluded: Node<'tree>,
909 in_comprehension: bool,
910 scope_step: &mut impl FnMut() -> bool,
911) -> Option<()> {
912 for child in named_children_bounded(node, scope_step)?
913 .into_iter()
914 .filter(|child| child.id() != excluded.id())
915 .rev()
916 {
917 stack.push(ScanFrame {
918 node: child,
919 in_comprehension,
920 });
921 }
922 Some(())
923}
924
925fn push_nested_scope_header_children<'tree>(
926 stack: &mut Vec<ScanFrame<'tree>>,
927 node: Node<'tree>,
928 in_comprehension: bool,
929 scope_step: &mut impl FnMut() -> bool,
930) -> Option<()> {
931 let body = node.child_by_field_name("body").map(|child| child.id());
932 let name = node.child_by_field_name("name").map(|child| child.id());
933 for child in named_children_bounded(node, scope_step)?
934 .into_iter()
935 .filter(|child| Some(child.id()) != body && Some(child.id()) != name)
936 .rev()
937 {
938 stack.push(ScanFrame {
939 node: child,
940 in_comprehension,
941 });
942 }
943 Some(())
944}
945
946fn named_children_bounded<'tree>(
947 node: Node<'tree>,
948 scope_step: &mut impl FnMut() -> bool,
949) -> Option<Vec<Node<'tree>>> {
950 let mut cursor = node.walk();
951 let mut children = Vec::new();
952 for child in node.named_children(&mut cursor) {
953 if !scope_step() {
954 return None;
955 }
956 children.push(child);
957 }
958 Some(children)
959}
960
961fn children_by_field_name_bounded<'tree>(
962 node: Node<'tree>,
963 field: &str,
964 scope_step: &mut impl FnMut() -> bool,
965) -> Option<Vec<Node<'tree>>> {
966 let mut cursor = node.walk();
967 let mut children = Vec::new();
968 for child in node.children_by_field_name(field, &mut cursor) {
969 if !scope_step() {
970 return None;
971 }
972 children.push(child);
973 }
974 Some(children)
975}
976
977fn is_comprehension(kind: &str) -> bool {
978 matches!(
979 kind,
980 "list_comprehension"
981 | "set_comprehension"
982 | "dictionary_comprehension"
983 | "generator_expression"
984 )
985}
986
987fn is_pattern_literal(kind: &str) -> bool {
988 matches!(
989 kind,
990 "string"
991 | "concatenated_string"
992 | "integer"
993 | "float"
994 | "complex_pattern"
995 | "true"
996 | "false"
997 | "none"
998 )
999}
1000
1001fn node_text<'a>(node: Node<'_>, source: &'a str) -> &'a str {
1002 brokk_bifrost_core::analyzer::common::node_source_text_trimmed(node, source)
1003}