1use super::extractor::{
20 call_result_types, collect_assigned_identifiers, collect_function_scope_facts_from_node,
21 collect_scope_facts_from_parsed_source, enclosing_scope_facts, is_declaration_identifier,
22 slice,
23};
24use super::resolver::{
25 annotation_reference_candidates, resolve_callable_parameter_default_types,
26 resolve_constructor_types, resolve_receiver_type,
27};
28use crate::graph::PythonGraphSource;
29use crate::graph_support::PythonUsageSource;
30use crate::imports::resolve_fqn_candidates;
31use crate::usage_index::{usage_resolve_module_files, usage_scope_facts};
32use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path;
33use brokk_bifrost_core::analyzer::usages::inverted_edges::{
34 FileEdgeScanInput, PerFileEdges, classify_reference_node,
35};
36use brokk_bifrost_core::analyzer::usages::local_inference::LocalBindingsSnapshot;
37use brokk_bifrost_core::analyzer::usages::model::ImportKind;
38use brokk_bifrost_core::analyzer::{CodeUnit, Language, ProjectFile, Range};
39use brokk_bifrost_core::hash::{HashMap, HashSet};
40use std::sync::{Arc, Mutex};
41use tree_sitter::Node;
42
43pub struct PythonEdgeScan<'a> {
50 targets: &'a HashSet<String>,
51 targets_by_terminal: HashMap<String, Vec<String>>,
52 canonical_namespace_candidates: Mutex<HashMap<String, Arc<Vec<String>>>>,
53}
54
55impl<'a> PythonEdgeScan<'a> {
56 pub fn new(nodes: &HashSet<String>, targets: &'a HashSet<String>) -> Self {
59 debug_assert!(targets.is_subset(nodes));
60 let mut targets_by_terminal: HashMap<String, Vec<String>> = HashMap::default();
61 for target in targets {
62 let terminal = parse_symbol_path(Language::Python, target)
67 .pop()
68 .unwrap_or_else(|| target.clone());
69 targets_by_terminal
70 .entry(terminal)
71 .or_default()
72 .push(target.clone());
73 }
74 Self {
75 targets,
76 targets_by_terminal,
77 canonical_namespace_candidates: Mutex::new(HashMap::default()),
78 }
79 }
80
81 pub fn scan_file(
87 &self,
88 graph: &PythonGraphSource<'_>,
89 python: &dyn PythonUsageSource,
90 file: &ProjectFile,
91 input: &FileEdgeScanInput<'_>,
92 ) -> PerFileEdges {
93 let source = input.source;
94
95 let binder = python.import_binder_of(file);
100 let mut named: HashMap<String, String> = HashMap::default();
101 let mut namespace: HashMap<String, NamespaceBinding> = HashMap::default();
102 for (local, binding) in &binder.bindings {
103 match binding.kind {
104 ImportKind::Named => {
105 if let Some(imported) = &binding.imported_name {
106 let module = canonical_import_module_fqn(
107 graph,
108 python,
109 file,
110 &binding.module_specifier,
111 )
112 .unwrap_or_else(|| binding.module_specifier.clone());
113 let imported_fqn = if module.ends_with('.') {
114 format!("{module}{imported}")
115 } else {
116 format!("{module}.{imported}")
117 };
118 if let Some(imported_module) =
119 canonical_import_module_fqn(graph, python, file, &imported_fqn)
120 {
121 namespace.insert(
122 local.clone(),
123 NamespaceBinding {
124 module: imported_module,
125 workspace_module: true,
126 consumed_attributes: 0,
127 },
128 );
129 } else {
130 named.insert(local.clone(), imported_fqn);
131 }
132 }
133 }
134 ImportKind::Namespace => {
135 let direct_module = binding.module_specifier.clone();
136 let imported_module = binding
137 .namespace_imported_module
138 .as_deref()
139 .unwrap_or(&direct_module);
140 let module = canonical_import_module_fqn(graph, python, file, imported_module);
141 let workspace_module = module.is_some();
142 let consumed_attributes = module.as_ref().map_or(0, |_| {
143 let imported_segments =
144 parse_symbol_path(Language::Python, imported_module);
145 let bound_segments = parse_symbol_path(Language::Python, &direct_module);
146 imported_segments.len().saturating_sub(bound_segments.len())
147 });
148 namespace.insert(
149 local.clone(),
150 NamespaceBinding {
151 module: module.unwrap_or(direct_module),
152 workspace_module,
153 consumed_attributes,
154 },
155 );
156 }
157 ImportKind::Default | ImportKind::CommonJsRequire | ImportKind::Glob => {}
158 }
159 }
160 let same_file: HashMap<String, String> = graph
161 .index
162 .declarations(file)
163 .into_iter()
164 .map(|unit| (unit.identifier().to_string(), unit.fq_name()))
165 .collect();
166
167 let scope_facts = usage_scope_facts(python, file, || {
171 collect_scope_facts_from_parsed_source(graph, python, file, source, input.root())
172 });
173
174 let mut ctx = PyScan {
175 graph,
176 python,
177 targets: self.targets,
178 targets_by_terminal: &self.targets_by_terminal,
179 file,
180 source,
181 named,
182 namespace,
183 same_file,
184 scope_facts: scope_facts.as_ref(),
185 canonical_namespace_candidates: &self.canonical_namespace_candidates,
186 input,
187 edges: PerFileEdges::default(),
188 };
189 scan_tree(input.root(), &mut ctx);
190 ctx.edges
191 }
192}
193
194fn canonical_import_module_fqn(
195 graph: &PythonGraphSource<'_>,
196 python: &dyn PythonUsageSource,
197 importing_file: &ProjectFile,
198 module_specifier: &str,
199) -> Option<String> {
200 let resolved = usage_resolve_module_files(python, importing_file, module_specifier);
201 let [module_file] = resolved.as_slice() else {
202 return None;
203 };
204 graph
205 .index
206 .declarations(module_file)
207 .into_iter()
208 .find(CodeUnit::is_module)
209 .map(|module| module.fq_name())
210}
211
212struct PyScan<'a> {
213 graph: &'a PythonGraphSource<'a>,
214 python: &'a dyn PythonUsageSource,
215 targets: &'a HashSet<String>,
216 targets_by_terminal: &'a HashMap<String, Vec<String>>,
217 file: &'a ProjectFile,
218 source: &'a str,
219 named: HashMap<String, String>,
220 namespace: HashMap<String, NamespaceBinding>,
221 same_file: HashMap<String, String>,
222 scope_facts: &'a HashMap<CodeUnit, LocalBindingsSnapshot<String>>,
223 canonical_namespace_candidates: &'a Mutex<HashMap<String, Arc<Vec<String>>>>,
224 input: &'a FileEdgeScanInput<'a>,
225 edges: PerFileEdges,
226}
227
228struct NamespaceBinding {
229 module: String,
230 workspace_module: bool,
231 consumed_attributes: usize,
232}
233
234impl PyScan<'_> {
235 fn bare_callee(&self, text: &str) -> Option<String> {
238 if let Some(fqn) = self.named.get(text) {
239 return Some(fqn.clone());
240 }
241 if let Some(fqn) = self.namespace.get(text) {
242 return Some(fqn.module.clone());
243 }
244 if let Some(fqn) = self.same_file.get(text) {
245 return Some(fqn.clone());
246 }
247 None
248 }
249
250 fn receiver_type_fqn(
254 &self,
255 facts: &LocalBindingsSnapshot<String>,
256 receiver: &str,
257 ) -> Option<String> {
258 let resolution = facts.resolution_for(receiver);
259 let type_name = resolution
260 .as_precise()
261 .and_then(|targets| targets.iter().next())?;
262 resolve_receiver_type(self.graph, self.python, self.file, type_name, false)
268 .map(|unit| unit.fq_name())
269 }
270
271 fn record(&mut self, callee: String, node: Node<'_>) {
272 if !self.targets.contains(&callee) {
273 return;
274 }
275 self.edges.record_kind(
276 self.input,
277 callee,
278 classify_reference_node(node),
279 node.start_byte(),
280 node.end_byte(),
281 );
282 }
283
284 fn record_unproven_name(&mut self, name: &str, node: Node<'_>) {
285 let Some(targets) = self.targets_by_terminal.get(name) else {
286 return;
287 };
288 for target in targets {
289 self.edges.record_unproven(
290 self.input,
291 target.clone(),
292 node.start_byte(),
293 node.end_byte(),
294 );
295 }
296 }
297
298 fn canonical_namespace_candidates(&self, direct: &str) -> Arc<Vec<String>> {
299 if let Some(cached) = self
300 .canonical_namespace_candidates
301 .lock()
302 .expect("Python namespace candidate cache mutex poisoned")
303 .get(direct)
304 .cloned()
305 {
306 return cached;
307 }
308
309 let resolved: Arc<Vec<String>> = Arc::new(
310 resolve_fqn_candidates(self.python, direct, |name| {
311 self.graph.index.definitions(name).collect()
312 })
313 .into_iter()
314 .map(|unit| unit.fq_name())
315 .collect(),
316 );
317 self.canonical_namespace_candidates
318 .lock()
319 .expect("Python namespace candidate cache mutex poisoned")
320 .entry(direct.to_string())
321 .or_insert_with(|| resolved.clone())
322 .clone()
323 }
324}
325
326fn scan_tree(root: Node<'_>, ctx: &mut PyScan<'_>) {
327 let mut scopes: Vec<FunctionScope> = Vec::new();
330 walk(root, ctx, &mut scopes, None);
331}
332
333fn walk(
334 node: Node<'_>,
335 ctx: &mut PyScan<'_>,
336 scopes: &mut Vec<FunctionScope>,
337 facts: Option<usize>,
338) {
339 let mut merged_facts = Vec::new();
340 let mut stack = vec![WalkFrame::Enter { node, facts }];
341 while let Some(frame) = stack.pop() {
342 match frame {
343 WalkFrame::Enter { node, facts } => match node.kind() {
344 "import_statement" | "import_from_statement" => {}
345 "function_definition" | "lambda" => {
349 let function_scope = collect_function_scope(node, ctx.source);
350 let scope_facts = merged_enclosing_scope_facts(
351 ctx.graph,
352 ctx.file,
353 ctx.scope_facts,
354 &mut merged_facts,
355 node,
356 ctx.source,
357 facts,
358 );
359 push_function_children(node, facts, scope_facts, function_scope, &mut stack);
360 }
361 "class_definition" => push_children(node, None, &mut stack),
364 "identifier" => {
365 if !handle_annotation_reference(node, ctx) {
366 handle_identifier(node, ctx, scopes);
367 }
368 push_children(node, facts, &mut stack);
369 }
370 "attribute" => {
371 if handle_annotation_reference(node, ctx) {
372 continue;
373 }
374 let scope_facts = facts.and_then(|id| merged_facts.get(id));
375 handle_attribute(node, ctx, scopes, scope_facts);
376 push_children(node, facts, &mut stack);
377 }
378 "string_content" => {
379 handle_annotation_reference(node, ctx);
380 }
381 "keyword_argument" => {
382 handle_keyword_argument(node, ctx, scopes);
383 if let Some(value) = node.child_by_field_name("value") {
384 stack.push(WalkFrame::Enter { node: value, facts });
385 }
386 }
387 _ => push_children(node, facts, &mut stack),
388 },
389 WalkFrame::ExitScope => {
390 scopes.pop();
391 }
392 WalkFrame::EnterScope(scope) => scopes.push(scope),
393 }
394 }
395}
396
397enum WalkFrame<'tree> {
398 Enter {
399 node: Node<'tree>,
400 facts: Option<usize>,
401 },
402 EnterScope(FunctionScope),
403 ExitScope,
404}
405
406fn push_children<'tree>(
407 node: Node<'tree>,
408 facts: Option<usize>,
409 stack: &mut Vec<WalkFrame<'tree>>,
410) {
411 for index in (0..node.named_child_count()).rev() {
412 if let Some(child) = node.named_child(index) {
413 stack.push(WalkFrame::Enter { node: child, facts });
414 }
415 }
416}
417
418fn push_function_children<'tree>(
419 function: Node<'tree>,
420 enclosing_facts: Option<usize>,
421 body_facts: Option<usize>,
422 function_scope: FunctionScope,
423 stack: &mut Vec<WalkFrame<'tree>>,
424) {
425 let body = function.child_by_field_name("body");
426 let mut function_scope = Some(function_scope);
427 for index in (0..function.named_child_count()).rev() {
428 if let Some(child) = function.named_child(index) {
429 let facts = if body == Some(child) {
433 body_facts
434 } else {
435 enclosing_facts
436 };
437 if body == Some(child) {
438 stack.push(WalkFrame::ExitScope);
439 stack.push(WalkFrame::Enter { node: child, facts });
440 stack.push(WalkFrame::EnterScope(
441 function_scope
442 .take()
443 .expect("a function has exactly one body scope"),
444 ));
445 } else {
446 stack.push(WalkFrame::Enter { node: child, facts });
447 }
448 }
449 }
450}
451
452fn merged_enclosing_scope_facts(
453 graph: &PythonGraphSource<'_>,
454 file: &ProjectFile,
455 scope_facts: &HashMap<CodeUnit, LocalBindingsSnapshot<String>>,
456 merged_facts: &mut Vec<LocalBindingsSnapshot<String>>,
457 node: Node<'_>,
458 source: &str,
459 inherited: Option<usize>,
460) -> Option<usize> {
461 let structural_local = collect_function_scope_facts_from_node(node, source);
462 let local = if inherited.is_none() && node.kind() == "function_definition" {
467 enclosing_scope_facts(graph.index, file, scope_facts, node)
468 .cloned()
469 .unwrap_or(structural_local)
470 } else {
471 structural_local
472 };
473 match (local, inherited) {
474 (local, Some(inherited_id)) => {
475 let inherited = merged_facts.get(inherited_id)?;
476 let merged = inherited.merged_with_shadowing(&local);
477 let next_id = merged_facts.len();
478 merged_facts.push(merged);
479 Some(next_id)
480 }
481 (local, None) => {
482 let next_id = merged_facts.len();
483 merged_facts.push(local);
484 Some(next_id)
485 }
486 }
487}
488
489#[derive(Default)]
490struct FunctionScope {
491 locals: HashSet<String>,
492 parameters: HashSet<String>,
493}
494
495fn is_shadowed(scopes: &[FunctionScope], name: &str) -> bool {
496 scopes.iter().any(|scope| scope.locals.contains(name))
497}
498
499fn is_receiver_parameter(scopes: &[FunctionScope], name: &str) -> bool {
500 scopes
501 .iter()
502 .rev()
503 .any(|scope| scope.parameters.contains(name))
504}
505
506fn handle_identifier(node: Node<'_>, ctx: &mut PyScan<'_>, scopes: &[FunctionScope]) {
507 if node
509 .parent()
510 .is_some_and(|parent| parent.kind() == "attribute")
511 {
512 return;
513 }
514 if is_declaration_identifier(node) {
515 return;
516 }
517 let text = slice(node, ctx.source);
518 if text.is_empty() || is_shadowed(scopes, text) {
519 return;
520 }
521 if let Some(callee) = ctx.bare_callee(text) {
522 ctx.record(callee, node);
523 }
524}
525
526fn handle_annotation_reference(node: Node<'_>, ctx: &mut PyScan<'_>) -> bool {
527 let Some(candidates) =
528 annotation_reference_candidates(ctx.graph, ctx.python, ctx.file, ctx.source, node, false)
529 else {
530 return false;
531 };
532 let [candidate] = candidates.as_slice() else {
533 return !(node.kind() == "attribute" && candidates.is_empty());
534 };
535
536 let site = if node.kind() == "attribute" {
537 node.child_by_field_name("attribute").unwrap_or(node)
538 } else {
539 node
540 };
541 ctx.record(candidate.fq_name(), site);
542 true
543}
544
545fn handle_attribute(
546 node: Node<'_>,
547 ctx: &mut PyScan<'_>,
548 scopes: &[FunctionScope],
549 facts: Option<&LocalBindingsSnapshot<String>>,
550) {
551 let (Some(object), Some(attribute)) = (
552 node.child_by_field_name("object"),
553 node.child_by_field_name("attribute"),
554 ) else {
555 return;
556 };
557 let object_text = slice(object, ctx.source);
558 let attribute_text = slice(attribute, ctx.source);
559 if object_text.is_empty() || attribute_text.is_empty() {
560 return;
561 }
562 if object.kind() == "call" && ctx.targets_by_terminal.contains_key(attribute_text) {
563 for class in call_result_types(ctx.graph, ctx.python, ctx.file, ctx.source, object, facts) {
564 let direct = format!("{}.{attribute_text}", class.fq_name());
565 if ctx.targets.contains(&direct) {
566 ctx.record(direct, attribute);
567 continue;
568 }
569 if let Some(provider) = ctx.graph.hierarchy {
570 for ancestor in provider.get_ancestors(&class) {
571 let inherited = format!("{}.{attribute_text}", ancestor.fq_name());
572 if ctx.targets.contains(&inherited) {
573 ctx.record(inherited, attribute);
574 }
575 }
576 }
577 }
578 }
579 if let Some((root, attributes)) = attribute_chain(node) {
583 let root_text = slice(root, ctx.source);
584 if !root_text.is_empty()
585 && !is_shadowed(scopes, root_text)
586 && let Some(binding) = ctx.namespace.get(root_text)
587 {
588 let mut direct = binding.module.clone();
589 let workspace_module = binding.workspace_module;
590 let consumed_attributes = binding.consumed_attributes;
591 if object.kind() == "identifier" && ctx.targets.contains(&direct) {
592 ctx.record(direct.clone(), object);
593 }
594 for member in attributes.into_iter().skip(consumed_attributes) {
595 let member_text = slice(member, ctx.source);
596 if member_text.is_empty() {
597 return;
598 }
599 direct.push('.');
600 direct.push_str(member_text);
601 }
602 if ctx.targets.contains(&direct) {
603 ctx.record(direct, attribute);
604 return;
605 }
606 if workspace_module {
612 for resolved in ctx.canonical_namespace_candidates(&direct).iter() {
613 ctx.record(resolved.clone(), attribute);
614 }
615 }
616 return;
617 }
618 }
619
620 if let Some(facts) = facts
626 && ctx.targets_by_terminal.contains_key(attribute_text)
627 {
628 if matches!(object_text, "self" | "cls") {
629 ctx.record_unproven_name(attribute_text, attribute);
635 } else if let Some(type_fqn) = ctx.receiver_type_fqn(facts, object_text) {
636 ctx.record(format!("{type_fqn}.{attribute_text}"), attribute);
637 } else if object.kind() == "identifier" && !ctx.named.contains_key(object_text) {
638 let resolution = facts.resolution_for(object_text);
639 if resolution.is_ambiguous()
640 || (resolution.is_unknown() && is_receiver_parameter(scopes, object_text))
641 {
642 ctx.record_unproven_name(attribute_text, attribute);
643 }
644 }
645 }
646}
647
648fn handle_keyword_argument(node: Node<'_>, ctx: &mut PyScan<'_>, scopes: &[FunctionScope]) {
649 let (Some(name), Some(arguments)) = (node.child_by_field_name("name"), node.parent()) else {
650 return;
651 };
652 if name.kind() != "identifier" || arguments.kind() != "argument_list" {
653 return;
654 }
655 let Some(call) = arguments.parent().filter(|parent| parent.kind() == "call") else {
656 return;
657 };
658 let Some(function) = call.child_by_field_name("function") else {
659 return;
660 };
661 let member = slice(name, ctx.source);
662 if member.is_empty() || !ctx.targets_by_terminal.contains_key(member) {
663 return;
664 }
665 let scoped_class_fqn = if function.kind() == "identifier" {
666 enclosing_scope_facts(ctx.graph.index, ctx.file, ctx.scope_facts, function)
667 .and_then(|facts| ctx.receiver_type_fqn(facts, slice(function, ctx.source)))
668 } else {
669 None
670 };
671 let function_name = (function.kind() == "identifier").then(|| slice(function, ctx.source));
672 let mut default_classes = function_name.map_or_else(Vec::new, |local_name| {
673 resolve_callable_parameter_default_types(
674 ctx.graph, ctx.python, ctx.file, ctx.source, function, local_name,
675 )
676 });
677 let root_shadowed = leftmost_identifier(function)
678 .is_some_and(|root| is_shadowed(scopes, slice(root, ctx.source)));
679 let mut classes = if function_name == Some("cls") {
680 lexical_class(ctx, function).into_iter().collect()
681 } else {
682 if root_shadowed && scoped_class_fqn.is_none() && default_classes.is_empty() {
683 return;
684 }
685 if !root_shadowed {
686 default_classes.extend(resolve_constructor_types(
687 ctx.graph, ctx.python, ctx.file, ctx.source, function,
688 ));
689 }
690 default_classes
691 };
692 if let Some(fqn) = scoped_class_fqn {
693 classes.extend(ctx.graph.index.definitions(&fqn).filter(CodeUnit::is_class));
694 classes.sort();
695 classes.dedup();
696 }
697 for class in classes {
698 let direct = format!("{}.{member}", class.fq_name());
699 if ctx.targets.contains(&direct) {
700 ctx.record(direct, name);
701 continue;
702 }
703 if let Some(provider) = ctx.graph.hierarchy {
704 for ancestor in provider.get_ancestors(&class) {
705 let inherited = format!("{}.{member}", ancestor.fq_name());
706 if ctx.targets.contains(&inherited) {
707 ctx.record(inherited, name);
708 }
709 }
710 }
711 }
712}
713
714fn lexical_class(ctx: &PyScan<'_>, node: Node<'_>) -> Option<CodeUnit> {
715 let range = Range {
716 start_byte: node.start_byte(),
717 end_byte: node.end_byte(),
718 start_line: 0,
719 end_line: 0,
720 };
721 let enclosing = ctx.graph.index.enclosing_code_unit(ctx.file, &range)?;
722 if enclosing.is_class() {
723 Some(enclosing)
724 } else {
725 ctx.graph
726 .index
727 .parent_of(&enclosing)
728 .filter(CodeUnit::is_class)
729 }
730}
731
732fn leftmost_identifier(mut node: Node<'_>) -> Option<Node<'_>> {
733 loop {
734 match node.kind() {
735 "identifier" => return Some(node),
736 "attribute" => node = node.child_by_field_name("object")?,
737 _ => return None,
738 }
739 }
740}
741
742fn attribute_chain<'a>(node: Node<'a>) -> Option<(Node<'a>, Vec<Node<'a>>)> {
743 let mut attributes = Vec::new();
744 let mut current = node;
745 loop {
746 if current.kind() != "attribute" {
747 return None;
748 }
749 attributes.push(current.child_by_field_name("attribute")?);
750 current = current.child_by_field_name("object")?;
751 if current.kind() == "identifier" {
752 attributes.reverse();
753 return Some((current, attributes));
754 }
755 }
756}
757
758fn collect_function_scope(func: Node<'_>, source: &str) -> FunctionScope {
763 let mut scope = FunctionScope::default();
764 if let Some(params) = func.child_by_field_name("parameters") {
765 collect_parameter_names(params, source, &mut scope.parameters);
766 scope.locals.extend(scope.parameters.iter().cloned());
767 }
768 if let Some(body) = func.child_by_field_name("body") {
769 collect_bound_targets(body, source, &mut scope.locals);
770 }
771 scope
772}
773
774fn collect_parameter_names(params: Node<'_>, source: &str, out: &mut HashSet<String>) {
775 let mut cursor = params.walk();
776 for child in params.named_children(&mut cursor) {
777 let name = match child.kind() {
778 "identifier" => Some(child),
779 _ => child
782 .child_by_field_name("name")
783 .or_else(|| child.named_child(0).filter(|n| n.kind() == "identifier")),
784 };
785 if let Some(name) = name {
786 let text = slice(name, source).trim();
787 if !text.is_empty() {
788 out.insert(text.to_string());
789 }
790 }
791 }
792}
793
794fn collect_bound_targets(node: Node<'_>, source: &str, out: &mut HashSet<String>) {
798 let mut stack = vec![node];
799 while let Some(node) = stack.pop() {
800 match node.kind() {
801 "function_definition" | "class_definition" => {
802 if let Some(name) = node.child_by_field_name("name") {
803 let text = slice(name, source).trim();
804 if !text.is_empty() {
805 out.insert(text.to_string());
806 }
807 }
808 continue;
809 }
810 "lambda" => continue,
811 "assignment" | "augmented_assignment" | "for_statement" | "for_in_clause" => {
812 if let Some(left) = node.child_by_field_name("left") {
813 collect_assigned_identifiers(left, source, out);
814 }
815 }
816 "named_expression" => {
817 if let Some(name) = node.child_by_field_name("name") {
818 collect_assigned_identifiers(name, source, out);
819 }
820 }
821 _ => {}
822 }
823 let mut cursor = node.walk();
824 let mut children: Vec<Node<'_>> = node.named_children(&mut cursor).collect();
825 children.reverse();
826 stack.extend(children);
827 }
828}