1use crate::bindings::python_is_type_parameter_binder;
2use brokk_bifrost_core::analyzer::Range;
3use brokk_bifrost_core::analyzer::common::node_source_text as node_text;
4use brokk_bifrost_core::cancellation::CancellationToken;
5use brokk_bifrost_core::hash::HashSet;
6use tree_sitter::{Node, Tree};
7
8pub(crate) fn is_python_member_path(value: &str) -> bool {
11 static IDENTIFIER: std::sync::LazyLock<::regex::Regex> = std::sync::LazyLock::new(|| {
12 ::regex::Regex::new(r"\A[_\p{XID_Start}]\p{XID_Continue}*\z")
13 .expect("Python identifier value regex")
14 });
15 value.contains('.') && value.split('.').all(|part| IDENTIFIER.is_match(part))
16}
17
18pub(crate) fn python_method_owner(definition: Node<'_>) -> Option<Node<'_>> {
21 debug_assert_eq!(definition.kind(), "function_definition");
22 let mut current = definition;
23 while let Some(parent) = current.parent() {
24 match parent.kind() {
25 "class_definition" => {
26 return (parent.child_by_field_name("body") == Some(current)).then_some(parent);
27 }
28 "function_definition" | "lambda" | "module" => return None,
29 _ => current = parent,
30 }
31 }
32 None
33}
34
35pub fn python_conditional_expression_operands(node: Node<'_>) -> Option<[Node<'_>; 3]> {
43 assert_eq!(node.kind(), "conditional_expression");
44 let mut cursor = node.walk();
45 let operands: Vec<Node<'_>> = node
46 .named_children(&mut cursor)
47 .filter(|child| !child.is_extra())
48 .collect();
49 operands.try_into().ok()
50}
51
52pub(crate) fn python_parameter_name(node: Node<'_>, source: &str) -> Option<String> {
53 python_parameter_name_bounded(node, source, || true)
54 .expect("unbounded parameter name lookup completes")
55}
56
57pub(crate) fn python_parameter_name_bounded(
58 node: Node<'_>,
59 source: &str,
60 scope_step: impl FnMut() -> bool,
61) -> Option<Option<String>> {
62 python_parameter_node_bounded(node, scope_step).map(|node| {
63 node.and_then(|node| {
64 let name = node_text(node, source).trim();
65 (!name.is_empty()).then(|| name.to_owned())
66 })
67 })
68}
69
70pub(crate) fn python_parameter_node(node: Node<'_>) -> Option<Node<'_>> {
72 python_parameter_node_bounded(node, || true).expect("unbounded parameter token lookup")
73}
74
75fn python_parameter_node_bounded(
76 mut node: Node<'_>,
77 mut scope_step: impl FnMut() -> bool,
78) -> Option<Option<Node<'_>>> {
79 loop {
80 if !scope_step() {
81 return None;
82 }
83 match node.kind() {
84 "identifier" => return Some((!node.is_missing()).then_some(node)),
85 "typed_parameter"
86 | "typed_default_parameter"
87 | "default_parameter"
88 | "list_splat_pattern"
89 | "dictionary_splat_pattern" => {
90 if let Some(name) = node.child_by_field_name("name") {
91 node = name;
92 continue;
93 }
94 let mut selected = None;
95 let mut cursor = node.walk();
96 for child in node.named_children(&mut cursor) {
97 if !scope_step() {
98 return None;
99 }
100 if matches!(
101 child.kind(),
102 "identifier" | "list_splat_pattern" | "dictionary_splat_pattern"
103 ) {
104 selected = Some(child);
105 break;
106 }
107 }
108 let Some(name) = selected else {
109 return Some(None);
110 };
111 node = name;
112 }
113 _ => return Some(None),
114 }
115 }
116}
117
118pub fn leftmost_identifier(mut node: Node<'_>) -> Option<Node<'_>> {
119 loop {
120 match node.kind() {
121 "identifier" => return Some(node),
122 "attribute" => node = node.child_by_field_name("object")?,
123 _ => return None,
124 }
125 }
126}
127
128pub(crate) fn attribute_chain<'tree>(node: Node<'tree>) -> Option<(Node<'tree>, Vec<Node<'tree>>)> {
129 let mut attributes = Vec::new();
130 let mut current = node;
131 loop {
132 if current.kind() != "attribute" {
133 return None;
134 }
135 attributes.push(current.child_by_field_name("attribute")?);
136 current = current.child_by_field_name("object")?;
137 if current.kind() == "identifier" {
138 attributes.reverse();
139 return Some((current, attributes));
140 }
141 }
142}
143
144pub fn python_reference_is_deferred_function_body(node: Node<'_>) -> bool {
145 python_reference_is_deferred_function_body_bounded(node, || true)
146 .expect("unbounded deferred-context lookup completes")
147}
148
149pub fn python_reference_is_deferred_function_body_bounded(
151 node: Node<'_>,
152 mut scope_step: impl FnMut() -> bool,
153) -> Option<bool> {
154 let site_start = node.start_byte();
155 let site_end = node.end_byte();
156 let mut current = node;
157 while let Some(parent) = current.parent() {
158 if !scope_step() {
159 return None;
160 }
161 if matches!(parent.kind(), "function_definition" | "lambda")
162 && parent
163 .child_by_field_name("body")
164 .is_some_and(|body| body.start_byte() <= site_start && site_end <= body.end_byte())
165 {
166 return Some(true);
167 }
168 current = parent;
169 }
170 Some(false)
171}
172
173pub fn python_static_attribute_path<'tree>(node: Node<'tree>) -> Option<Vec<Node<'tree>>> {
179 python_static_value_path_with(node, || true, |_| None)
180 .expect("unbounded static path lookup completes")
181}
182
183pub fn python_static_attribute_path_bounded<'tree>(
186 node: Node<'tree>,
187 scope_step: impl FnMut() -> bool,
188) -> Option<Vec<Node<'tree>>> {
189 python_static_value_path_with(node, scope_step, |_| None).flatten()
190}
191
192pub fn python_static_value_path_bounded<'tree>(
195 node: Node<'tree>,
196 scope_step: impl FnMut() -> bool,
197) -> Option<Option<Vec<Node<'tree>>>> {
198 python_static_value_path_with(
199 node,
200 scope_step,
201 crate::structural::python_parenthesized_expression_child,
202 )
203}
204
205fn python_static_value_path_with<'tree>(
206 mut node: Node<'tree>,
207 mut scope_step: impl FnMut() -> bool,
208 mut parenthesized_child: impl FnMut(Node<'tree>) -> Option<Node<'tree>>,
209) -> Option<Option<Vec<Node<'tree>>>> {
210 let mut path = Vec::new();
211 loop {
212 if !scope_step() {
213 return None;
214 }
215 match node.kind() {
216 "identifier" => {
217 path.push(node);
218 break;
219 }
220 "attribute" => {
221 let Some(object) = push_static_attribute_segment(node, &mut path) else {
222 return Some(None);
223 };
224 node = object;
225 }
226 "parenthesized_expression" => {
227 for _ in brokk_bifrost_core::analyzer::tree_walk::named_children_iter(node) {
230 if !scope_step() {
231 return None;
232 }
233 }
234 let Some(child) = parenthesized_child(node) else {
235 return Some(None);
236 };
237 node = child;
238 }
239 _ => return Some(None),
240 }
241 }
242 for _ in 0..path.len() {
243 if !scope_step() {
244 return None;
245 }
246 }
247 path.reverse();
248 Some(Some(path))
249}
250
251pub fn python_static_type_path<'tree>(mut node: Node<'tree>) -> Option<Vec<Node<'tree>>> {
255 let mut path = Vec::new();
256 loop {
257 match node.kind() {
258 "identifier" => {
259 path.push(node);
260 break;
261 }
262 "type" | "generic_type" | "subscript" => node = node.named_child(0)?,
263 "attribute" => {
264 node = push_static_attribute_segment(node, &mut path)?;
265 }
266 "member_type" => {
267 let mut cursor = node.walk();
268 let mut children = node.named_children(&mut cursor);
269 let qualifier = children.next()?;
270 let member = children.next()?;
271 if member.kind() != "identifier" || children.next().is_some() {
272 return None;
273 }
274 path.push(member);
275 node = qualifier;
276 }
277 _ => return None,
278 }
279 }
280 path.reverse();
281 Some(path)
282}
283
284fn push_static_attribute_segment<'tree>(
285 node: Node<'tree>,
286 path: &mut Vec<Node<'tree>>,
287) -> Option<Node<'tree>> {
288 let attribute = node.child_by_field_name("attribute")?;
289 if attribute.kind() != "identifier" {
290 return None;
291 }
292 path.push(attribute);
293 node.child_by_field_name("object")
294}
295
296pub fn python_plain_string_literal<'source>(
302 node: Node<'_>,
303 source: &'source str,
304) -> Option<&'source str> {
305 if node.kind() != "string"
306 || node
307 .parent()
308 .is_some_and(|parent| parent.kind() == "concatenated_string")
309 {
310 return None;
311 }
312 let mut content = None;
313 let mut cursor = node.walk();
314 for child in node.named_children(&mut cursor) {
315 match child.kind() {
316 "string_start" | "string_end" => {
317 let delimiter = child.utf8_text(source.as_bytes()).ok()?;
318 if delimiter
319 .chars()
320 .any(|character| character != '"' && character != '\'')
321 {
322 return None;
323 }
324 }
325 "string_content" if child.named_child_count() == 0 && content.is_none() => {
326 content = Some(child);
327 }
328 _ => return None,
329 }
330 }
331 Some(content.map_or("", |child| {
332 child
333 .utf8_text(source.as_bytes())
334 .expect("a tree-sitter node range is valid UTF-8 source")
335 }))
336}
337
338#[derive(Debug, Default)]
339pub struct PythonOverloadDecoratorBindings {
340 direct: HashSet<String>,
341 namespaces: HashSet<String>,
342}
343
344#[derive(Debug)]
345pub(crate) enum PythonOverloadDecoratorName {
346 Direct(String),
347 Namespace(String),
348}
349
350impl PythonOverloadDecoratorBindings {
351 pub(crate) fn collect_import(
354 &mut self,
355 import: &brokk_bifrost_core::analyzer::model::ImportInfo,
356 ) {
357 use brokk_bifrost_core::analyzer::model::StructuredImportPathKind;
358 let Some(path) = &import.path else { return };
359 match (path.kind, path.segments.as_slice()) {
360 (Some(StructuredImportPathKind::Namespace), [module]) if is_typing_module(module) => {
361 self.namespaces
362 .insert(import.alias.as_ref().unwrap_or(module).clone());
363 }
364 (Some(StructuredImportPathKind::ImportFrom), [module, name])
365 if is_typing_module(module) && name == "overload" =>
366 {
367 self.direct
368 .insert(import.alias.as_ref().unwrap_or(name).clone());
369 }
370 _ => {}
371 }
372 }
373
374 pub(crate) fn overload_decorator_names(
379 function: Node<'_>,
380 source: &str,
381 ) -> Vec<PythonOverloadDecoratorName> {
382 let Some(parent) = function
383 .parent()
384 .filter(|node| node.kind() == "decorated_definition")
385 else {
386 return Vec::new();
387 };
388 let mut cursor = parent.walk();
389 parent
390 .named_children(&mut cursor)
391 .filter(|child| child.kind() == "decorator")
392 .filter_map(decorator_callee)
393 .filter_map(|callee| match callee.kind() {
394 "identifier" => Some(PythonOverloadDecoratorName::Direct(
395 node_text(callee, source).trim().to_string(),
396 )),
397 "attribute" => {
398 let attribute = callee.child_by_field_name("attribute")?;
399 if node_text(attribute, source).trim() != "overload" {
400 return None;
401 }
402 let object = callee.child_by_field_name("object")?;
403 (object.kind() == "identifier").then(|| {
404 PythonOverloadDecoratorName::Namespace(
405 node_text(object, source).trim().to_string(),
406 )
407 })
408 }
409 _ => None,
410 })
411 .collect()
412 }
413
414 pub(crate) fn matches_overload_binding(&self, name: &PythonOverloadDecoratorName) -> bool {
415 match name {
416 PythonOverloadDecoratorName::Direct(name) => self.direct.contains(name),
417 PythonOverloadDecoratorName::Namespace(name) => self.namespaces.contains(name),
418 }
419 }
420}
421
422fn is_typing_module(module: &str) -> bool {
423 matches!(module, "typing" | "typing_extensions")
424}
425
426pub fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
428 let mut current = expression;
429 loop {
430 match current.kind() {
431 "identifier" => return Some(current),
432 "attribute" => current = current.child_by_field_name("attribute")?,
433 "call" => current = current.child_by_field_name("function")?,
434 _ => return None,
435 }
436 }
437}
438
439pub fn python_keyword_argument_label(node: Node<'_>) -> bool {
447 node.kind() == "identifier"
448 && node.parent().is_some_and(|parent| {
449 parent.kind() == "keyword_argument" && parent.child_by_field_name("name") == Some(node)
450 })
451}
452
453pub fn decorator_callee<'tree>(decorator: Node<'tree>) -> Option<Node<'tree>> {
455 if decorator.kind() != "decorator" {
456 return None;
457 }
458 let mut cursor = decorator.walk();
461 let mut expression = decorator
462 .named_children(&mut cursor)
463 .find(|child| !child.is_extra())?;
464 while expression.kind() == "call" {
465 expression = expression.child_by_field_name("function")?;
466 }
467 Some(expression)
468}
469
470pub fn python_node_can_be_reference(node: Node<'_>) -> bool {
474 matches!(node.kind(), "identifier" | "keyword_identifier")
475 || (node.kind() == "string_content" && python_node_is_in_annotation(node))
476}
477
478pub fn python_node_is_in_annotation(node: Node<'_>) -> bool {
481 let start = node.start_byte();
482 let end = node.end_byte();
483 let mut current = node;
484 while let Some(parent) = current.parent() {
485 let annotation = match parent.kind() {
486 "function_definition" => parent.child_by_field_name("return_type"),
487 "typed_parameter" | "typed_default_parameter" | "assignment" => {
488 parent.child_by_field_name("type")
489 }
490 _ => None,
491 };
492 if let Some(annotation) = annotation
493 && annotation.start_byte() <= start
494 && end <= annotation.end_byte()
495 {
496 return true;
497 }
498 current = parent;
499 }
500 false
501}
502
503pub fn python_deferred_annotation_identifier_ranges(
505 string: Node<'_>,
506 source: &str,
507 cancellation: Option<&CancellationToken>,
508) -> Option<Vec<Range>> {
509 let tree = python_deferred_annotation_tree(string, source, cancellation)?;
510
511 let mut ranges = Vec::new();
512 let mut stack = vec![tree.root_node()];
513 while let Some(current) = stack.pop() {
514 if cancellation.is_some_and(CancellationToken::is_cancelled) {
515 return None;
516 }
517 if current.kind() == "identifier" {
518 ranges.push(Range {
519 start_byte: current.start_byte(),
520 end_byte: current.end_byte(),
521 start_line: current.start_position().row + 1,
522 end_line: current.end_position().row + 1,
523 });
524 }
525 for index in (0..current.named_child_count()).rev() {
526 if let Some(child) = current.named_child(index) {
527 stack.push(child);
528 }
529 }
530 }
531 Some(ranges)
532}
533
534pub fn python_deferred_annotation_tree(
538 string: Node<'_>,
539 source: &str,
540 cancellation: Option<&CancellationToken>,
541) -> Option<Tree> {
542 if string.kind() != "string"
543 || string
544 .parent()
545 .is_some_and(|parent| parent.kind() == "concatenated_string")
546 || !python_node_is_in_annotation(string)
547 || python_string_is_literal_value(string, source)
548 {
549 return None;
550 }
551
552 let mut content = None;
553 for index in 0..string.named_child_count() {
554 let child = string.named_child(index)?;
555 match child.kind() {
556 "string_start" | "string_end" => {}
557 "string_content" if content.is_none() => content = Some(child),
558 _ => return None,
559 }
560 }
561 let content = content?;
562 let language = tree_sitter_python::LANGUAGE.into();
563 let tree = brokk_bifrost_core::analyzer::common::parse_source_range_with_cancellation(
564 &language,
565 source,
566 content.range(),
567 cancellation,
568 )?;
569 if tree.root_node().has_error() {
570 return None;
571 }
572 Some(tree)
573}
574
575fn python_string_is_literal_value(string: Node<'_>, source: &str) -> bool {
579 let start = string.start_byte();
580 let end = string.end_byte();
581 let mut current = string;
582 while let Some(parent) = current.parent() {
583 match parent.kind() {
584 "subscript" => {
585 let Some(value) = parent.child_by_field_name("value") else {
586 return false;
587 };
588 if value.start_byte() <= start && end <= value.end_byte() {
589 return false;
590 }
591 return python_literal_annotation_base(value, source);
592 }
593 "generic_type" => {
594 let Some(value) = parent.named_child(0) else {
595 return false;
596 };
597 return python_literal_annotation_base(value, source);
598 }
599 _ => current = parent,
600 }
601 }
602 false
603}
604
605fn python_literal_annotation_base(value: Node<'_>, source: &str) -> bool {
606 match value.kind() {
607 "identifier" => node_text(value, source) == "Literal",
608 "attribute" => {
609 let (Some(object), Some(attribute)) = (
610 value.child_by_field_name("object"),
611 value.child_by_field_name("attribute"),
612 ) else {
613 return false;
614 };
615 object.kind() == "identifier"
616 && matches!(node_text(object, source), "typing" | "typing_extensions")
617 && attribute.kind() == "identifier"
618 && node_text(attribute, source) == "Literal"
619 }
620 "member_type" => {
621 let mut identifiers = Vec::new();
622 let mut stack = vec![value];
623 while let Some(node) = stack.pop() {
624 if node.kind() == "identifier" {
625 identifiers.push(node_text(node, source));
626 continue;
627 }
628 for index in (0..node.named_child_count()).rev() {
629 if let Some(child) = node.named_child(index) {
630 stack.push(child);
631 }
632 }
633 }
634 matches!(
635 identifiers.as_slice(),
636 ["typing", "Literal"] | ["typing_extensions", "Literal"]
637 )
638 }
639 _ => false,
640 }
641}
642
643pub(crate) fn python_contains_all_identifier(node: Node<'_>, source: &str) -> bool {
646 use brokk_bifrost_core::analyzer::tree_walk::{WalkControl, walk_named_tree_preorder};
647 let mut found = false;
648 walk_named_tree_preorder(node, true, |node| {
649 if node.kind() == "identifier" && &source[node.byte_range()] == "__all__" {
650 found = true;
651 WalkControl::Break
652 } else {
653 WalkControl::Continue
654 }
655 });
656 found
657}
658
659pub fn is_declaration_identifier(node: Node<'_>) -> bool {
660 let contains = |container: Node<'_>| {
661 container.start_byte() <= node.start_byte() && node.end_byte() <= container.end_byte()
662 };
663 let mut current = node;
664 while let Some(parent) = current.parent() {
665 match parent.kind() {
666 "class_definition" | "function_definition" => {
667 return parent
668 .child_by_field_name("name")
669 .is_some_and(|name| name.id() == node.id())
670 || python_is_type_parameter_binder(node);
671 }
672 "parameters"
673 | "lambda_parameters"
674 | "list_splat_pattern"
675 | "dictionary_splat_pattern" => return true,
676 "default_parameter" | "typed_parameter" | "typed_default_parameter" => {
677 return parent.child_by_field_name("name").is_some_and(contains);
678 }
679 "assignment" | "augmented_assignment" | "for_statement" | "for_in_clause" => {
680 return parent.child_by_field_name("left").is_some_and(contains);
681 }
682 "named_expression" => {
683 return parent.child_by_field_name("name").is_some_and(contains);
684 }
685 "aliased_import" | "import_from_statement" | "import_statement" => return true,
686 "pattern_list"
687 | "tuple_pattern"
688 | "list_pattern"
689 | "parenthesized_expression"
690 | "type"
691 | "type_parameter"
692 | "constrained_type" => current = parent,
693 _ => return false,
694 }
695 }
696 false
697}
698
699pub fn python_attribute_callee_uses_self_or_cls_bounded(source: &str, callee: Node<'_>) -> bool {
701 let Some(object) = callee.child_by_field_name("object") else {
702 return false;
703 };
704 object.kind() == "identifier" && matches!(node_text(object, source), "self" | "cls")
705}
706
707pub fn python_attribute_callee_names_enclosing_nested_class_bounded(
709 source: &str,
710 callee: Node<'_>,
711) -> bool {
712 let Some(attribute) = callee.child_by_field_name("attribute") else {
713 return false;
714 };
715 let name = node_text(attribute, source);
716 let mut ancestor = callee.parent();
717 while let Some(node) = ancestor {
718 if node.kind() == "class_definition" {
719 let Some(body) = node.child_by_field_name("body") else {
720 return false;
721 };
722 let mut cursor = body.walk();
723 return body.named_children(&mut cursor).any(|member| {
724 member.kind() == "class_definition"
725 && member
726 .child_by_field_name("name")
727 .is_some_and(|class_name| node_text(class_name, source) == name)
728 });
729 }
730 ancestor = node.parent();
731 }
732 false
733}
734
735pub fn python_attribute_names_nested_class_through_current_receiver_bounded(
739 source: &str,
740 expression: Node<'_>,
741) -> bool {
742 expression.kind() == "attribute"
743 && python_attribute_callee_uses_self_or_cls_bounded(source, expression)
744 && python_attribute_callee_names_enclosing_nested_class_bounded(source, expression)
745}
746
747#[cfg(test)]
748mod tests {
749 use brokk_bifrost_test_macros::hardwon;
750
751 #[test]
752 fn qs6_python_syntax_field_boundary() {
753 use super::*;
754 for (source, expected) in [
755 ("sys.argv[1]", Some("sys.argv")),
756 ("obj.member[key]", Some("obj.member")),
757 ("obj.nested.member[key]", None),
758 ("values[key]", None),
759 ("obj.member", None),
760 ] {
761 let tree = crate::declarations::parse_python_tree(source).unwrap();
762 assert!(!tree.root_node().has_error());
763 let expression = tree
764 .root_node()
765 .named_child(0)
766 .unwrap()
767 .named_child(0)
768 .unwrap();
769 let seed = crate::runtime_values::python_runtime_keyed_access_seed(expression);
770 assert_eq!(seed.map(|node| &source[node.byte_range()]), expected);
771 if let Some(seed) = seed {
772 assert_eq!(seed.kind(), "attribute");
773 assert_eq!(
774 expression.child_by_field_name("value").unwrap().id(),
775 seed.id()
776 );
777 assert_eq!(seed.child_by_field_name("object").unwrap().start_byte(), 0);
778 assert_eq!(seed.start_byte(), 0);
779 assert_eq!(seed.end_byte(), expected.unwrap().len());
780 }
781 }
782 for (annotation, names) in [
783 ("pkg.Type", vec!["pkg", "Type"]),
784 ("pkg.Outer.Inner[int]", vec!["pkg", "Outer", "Inner"]),
785 ("Outer[T].Inner", vec!["Outer", "Inner"]),
786 ] {
787 let source = format!("def f() -> {annotation}: pass\n");
788 let tree = crate::declarations::parse_python_tree(&source).unwrap();
789 assert!(!tree.root_node().has_error());
790 let annotation_node = tree
791 .root_node()
792 .named_child(0)
793 .unwrap()
794 .child_by_field_name("return_type")
795 .unwrap();
796 let path = python_static_type_path(annotation_node).unwrap();
797 assert_eq!(
798 path.iter()
799 .map(|node| &source[node.byte_range()])
800 .collect::<Vec<_>>(),
801 names
802 );
803 for node in path {
804 assert_eq!(node.kind(), "identifier");
805 assert!(node.start_byte() >= annotation_node.start_byte());
806 assert!(node.end_byte() <= annotation_node.end_byte());
807 }
808 }
809 let source = "@factory()()\ndef f(): pass\n";
810 let tree = crate::declarations::parse_python_tree(source).unwrap();
811 let decorator = tree
812 .root_node()
813 .named_child(0)
814 .unwrap()
815 .named_child(0)
816 .unwrap();
817 assert_eq!(decorator.kind(), "decorator");
818 let call = decorator.named_child(0).unwrap();
819 assert_eq!(call.kind(), "call");
820 assert_eq!(call.child_by_field_name("function").unwrap().kind(), "call");
821 let callee = decorator_callee(decorator).unwrap();
822 assert_eq!(callee.kind(), "identifier");
823 assert_eq!(callee.byte_range(), 1..8);
824 assert_eq!(&source[callee.byte_range()], "factory");
825 }
826
827 #[test]
828 fn qs6_deferred_annotation_live_token_ranges() {
829 use super::*;
830 let source = "# \u{e9}\ndef f(x: \"pkg.Type[Other]\") -> \"Result\": pass\nordinary = \"text\"\ny: Literal[\"value\"]\n";
831 let tree = crate::declarations::parse_python_tree(source).unwrap();
832 assert!(!tree.root_node().has_error());
833 let fresh = CancellationToken::new();
834 let cancelled = CancellationToken::new();
835 cancelled.cancel();
836 let mut pending = vec![tree.root_node()];
837 let mut strings = Vec::new();
838 while let Some(node) = pending.pop() {
839 if node.kind() == "string" {
840 strings.push(node);
841 }
842 for index in (0..node.named_child_count()).rev() {
843 pending.push(node.named_child(index).unwrap());
844 }
845 }
846 assert_eq!(strings.len(), 4);
847 let mut names = Vec::new();
848 for string in strings {
849 let no_token = python_deferred_annotation_identifier_ranges(string, source, None);
850 assert_eq!(
851 python_deferred_annotation_identifier_ranges(string, source, Some(&fresh)),
852 no_token
853 );
854 assert!(
855 python_deferred_annotation_identifier_ranges(string, source, Some(&cancelled))
856 .is_none()
857 );
858 if let Some(ranges) = no_token {
859 for range in ranges {
860 assert!(range.start_byte > string.start_byte());
861 assert!(range.end_byte < string.end_byte());
862 assert_eq!(range.start_line, 2);
863 assert_eq!(range.end_line, 2);
864 names.push(&source[range.start_byte..range.end_byte]);
865 }
866 }
867 }
868 assert_eq!(names, ["pkg", "Type", "Other", "Result"]);
869 }
870
871 #[test]
872 #[hardwon("#5044: commented parentheses must preserve import identity and budget exhaustion")]
873 fn static_paths_keep_parentheses_comments_and_exhaustion_distinct() {
874 let source = "( # receiver\n module).function\n";
875 let tree = crate::declarations::parse_python_tree(source).unwrap();
876 let expression = tree
877 .root_node()
878 .named_child(0)
879 .unwrap()
880 .named_child(0)
881 .unwrap();
882 assert!(super::python_static_attribute_path(expression).is_none());
884 assert!(super::python_static_value_path_bounded(expression, || false).is_none());
885 let path = super::python_static_value_path_bounded(expression, || true)
886 .unwrap()
887 .unwrap();
888 assert_eq!(
889 path.iter()
890 .map(|node| node.utf8_text(source.as_bytes()).unwrap())
891 .collect::<Vec<_>>(),
892 ["module", "function"]
893 );
894 let dynamic = crate::declarations::parse_python_tree("factory().function\n").unwrap();
895 let expression = dynamic
896 .root_node()
897 .named_child(0)
898 .unwrap()
899 .named_child(0)
900 .unwrap();
901 assert!(matches!(
902 super::python_static_value_path_bounded(expression, || true),
903 Some(None)
904 ));
905 }
906}