Skip to main content

brokk_bifrost_python/
syntax.rs

1use brokk_bifrost_core::analyzer::Range;
2use brokk_bifrost_core::analyzer::common::node_source_text as node_text;
3use brokk_bifrost_core::cancellation::CancellationToken;
4use brokk_bifrost_core::hash::HashSet;
5use tree_sitter::{Node, Tree};
6
7/// The operands of a Python conditional expression (`value if test else
8/// alternative`).
9///
10/// The grammar leaves `conditional_expression` without fields, so its operands
11/// can only be selected positionally. Skipping extras is required: a backslash
12/// line continuation before `else` otherwise appears as a fourth named child
13/// and trips the operand-count check (#4797).
14pub(crate) fn python_conditional_expression_operands(node: Node<'_>) -> Option<[Node<'_>; 3]> {
15    assert_eq!(node.kind(), "conditional_expression");
16    let mut cursor = node.walk();
17    let operands: Vec<Node<'_>> = node
18        .named_children(&mut cursor)
19        .filter(|child| !child.is_extra())
20        .collect();
21    operands.try_into().ok()
22}
23
24pub(crate) fn python_parameter_name(node: Node<'_>, source: &str) -> Option<String> {
25    python_parameter_name_bounded(node, source, || true)
26        .expect("unbounded parameter name lookup completes")
27}
28
29pub(crate) fn python_parameter_name_bounded(
30    node: Node<'_>,
31    source: &str,
32    scope_step: impl FnMut() -> bool,
33) -> Option<Option<String>> {
34    python_parameter_node_bounded(node, scope_step).map(|node| {
35        node.and_then(|node| {
36            let name = node_text(node, source).trim();
37            (!name.is_empty()).then(|| name.to_owned())
38        })
39    })
40}
41
42/// The exact binder token, shared by signature and native lexical production.
43#[cfg(any(test, feature = "test-support"))]
44pub(crate) fn python_parameter_node(node: Node<'_>) -> Option<Node<'_>> {
45    python_parameter_node_bounded(node, || true).expect("unbounded parameter token lookup")
46}
47
48fn python_parameter_node_bounded(
49    mut node: Node<'_>,
50    mut scope_step: impl FnMut() -> bool,
51) -> Option<Option<Node<'_>>> {
52    loop {
53        if !scope_step() {
54            return None;
55        }
56        match node.kind() {
57            "identifier" => return Some((!node.is_missing()).then_some(node)),
58            "typed_parameter"
59            | "typed_default_parameter"
60            | "default_parameter"
61            | "list_splat_pattern"
62            | "dictionary_splat_pattern" => {
63                if let Some(name) = node.child_by_field_name("name") {
64                    node = name;
65                    continue;
66                }
67                let mut selected = None;
68                let mut cursor = node.walk();
69                for child in node.named_children(&mut cursor) {
70                    if !scope_step() {
71                        return None;
72                    }
73                    if matches!(
74                        child.kind(),
75                        "identifier" | "list_splat_pattern" | "dictionary_splat_pattern"
76                    ) {
77                        selected = Some(child);
78                        break;
79                    }
80                }
81                let Some(name) = selected else {
82                    return Some(None);
83                };
84                node = name;
85            }
86            _ => return Some(None),
87        }
88    }
89}
90
91pub fn leftmost_identifier(mut node: Node<'_>) -> Option<Node<'_>> {
92    loop {
93        match node.kind() {
94            "identifier" => return Some(node),
95            "attribute" => node = node.child_by_field_name("object")?,
96            _ => return None,
97        }
98    }
99}
100
101pub(crate) fn attribute_chain<'tree>(node: Node<'tree>) -> Option<(Node<'tree>, Vec<Node<'tree>>)> {
102    let mut attributes = Vec::new();
103    let mut current = node;
104    loop {
105        if current.kind() != "attribute" {
106            return None;
107        }
108        attributes.push(current.child_by_field_name("attribute")?);
109        current = current.child_by_field_name("object")?;
110        if current.kind() == "identifier" {
111            attributes.reverse();
112            return Some((current, attributes));
113        }
114    }
115}
116
117pub fn python_reference_is_deferred_function_body(node: Node<'_>) -> bool {
118    python_reference_is_deferred_function_body_bounded(node, || true)
119        .expect("unbounded deferred-context lookup completes")
120}
121
122/// Body containment under the caller's sticky traversal budget.
123pub fn python_reference_is_deferred_function_body_bounded(
124    node: Node<'_>,
125    mut scope_step: impl FnMut() -> bool,
126) -> Option<bool> {
127    let site_start = node.start_byte();
128    let site_end = node.end_byte();
129    let mut current = node;
130    while let Some(parent) = current.parent() {
131        if !scope_step() {
132            return None;
133        }
134        if matches!(parent.kind(), "function_definition" | "lambda")
135            && parent
136                .child_by_field_name("body")
137                .is_some_and(|body| body.start_byte() <= site_start && site_end <= body.end_byte())
138        {
139            return Some(true);
140        }
141        current = parent;
142    }
143    Some(false)
144}
145
146/// The identifier nodes in one static Python value path, from root to leaf.
147///
148/// Dynamic receivers and subscripts have no static path. Keeping the nodes
149/// lets callers combine the parser shape with lexical/import binding facts
150/// without reparsing a dotted source spelling.
151pub fn python_static_attribute_path<'tree>(node: Node<'tree>) -> Option<Vec<Node<'tree>>> {
152    python_static_value_path_with(node, || true, |_| None)
153        .expect("unbounded static path lookup completes")
154}
155
156/// The strict static attribute path proof, charging traversal and reversal.
157/// Parentheses remain outside this API's admitted grammar.
158pub fn python_static_attribute_path_bounded<'tree>(
159    node: Node<'tree>,
160    scope_step: impl FnMut() -> bool,
161) -> Option<Vec<Node<'tree>>> {
162    python_static_value_path_with(node, scope_step, |_| None).flatten()
163}
164
165/// A metered static value path, including transparent parentheses. The outer
166/// `None` means exhaustion; `Some(None)` means the receiver is not static.
167pub fn python_static_value_path_bounded<'tree>(
168    node: Node<'tree>,
169    scope_step: impl FnMut() -> bool,
170) -> Option<Option<Vec<Node<'tree>>>> {
171    python_static_value_path_with(
172        node,
173        scope_step,
174        crate::structural::python_parenthesized_expression_child,
175    )
176}
177
178fn python_static_value_path_with<'tree>(
179    mut node: Node<'tree>,
180    mut scope_step: impl FnMut() -> bool,
181    mut parenthesized_child: impl FnMut(Node<'tree>) -> Option<Node<'tree>>,
182) -> Option<Option<Vec<Node<'tree>>>> {
183    let mut path = Vec::new();
184    loop {
185        if !scope_step() {
186            return None;
187        }
188        match node.kind() {
189            "identifier" => {
190                path.push(node);
191                break;
192            }
193            "attribute" => {
194                let Some(object) = push_static_attribute_segment(node, &mut path) else {
195                    return Some(None);
196                };
197                node = object;
198            }
199            "parenthesized_expression" => {
200                // The shared grammar helper skips comments. Charge all of its
201                // immediate inputs before it inspects them.
202                for _ in brokk_bifrost_core::analyzer::tree_walk::named_children_iter(node) {
203                    if !scope_step() {
204                        return None;
205                    }
206                }
207                let Some(child) = parenthesized_child(node) else {
208                    return Some(None);
209                };
210                node = child;
211            }
212            _ => return Some(None),
213        }
214    }
215    for _ in 0..path.len() {
216        if !scope_step() {
217            return None;
218        }
219    }
220    path.reverse();
221    Some(Some(path))
222}
223
224/// The identifier nodes in one static Python annotation name, root to leaf.
225/// Wrappers and generic arguments are ignored while the named generic origin
226/// remains part of the path.
227pub fn python_static_type_path<'tree>(mut node: Node<'tree>) -> Option<Vec<Node<'tree>>> {
228    let mut path = Vec::new();
229    loop {
230        match node.kind() {
231            "identifier" => {
232                path.push(node);
233                break;
234            }
235            "type" | "generic_type" | "subscript" => node = node.named_child(0)?,
236            "attribute" => {
237                node = push_static_attribute_segment(node, &mut path)?;
238            }
239            "member_type" => {
240                let mut cursor = node.walk();
241                let mut children = node.named_children(&mut cursor);
242                let qualifier = children.next()?;
243                let member = children.next()?;
244                if member.kind() != "identifier" || children.next().is_some() {
245                    return None;
246                }
247                path.push(member);
248                node = qualifier;
249            }
250            _ => return None,
251        }
252    }
253    path.reverse();
254    Some(path)
255}
256
257fn push_static_attribute_segment<'tree>(
258    node: Node<'tree>,
259    path: &mut Vec<Node<'tree>>,
260) -> Option<Node<'tree>> {
261    let attribute = node.child_by_field_name("attribute")?;
262    if attribute.kind() != "identifier" {
263        return None;
264    }
265    path.push(attribute);
266    node.child_by_field_name("object")
267}
268
269/// The text one plain string literal denotes.
270///
271/// Prefixed strings, interpolations, escapes, implicit concatenations, and
272/// malformed shapes return `None`. The returned slice is always the exact
273/// `string_content` AST span, never text recovered by delimiter parsing.
274pub fn python_plain_string_literal<'source>(
275    node: Node<'_>,
276    source: &'source str,
277) -> Option<&'source str> {
278    if node.kind() != "string"
279        || node
280            .parent()
281            .is_some_and(|parent| parent.kind() == "concatenated_string")
282    {
283        return None;
284    }
285    let mut content = None;
286    let mut cursor = node.walk();
287    for child in node.named_children(&mut cursor) {
288        match child.kind() {
289            "string_start" | "string_end" => {
290                let delimiter = child.utf8_text(source.as_bytes()).ok()?;
291                if delimiter
292                    .chars()
293                    .any(|character| character != '"' && character != '\'')
294                {
295                    return None;
296                }
297            }
298            "string_content" if child.named_child_count() == 0 && content.is_none() => {
299                content = Some(child);
300            }
301            _ => return None,
302        }
303    }
304    Some(content.map_or("", |child| {
305        child
306            .utf8_text(source.as_bytes())
307            .expect("a tree-sitter node range is valid UTF-8 source")
308    }))
309}
310
311#[derive(Debug, Default)]
312pub struct PythonOverloadDecoratorBindings {
313    direct: HashSet<String>,
314    namespaces: HashSet<String>,
315}
316
317#[derive(Debug)]
318pub(crate) enum PythonOverloadDecoratorName {
319    Direct(String),
320    Namespace(String),
321}
322
323impl PythonOverloadDecoratorBindings {
324    /// Capture typing bindings from the import interpretation already owned by
325    /// the primary producer. Only module-level leaves reach this collector.
326    pub(crate) fn collect_import(
327        &mut self,
328        import: &brokk_bifrost_core::analyzer::model::ImportInfo,
329    ) {
330        use brokk_bifrost_core::analyzer::model::StructuredImportPathKind;
331        let Some(path) = &import.path else { return };
332        match (path.kind, path.segments.as_slice()) {
333            (Some(StructuredImportPathKind::Namespace), [module]) if is_typing_module(module) => {
334                self.namespaces
335                    .insert(import.alias.as_ref().unwrap_or(module).clone());
336            }
337            (Some(StructuredImportPathKind::ImportFrom), [module, name])
338                if is_typing_module(module) && name == "overload" =>
339            {
340                self.direct
341                    .insert(import.alias.as_ref().unwrap_or(name).clone());
342            }
343            _ => {}
344        }
345    }
346
347    /// Return the exact decorator binding shapes used by one function. The
348    /// names are captured during the primary walk; the import binding set is
349    /// resolved after the walk so a later typing import is handled exactly as
350    /// the historical whole-file predicate did.
351    pub(crate) fn overload_decorator_names(
352        function: Node<'_>,
353        source: &str,
354    ) -> Vec<PythonOverloadDecoratorName> {
355        let Some(parent) = function
356            .parent()
357            .filter(|node| node.kind() == "decorated_definition")
358        else {
359            return Vec::new();
360        };
361        let mut cursor = parent.walk();
362        parent
363            .named_children(&mut cursor)
364            .filter(|child| child.kind() == "decorator")
365            .filter_map(decorator_callee)
366            .filter_map(|callee| match callee.kind() {
367                "identifier" => Some(PythonOverloadDecoratorName::Direct(
368                    node_text(callee, source).trim().to_string(),
369                )),
370                "attribute" => {
371                    let attribute = callee.child_by_field_name("attribute")?;
372                    if node_text(attribute, source).trim() != "overload" {
373                        return None;
374                    }
375                    let object = callee.child_by_field_name("object")?;
376                    (object.kind() == "identifier").then(|| {
377                        PythonOverloadDecoratorName::Namespace(
378                            node_text(object, source).trim().to_string(),
379                        )
380                    })
381                }
382                _ => None,
383            })
384            .collect()
385    }
386
387    pub(crate) fn matches_overload_binding(&self, name: &PythonOverloadDecoratorName) -> bool {
388        match name {
389            PythonOverloadDecoratorName::Direct(name) => self.direct.contains(name),
390            PythonOverloadDecoratorName::Namespace(name) => self.namespaces.contains(name),
391        }
392    }
393}
394
395fn is_typing_module(module: &str) -> bool {
396    matches!(module, "typing" | "typing_extensions")
397}
398
399/// Return the name-bearing node of a Python expression using tree-sitter fields.
400pub fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
401    let mut current = expression;
402    loop {
403        match current.kind() {
404            "identifier" => return Some(current),
405            "attribute" => current = current.child_by_field_name("attribute")?,
406            "call" => current = current.child_by_field_name("function")?,
407            _ => return None,
408        }
409    }
410}
411
412/// Whether `node` is the label of a keyword argument: the `x` in `f(x=1)`.
413///
414/// The label names a parameter or member at the CALLEE, selected by the call's
415/// target, not by anything bound where the label is written. That is why the
416/// occurrence-role adapter in `structural.rs` classifies it `LabelOrKey`, whose
417/// occurrence class is `NonReference`, and it is the rule the reference census
418/// reads to decide that the label is not a forward-reference probe (#2054).
419pub fn python_keyword_argument_label(node: Node<'_>) -> bool {
420    node.kind() == "identifier"
421        && node.parent().is_some_and(|parent| {
422            parent.kind() == "keyword_argument" && parent.child_by_field_name("name") == Some(node)
423        })
424}
425
426/// Return a decorator's callable expression, peeling an optional invocation.
427pub fn decorator_callee<'tree>(decorator: Node<'tree>) -> Option<Node<'tree>> {
428    if decorator.kind() != "decorator" {
429        return None;
430    }
431    // The grammar can interleave a `line_continuation` extra before the
432    // decorated expression; skip extras so `@\`-continued names resolve.
433    let mut cursor = decorator.walk();
434    let mut expression = decorator
435        .named_children(&mut cursor)
436        .find(|child| !child.is_extra())?;
437    while expression.kind() == "call" {
438        expression = expression.child_by_field_name("function")?;
439    }
440    Some(expression)
441}
442
443/// Whether `node` is contained by a parser field that Python evaluates as an
444/// annotation rather than as an ordinary expression.
445pub fn python_node_is_in_annotation(node: Node<'_>) -> bool {
446    let start = node.start_byte();
447    let end = node.end_byte();
448    let mut current = node;
449    while let Some(parent) = current.parent() {
450        let annotation = match parent.kind() {
451            "function_definition" => parent.child_by_field_name("return_type"),
452            "typed_parameter" | "typed_default_parameter" | "assignment" => {
453                parent.child_by_field_name("type")
454            }
455            _ => None,
456        };
457        if let Some(annotation) = annotation
458            && annotation.start_byte() <= start
459            && end <= annotation.end_byte()
460        {
461            return true;
462        }
463        current = parent;
464    }
465    false
466}
467
468/// Parse one exactly mapped deferred annotation and return its identifier ranges.
469pub fn python_deferred_annotation_identifier_ranges(
470    string: Node<'_>,
471    source: &str,
472    cancellation: Option<&CancellationToken>,
473) -> Option<Vec<Range>> {
474    let tree = python_deferred_annotation_tree(string, source, cancellation)?;
475
476    let mut ranges = Vec::new();
477    let mut stack = vec![tree.root_node()];
478    while let Some(current) = stack.pop() {
479        if cancellation.is_some_and(CancellationToken::is_cancelled) {
480            return None;
481        }
482        if current.kind() == "identifier" {
483            ranges.push(Range {
484                start_byte: current.start_byte(),
485                end_byte: current.end_byte(),
486                start_line: current.start_position().row + 1,
487                end_line: current.end_position().row + 1,
488            });
489        }
490        for index in (0..current.named_child_count()).rev() {
491            if let Some(child) = current.named_child(index) {
492                stack.push(child);
493            }
494        }
495    }
496    Some(ranges)
497}
498
499/// Parse one quoted annotation expression while preserving its original source
500/// byte coordinates. Literal string values and arbitrary strings are rejected
501/// by the same structured gate used by inverse membership.
502pub fn python_deferred_annotation_tree(
503    string: Node<'_>,
504    source: &str,
505    cancellation: Option<&CancellationToken>,
506) -> Option<Tree> {
507    if string.kind() != "string"
508        || string
509            .parent()
510            .is_some_and(|parent| parent.kind() == "concatenated_string")
511        || !python_node_is_in_annotation(string)
512        || python_string_is_literal_value(string, source)
513    {
514        return None;
515    }
516
517    let mut content = None;
518    for index in 0..string.named_child_count() {
519        let child = string.named_child(index)?;
520        match child.kind() {
521            "string_start" | "string_end" => {}
522            "string_content" if content.is_none() => content = Some(child),
523            _ => return None,
524        }
525    }
526    let content = content?;
527    let language = tree_sitter_python::LANGUAGE.into();
528    let tree = brokk_bifrost_core::analyzer::common::parse_source_range_with_cancellation(
529        &language,
530        source,
531        content.range(),
532        cancellation,
533    )?;
534    if tree.root_node().has_error() {
535        return None;
536    }
537    Some(tree)
538}
539
540/// Whether `string` is a value argument of `Literal[...]`, rather than a
541/// deferred type expression merely because the whole subscript is an
542/// annotation.
543fn python_string_is_literal_value(string: Node<'_>, source: &str) -> bool {
544    let start = string.start_byte();
545    let end = string.end_byte();
546    let mut current = string;
547    while let Some(parent) = current.parent() {
548        match parent.kind() {
549            "subscript" => {
550                let Some(value) = parent.child_by_field_name("value") else {
551                    return false;
552                };
553                if value.start_byte() <= start && end <= value.end_byte() {
554                    return false;
555                }
556                return python_literal_annotation_base(value, source);
557            }
558            "generic_type" => {
559                let Some(value) = parent.named_child(0) else {
560                    return false;
561                };
562                return python_literal_annotation_base(value, source);
563            }
564            _ => current = parent,
565        }
566    }
567    false
568}
569
570fn python_literal_annotation_base(value: Node<'_>, source: &str) -> bool {
571    match value.kind() {
572        "identifier" => node_text(value, source) == "Literal",
573        "attribute" => {
574            let (Some(object), Some(attribute)) = (
575                value.child_by_field_name("object"),
576                value.child_by_field_name("attribute"),
577            ) else {
578                return false;
579            };
580            object.kind() == "identifier"
581                && matches!(node_text(object, source), "typing" | "typing_extensions")
582                && attribute.kind() == "identifier"
583                && node_text(attribute, source) == "Literal"
584        }
585        "member_type" => {
586            let mut identifiers = Vec::new();
587            let mut stack = vec![value];
588            while let Some(node) = stack.pop() {
589                if node.kind() == "identifier" {
590                    identifiers.push(node_text(node, source));
591                    continue;
592                }
593                for index in (0..node.named_child_count()).rev() {
594                    if let Some(child) = node.named_child(index) {
595                        stack.push(child);
596                    }
597                }
598            }
599            matches!(
600                identifiers.as_slice(),
601                ["typing", "Literal"] | ["typing_extensions", "Literal"]
602            )
603        }
604        _ => false,
605    }
606}
607
608/// A structurally selected subtree mentioning the module export identifier.
609/// Used by both legacy recovery and native export-operation classification.
610pub(crate) fn python_contains_all_identifier(node: Node<'_>, source: &str) -> bool {
611    use brokk_bifrost_core::analyzer::tree_walk::{WalkControl, walk_named_tree_preorder};
612    let mut found = false;
613    walk_named_tree_preorder(node, true, |node| {
614        if node.kind() == "identifier" && &source[node.byte_range()] == "__all__" {
615            found = true;
616            WalkControl::Break
617        } else {
618            WalkControl::Continue
619        }
620    });
621    found
622}
623
624#[cfg(test)]
625mod tests {
626    use brokk_bifrost_test_macros::hardwon;
627
628    #[test]
629    fn qs6_python_syntax_field_boundary() {
630        use super::*;
631        for (source, expected) in [
632            ("sys.argv[1]", Some("sys.argv")),
633            ("obj.member[key]", Some("obj.member")),
634            ("obj.nested.member[key]", None),
635            ("values[key]", None),
636            ("obj.member", None),
637        ] {
638            let tree = crate::declarations::parse_python_tree(source).unwrap();
639            assert!(!tree.root_node().has_error());
640            let expression = tree
641                .root_node()
642                .named_child(0)
643                .unwrap()
644                .named_child(0)
645                .unwrap();
646            let seed = crate::runtime_values::python_runtime_keyed_access_seed(expression);
647            assert_eq!(seed.map(|node| &source[node.byte_range()]), expected);
648            if let Some(seed) = seed {
649                assert_eq!(seed.kind(), "attribute");
650                assert_eq!(
651                    expression.child_by_field_name("value").unwrap().id(),
652                    seed.id()
653                );
654                assert_eq!(seed.child_by_field_name("object").unwrap().start_byte(), 0);
655                assert_eq!(seed.start_byte(), 0);
656                assert_eq!(seed.end_byte(), expected.unwrap().len());
657            }
658        }
659        for (annotation, names) in [
660            ("pkg.Type", vec!["pkg", "Type"]),
661            ("pkg.Outer.Inner[int]", vec!["pkg", "Outer", "Inner"]),
662            ("Outer[T].Inner", vec!["Outer", "Inner"]),
663        ] {
664            let source = format!("def f() -> {annotation}: pass\n");
665            let tree = crate::declarations::parse_python_tree(&source).unwrap();
666            assert!(!tree.root_node().has_error());
667            let annotation_node = tree
668                .root_node()
669                .named_child(0)
670                .unwrap()
671                .child_by_field_name("return_type")
672                .unwrap();
673            let path = python_static_type_path(annotation_node).unwrap();
674            assert_eq!(
675                path.iter()
676                    .map(|node| &source[node.byte_range()])
677                    .collect::<Vec<_>>(),
678                names
679            );
680            for node in path {
681                assert_eq!(node.kind(), "identifier");
682                assert!(node.start_byte() >= annotation_node.start_byte());
683                assert!(node.end_byte() <= annotation_node.end_byte());
684            }
685        }
686        let source = "@factory()()\ndef f(): pass\n";
687        let tree = crate::declarations::parse_python_tree(source).unwrap();
688        let decorator = tree
689            .root_node()
690            .named_child(0)
691            .unwrap()
692            .named_child(0)
693            .unwrap();
694        assert_eq!(decorator.kind(), "decorator");
695        let call = decorator.named_child(0).unwrap();
696        assert_eq!(call.kind(), "call");
697        assert_eq!(call.child_by_field_name("function").unwrap().kind(), "call");
698        let callee = decorator_callee(decorator).unwrap();
699        assert_eq!(callee.kind(), "identifier");
700        assert_eq!(callee.byte_range(), 1..8);
701        assert_eq!(&source[callee.byte_range()], "factory");
702    }
703
704    #[test]
705    fn qs6_deferred_annotation_live_token_ranges() {
706        use super::*;
707        let source = "# \u{e9}\ndef f(x: \"pkg.Type[Other]\") -> \"Result\": pass\nordinary = \"text\"\ny: Literal[\"value\"]\n";
708        let tree = crate::declarations::parse_python_tree(source).unwrap();
709        assert!(!tree.root_node().has_error());
710        let fresh = CancellationToken::new();
711        let cancelled = CancellationToken::new();
712        cancelled.cancel();
713        let mut pending = vec![tree.root_node()];
714        let mut strings = Vec::new();
715        while let Some(node) = pending.pop() {
716            if node.kind() == "string" {
717                strings.push(node);
718            }
719            for index in (0..node.named_child_count()).rev() {
720                pending.push(node.named_child(index).unwrap());
721            }
722        }
723        assert_eq!(strings.len(), 4);
724        let mut names = Vec::new();
725        for string in strings {
726            let no_token = python_deferred_annotation_identifier_ranges(string, source, None);
727            assert_eq!(
728                python_deferred_annotation_identifier_ranges(string, source, Some(&fresh)),
729                no_token
730            );
731            assert!(
732                python_deferred_annotation_identifier_ranges(string, source, Some(&cancelled))
733                    .is_none()
734            );
735            if let Some(ranges) = no_token {
736                for range in ranges {
737                    assert!(range.start_byte > string.start_byte());
738                    assert!(range.end_byte < string.end_byte());
739                    assert_eq!(range.start_line, 2);
740                    assert_eq!(range.end_line, 2);
741                    names.push(&source[range.start_byte..range.end_byte]);
742                }
743            }
744        }
745        assert_eq!(names, ["pkg", "Type", "Other", "Result"]);
746    }
747
748    #[test]
749    #[hardwon("#5044: commented parentheses must preserve import identity and budget exhaustion")]
750    fn static_paths_keep_parentheses_comments_and_exhaustion_distinct() {
751        let source = "(  # receiver\n    module).function\n";
752        let tree = crate::declarations::parse_python_tree(source).unwrap();
753        let expression = tree
754            .root_node()
755            .named_child(0)
756            .unwrap()
757            .named_child(0)
758            .unwrap();
759        // Preserve the producer-facing helper's existing admission contract.
760        assert!(super::python_static_attribute_path(expression).is_none());
761        assert!(super::python_static_value_path_bounded(expression, || false).is_none());
762        let path = super::python_static_value_path_bounded(expression, || true)
763            .unwrap()
764            .unwrap();
765        assert_eq!(
766            path.iter()
767                .map(|node| node.utf8_text(source.as_bytes()).unwrap())
768                .collect::<Vec<_>>(),
769            ["module", "function"]
770        );
771        let dynamic = crate::declarations::parse_python_tree("factory().function\n").unwrap();
772        let expression = dynamic
773            .root_node()
774            .named_child(0)
775            .unwrap()
776            .named_child(0)
777            .unwrap();
778        assert!(matches!(
779            super::python_static_value_path_bounded(expression, || true),
780            Some(None)
781        ));
782    }
783}