Skip to main content

brokk_bifrost_python/
structural.rs

1//! Python structural spec: maps tree-sitter-python node types onto the
2//! normalized kind vocabulary and extracts role edges from AST fields.
3//! See `src/analyzer/structural/spec.rs` for the contract and
4//! `.agent/ISSUE_328_SEARCH_AST_EXECPLAN.md` for the design.
5
6use brokk_bifrost_core::analyzer::common::node_source_text;
7use brokk_bifrost_core::analyzer::structural::adapter_helpers::{
8    CalleeSpineStep, attach_argument_role_with_derived_name, attach_role_with_derived_name,
9    attach_terminal_callee, field_name_in_parent, nearest_ancestor, node_range,
10    terminal_callee_leaf,
11};
12use brokk_bifrost_core::analyzer::structural::callable::CallSiteContext;
13use brokk_bifrost_core::analyzer::structural::edges::{
14    DEEP_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
15};
16use brokk_bifrost_core::analyzer::structural::facts::{
17    AssignmentSequenceKind, AssignmentShape, IntegerLiteralValue, Span,
18    decode_integer_literal_digits,
19};
20use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
21use brokk_bifrost_core::analyzer::structural::materialization::{
22    DeclarationMaterializationSupport, PYTHON_MATERIALIZATION_SUPPORT, SourceExportBinding,
23    SourceExportBindingKind, SourceExportFact, SourceExportFacts, SourceExportLocalDefinition,
24};
25use brokk_bifrost_core::analyzer::structural::occurrences::{
26    Namespace, OccurrenceRole, OccurrenceRoleSupport, module_path_occurrence_namespace,
27};
28use brokk_bifrost_core::analyzer::structural::resolution::{
29    BindingActivation, BindingKind, DEEP_LEXICAL_ENVIRONMENT_SUPPORT, HoistingClass,
30    LexicalEnvironmentSupport, ScopeFormation, default_scope_formation,
31};
32use brokk_bifrost_core::analyzer::structural::routes::{
33    CuratedExportSurface, DEEP_IDENTITY_AXES, IdentityRouteSupport, RouteHopKind,
34};
35use brokk_bifrost_core::analyzer::structural::spec::{EmbeddedLeafFact, RoleSink, StructuralSpec};
36use brokk_bifrost_core::analyzer::{Language, Range};
37use brokk_bifrost_core::cancellation::CancellationToken;
38use brokk_bifrost_core::hash::{HashMap, HashSet};
39use tree_sitter::Node;
40
41use crate::bindings::{
42    PythonDirectScopeBindingKind, python_direct_scope_bindings_bounded,
43    python_executable_scope_children,
44};
45use crate::imports::python_import_bindings_from_tree;
46use crate::syntax::{
47    expression_name_node, python_conditional_expression_operands,
48    python_deferred_annotation_identifier_ranges, python_keyword_argument_label,
49    python_node_is_in_annotation, python_plain_string_literal,
50};
51
52#[derive(Debug, Default)]
53pub struct PythonStructuralSpec;
54
55pub static PYTHON_STRUCTURAL_SPEC: PythonStructuralSpec = PythonStructuralSpec;
56
57/// Tree-sitter interleaves grammar extras (`comment`, `line_continuation`)
58/// between the operands of a Python node wherever whitespace is allowed. A
59/// backslash newline is the `line_continuation` extra, so an operand that is
60/// selected by position among every named child can land on an extra instead.
61/// Structural roles and shapes describe the operands, so selection skips extras.
62fn python_first_named_operand(node: Node<'_>) -> Option<Node<'_>> {
63    let mut cursor = node.walk();
64    node.named_children(&mut cursor)
65        .find(|child| !child.is_extra())
66}
67
68pub(crate) fn python_parenthesized_expression_child(node: Node<'_>) -> Option<Node<'_>> {
69    assert_eq!(node.kind(), "parenthesized_expression");
70    let mut cursor = node.walk();
71    let mut children = node
72        .named_children(&mut cursor)
73        .filter(|child| !child.is_extra());
74    let child = children.next()?;
75    children.next().is_none().then_some(child)
76}
77
78fn python_unwrap_parenthesized(mut node: Node<'_>) -> Node<'_> {
79    while node.kind() == "parenthesized_expression" {
80        let Some(child) = python_parenthesized_expression_child(node) else {
81            break;
82        };
83        node = child;
84    }
85    node
86}
87
88fn python_unstarred_sequence_shape(node: Node<'_>, starred_node_kinds: &[&str]) -> (u32, bool) {
89    let mut unstarred_count = 0u32;
90    let mut starred = false;
91    for index in 0..node.named_child_count() {
92        let child = node
93            .named_child(index)
94            .expect("named child index is below the named child count");
95        if child.is_extra() {
96            continue;
97        }
98        match child.kind() {
99            kind if starred_node_kinds.contains(&kind) => starred = true,
100            _ => unstarred_count += 1,
101        }
102    }
103    (unstarred_count, starred)
104}
105
106/// Expand only displays whose argument names and cardinality are known from
107/// syntax. Validate the entire pack before attaching any ordinary arguments.
108fn python_literal_pack_arguments(argument: Node<'_>, sink: &mut RoleSink<'_>) -> bool {
109    let Some(operand) = python_first_named_operand(argument) else {
110        return false;
111    };
112    let operand = python_unwrap_parenthesized(operand);
113    match (argument.kind(), operand.kind()) {
114        ("list_splat", "tuple" | "list") => {
115            let mut cursor = operand.walk();
116            let elements: Vec<_> = operand
117                .named_children(&mut cursor)
118                .filter(|child| !child.is_extra())
119                .collect();
120            if elements
121                .iter()
122                .any(|element| element.kind() == "list_splat")
123            {
124                return false;
125            }
126            for element in elements {
127                attach_argument_role_with_derived_name(sink, element, expression_name_node);
128            }
129            true
130        }
131        ("dictionary_splat", "dictionary") => {
132            let mut entries = Vec::new();
133            let mut indices = HashMap::default();
134            let mut cursor = operand.walk();
135            for pair in operand
136                .named_children(&mut cursor)
137                .filter(|child| !child.is_extra())
138            {
139                if pair.kind() != "pair" {
140                    return false;
141                }
142                let (Some(key), Some(value)) = (
143                    pair.child_by_field_name("key"),
144                    pair.child_by_field_name("value"),
145                ) else {
146                    return false;
147                };
148                let Some(name) = python_plain_string_literal(key, sink.source()) else {
149                    return false;
150                };
151                // Plain strings have no escapes. Select their content using
152                // grammar nodes, including the zero-width content of "".
153                let mut key_cursor = key.walk();
154                let Some(content) = key
155                    .named_children(&mut key_cursor)
156                    .find(|child| matches!(child.kind(), "string_content" | "string_end"))
157                else {
158                    return false;
159                };
160                let span = Span {
161                    start_byte: content.start_byte(),
162                    end_byte: content.start_byte() + name.len(),
163                };
164                // Dict construction overwrites duplicate keys without moving
165                // their insertion position. Only the retained value is passed.
166                if let Some(&index) = indices.get(name) {
167                    entries[index] = (span, value);
168                } else {
169                    indices.insert(name, entries.len());
170                    entries.push((span, value));
171                }
172            }
173            for (keyword, value) in entries {
174                sink.kwarg_span(keyword, value);
175            }
176            true
177        }
178        _ => false,
179    }
180}
181
182fn python_assignment_target_shape(node: Node<'_>) -> Option<(AssignmentSequenceKind, u32, bool)> {
183    let node = python_unwrap_parenthesized(node);
184    let kind = match node.kind() {
185        "tuple_pattern" | "pattern_list" => AssignmentSequenceKind::Tuple,
186        "list_pattern" => AssignmentSequenceKind::List,
187        _ => return None,
188    };
189    let (count, starred) =
190        python_unstarred_sequence_shape(node, &["list_splat_pattern", "splat_pattern"]);
191    Some((kind, count, starred))
192}
193
194fn python_assignment_value_shape(node: Node<'_>) -> Option<(AssignmentSequenceKind, u32, bool)> {
195    let node = python_unwrap_parenthesized(node);
196    let kind = match node.kind() {
197        "tuple" | "expression_list" => AssignmentSequenceKind::Tuple,
198        "list" => AssignmentSequenceKind::List,
199        _ => return None,
200    };
201    let (count, starred) = python_unstarred_sequence_shape(node, &["list_splat"]);
202    Some((kind, count, starred))
203}
204
205fn python_assignment_shape(node: Node<'_>) -> Option<AssignmentShape> {
206    if node.kind() != "assignment" {
207        return None;
208    }
209    let (target_kind, target_unstarred_count, target_starred) =
210        python_assignment_target_shape(node.child_by_field_name("left")?)?;
211    let (value_kind, value_unstarred_count, value_starred) =
212        python_assignment_value_shape(node.child_by_field_name("right")?)?;
213    Some(AssignmentShape {
214        target_kind,
215        value_kind,
216        target_unstarred_count,
217        value_unstarred_count,
218        target_starred,
219        value_starred,
220    })
221}
222
223fn python_integer_literal_value(node: Node<'_>, source: &str) -> Option<IntegerLiteralValue> {
224    if node.kind() != "integer" {
225        return None;
226    }
227
228    let text = node_source_text(node, source).as_bytes();
229    let (radix, digits) = match (text.first(), text.get(1)) {
230        (Some(b'0'), Some(b'b' | b'B')) => (2u32, &text[2..]),
231        (Some(b'0'), Some(b'o' | b'O')) => (8u32, &text[2..]),
232        (Some(b'0'), Some(b'x' | b'X')) => (16u32, &text[2..]),
233        _ => (10u32, text),
234    };
235    decode_integer_literal_digits(radix, digits)
236}
237
238#[derive(Debug, PartialEq, Eq)]
239enum PythonDictionaryKeyValue {
240    String(String),
241    Integer { negative: bool, magnitude: String },
242    Boolean(bool),
243    None,
244}
245
246impl PythonDictionaryKeyValue {
247    fn equals(&self, other: &Self) -> bool {
248        match (self, other) {
249            (Self::String(left), Self::String(right)) => left == right,
250            (Self::None, Self::None) => true,
251            (
252                Self::Integer {
253                    negative: left_negative,
254                    magnitude: left_magnitude,
255                },
256                Self::Integer {
257                    negative: right_negative,
258                    magnitude: right_magnitude,
259                },
260            ) => left_negative == right_negative && left_magnitude == right_magnitude,
261            (Self::Boolean(value), Self::Boolean(other)) => value == other,
262            (
263                Self::Boolean(value),
264                Self::Integer {
265                    negative,
266                    magnitude,
267                },
268            )
269            | (
270                Self::Integer {
271                    negative,
272                    magnitude,
273                },
274                Self::Boolean(value),
275            ) => !negative && magnitude == if *value { "1" } else { "0" },
276            _ => false,
277        }
278    }
279}
280
281fn python_string_key_value(node: Node<'_>, source: &str) -> Option<String> {
282    if node.kind() == "concatenated_string" {
283        let mut value = String::new();
284        let mut cursor = node.walk();
285        for part in node
286            .named_children(&mut cursor)
287            .filter(|part| !part.is_extra())
288        {
289            if part.kind() != "string" {
290                return None;
291            }
292            value.push_str(&python_string_key_value(part, source)?);
293        }
294        return Some(value);
295    }
296    if node.kind() != "string" {
297        return None;
298    }
299
300    let mut raw = false;
301    let mut content = String::new();
302    let mut cursor = node.walk();
303    for child in node.children(&mut cursor) {
304        match child.kind() {
305            "string_start" => {
306                let start = node_source_text(child, source).as_bytes();
307                let prefix_end = start
308                    .iter()
309                    .position(|byte| matches!(byte, b'\'' | b'"'))
310                    .unwrap_or(start.len());
311                let prefix = std::str::from_utf8(&start[..prefix_end]).ok()?;
312                if !prefix.bytes().all(|byte| byte.is_ascii_alphabetic()) {
313                    return None;
314                }
315                let prefix = prefix.to_ascii_lowercase();
316                if prefix.contains('b') || prefix.contains('f') {
317                    return None;
318                }
319                raw = prefix.contains('r');
320            }
321            "string_content" => content.push_str(node_source_text(child, source)),
322            "escape_sequence" => content.push_str(node_source_text(child, source)),
323            "string_end" => {}
324            _ => return None,
325        }
326    }
327    python_decode_string_content(&content, raw)
328}
329
330fn python_decode_string_content(content: &str, raw: bool) -> Option<String> {
331    if raw {
332        return Some(content.to_owned());
333    }
334    let mut chars = content.chars().peekable();
335    let mut decoded = String::with_capacity(content.len());
336    while let Some(character) = chars.next() {
337        if character != '\\' {
338            decoded.push(character);
339            continue;
340        }
341        let escaped = chars.next()?;
342        let replacement = match escaped {
343            '\n' => continue,
344            '\r' => {
345                chars.next_if_eq(&'\n')?;
346                continue;
347            }
348            '\\' => '\\',
349            '\'' => '\'',
350            '"' => '"',
351            'a' => '\u{0007}',
352            'b' => '\u{0008}',
353            'f' => '\u{000c}',
354            'n' => '\n',
355            'r' => '\r',
356            't' => '\t',
357            'v' => '\u{000b}',
358            'x' => python_escape_codepoint(&mut chars, 2)?,
359            'u' => python_escape_codepoint(&mut chars, 4)?,
360            'U' => python_escape_codepoint(&mut chars, 8)?,
361            'N' => return None,
362            '0'..='7' => {
363                let mut value = escaped.to_digit(8)?;
364                for _ in 0..2 {
365                    let Some(next) = chars.peek().copied().filter(char::is_ascii_digit) else {
366                        break;
367                    };
368                    let Some(digit) = next.to_digit(8) else {
369                        break;
370                    };
371                    chars.next();
372                    value = value * 8 + digit;
373                }
374                char::from_u32(value)?
375            }
376            other => {
377                decoded.push('\\');
378                other
379            }
380        };
381        decoded.push(replacement);
382    }
383    Some(decoded)
384}
385
386fn python_escape_codepoint(
387    chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
388    digits: usize,
389) -> Option<char> {
390    let mut value = 0u32;
391    for _ in 0..digits {
392        value = value
393            .checked_mul(16)?
394            .checked_add(chars.next()?.to_digit(16)?)?;
395    }
396    char::from_u32(value)
397}
398
399fn python_dictionary_key_value(node: Node<'_>, source: &str) -> Option<PythonDictionaryKeyValue> {
400    let node = python_unwrap_parenthesized(node);
401    if matches!(node.kind(), "string" | "concatenated_string") {
402        return python_string_key_value(node, source).map(PythonDictionaryKeyValue::String);
403    }
404    if matches!(node.kind(), "true" | "false") {
405        return Some(PythonDictionaryKeyValue::Boolean(node.kind() == "true"));
406    }
407    if node.kind() == "none" {
408        return Some(PythonDictionaryKeyValue::None);
409    }
410
411    let (integer, negative) = if node.kind() == "unary_operator" {
412        let operator = node.child_by_field_name("operator")?;
413        match node_source_text(operator, source) {
414            "+" => (
415                python_unwrap_parenthesized(node.child_by_field_name("argument")?),
416                false,
417            ),
418            "-" => (
419                python_unwrap_parenthesized(node.child_by_field_name("argument")?),
420                true,
421            ),
422            _ => return None,
423        }
424    } else {
425        (node, false)
426    };
427    let value = python_integer_literal_value(integer, source)?;
428    let magnitude = value.decimal?;
429    let negative = negative && magnitude != "0";
430    Some(PythonDictionaryKeyValue::Integer {
431        negative,
432        magnitude,
433    })
434}
435
436fn python_duplicate_dictionary_key(node: Node<'_>, source: &str) -> Option<bool> {
437    if node.kind() != "pair" {
438        return None;
439    }
440    let dictionary = node
441        .parent()
442        .filter(|parent| parent.kind() == "dictionary")?;
443    let current_key = python_dictionary_key_value(node.child_by_field_name("key")?, source)?;
444    let mut earlier_unknown = false;
445    let mut all_entries = dictionary.walk();
446    let has_unpacked_entry = dictionary
447        .named_children(&mut all_entries)
448        .any(|entry| !entry.is_extra() && entry.kind() != "pair");
449    let mut cursor = dictionary.walk();
450    for entry in dictionary.named_children(&mut cursor) {
451        if entry.id() == node.id() {
452            return if earlier_unknown || has_unpacked_entry {
453                None
454            } else {
455                Some(false)
456            };
457        }
458        match entry.kind() {
459            "pair" => {
460                let previous_key = entry
461                    .child_by_field_name("key")
462                    .and_then(|key| python_dictionary_key_value(key, source));
463                if previous_key
464                    .as_ref()
465                    .is_some_and(|previous| current_key.equals(previous))
466                {
467                    return Some(true);
468                }
469                earlier_unknown |= previous_key.is_none();
470            }
471            _ if entry.is_extra() => {}
472            _ => {
473                earlier_unknown = true;
474            }
475        }
476    }
477    None
478}
479
480#[derive(Clone, Copy)]
481pub struct PythonComparisonShape<'tree> {
482    pub operator_count: usize,
483    pub operand_count: usize,
484    pub left: Option<Node<'tree>>,
485    pub right: Option<Node<'tree>>,
486    pub operator: Option<Node<'tree>>,
487}
488
489/// Read comparison operands and operator tokens from the grammar node. Python
490/// comparison operators are repeated fields; named operator aliases such as
491/// `is not` are distinguished from operands by their field name.
492pub fn python_comparison_shape(node: Node<'_>) -> PythonComparisonShape<'_> {
493    let mut operator_count = 0usize;
494    let mut operand_count = 0usize;
495    let mut left = None;
496    let mut right = None;
497    let mut operator = None;
498    visit_python_comparison_children(node, |child, is_operator| {
499        if is_operator {
500            operator_count += 1;
501            operator.get_or_insert(child);
502        } else if child.is_named() && !child.is_extra() {
503            operand_count += 1;
504            if left.is_none() {
505                left = Some(child);
506            } else if right.is_none() {
507                right = Some(child);
508            }
509        }
510        true
511    });
512    debug_assert!(
513        node.has_error() || operand_count == operator_count.saturating_add(1),
514        "well-formed Python comparison must have one more operand than operators: {node:?}"
515    );
516    PythonComparisonShape {
517        operator_count,
518        operand_count,
519        left,
520        right,
521        operator,
522    }
523}
524
525/// Shared grammar-field traversal for singular roles and transient domains.
526fn visit_python_comparison_children<'tree>(
527    node: Node<'tree>,
528    mut visit: impl FnMut(Node<'tree>, bool) -> bool,
529) -> bool {
530    let mut cursor = node.walk();
531    if cursor.goto_first_child() {
532        loop {
533            if !visit(cursor.node(), cursor.field_name() == Some("operators")) {
534                return false;
535            }
536            if !cursor.goto_next_sibling() {
537                break;
538            }
539        }
540    }
541    true
542}
543
544/// Whether a wrapper is a constant form used by comparison-of-constants:
545/// parenthesized scalar literals and unary signs over numeric literals. This
546/// deliberately does not fold arbitrary expressions such as `1 + 2`.
547fn python_comparison_constant(node: Node<'_>) -> bool {
548    let mut current = node;
549    let mut signed = false;
550    loop {
551        match current.kind() {
552            "parenthesized_expression" => {
553                let Some(expression) = python_parenthesized_expression_child(current) else {
554                    return false;
555                };
556                current = expression;
557            }
558            "unary_operator" => {
559                if !current
560                    .child_by_field_name("operator")
561                    .is_some_and(|operator| matches!(operator.kind(), "+" | "-"))
562                {
563                    return false;
564                }
565                let Some(argument) = current.child_by_field_name("argument") else {
566                    return false;
567                };
568                signed = true;
569                current = argument;
570            }
571            "integer" | "float" => return true,
572            "true" | "false" | "none" => return !signed,
573            "string" | "concatenated_string" => {
574                return !signed && python_plain_string_constant(current);
575            }
576            _ => return false,
577        }
578    }
579}
580
581/// Accept ordinary string tokens and implicit literal concatenation, but not
582/// interpolated strings. Tree structure, rather than source-text scanning,
583/// distinguishes the interpolation form.
584fn python_plain_string_constant(node: Node<'_>) -> bool {
585    let mut pending = vec![node];
586    let mut saw_string = false;
587    while let Some(current) = pending.pop() {
588        match current.kind() {
589            "string" => {
590                saw_string = true;
591                let mut cursor = current.walk();
592                if current
593                    .named_children(&mut cursor)
594                    .any(|child| child.kind() == "interpolation")
595                {
596                    return false;
597                }
598            }
599            "concatenated_string" => {
600                let mut cursor = current.walk();
601                let mut parts = 0;
602                for child in current.named_children(&mut cursor) {
603                    if child.kind() != "string" {
604                        return false;
605                    }
606                    parts += 1;
607                    pending.push(child);
608                }
609                if parts == 0 {
610                    return false;
611                }
612            }
613            _ => return false,
614        }
615    }
616    saw_string
617}
618
619/// Grammar node-type → normalized kind. Every name here must exist in the
620/// tree-sitter-python grammar; `tests::python_kind_table_matches_grammar`
621/// asserts that, so a grammar bump that renames a node fails loudly.
622pub const PYTHON_KIND_TABLE: &[(&str, NormalizedKind)] = &[
623    ("call", NormalizedKind::Call),
624    ("attribute", NormalizedKind::FieldAccess),
625    ("function_definition", NormalizedKind::Function),
626    ("lambda", NormalizedKind::Lambda),
627    ("await", NormalizedKind::Await),
628    ("yield", NormalizedKind::Yield),
629    ("module", NormalizedKind::Module),
630    ("class_definition", NormalizedKind::Class),
631    ("assignment", NormalizedKind::Assignment),
632    ("binary_operator", NormalizedKind::BinaryExpression),
633    ("boolean_operator", NormalizedKind::BinaryExpression),
634    ("comparison_operator", NormalizedKind::Comparison),
635    ("assert_statement", NormalizedKind::Assertion),
636    ("conditional_expression", NormalizedKind::If),
637    // These wrappers are emitted as literals only for supported constant
638    // forms; `should_extract` rejects computed or interpolated expressions.
639    ("parenthesized_expression", NormalizedKind::Literal),
640    ("unary_operator", NormalizedKind::Literal),
641    // The implicit `__exit__` of a `with` statement runs at the construct's
642    // own exit, so the statement is the source fact the resource-lifecycle
643    // selector names for that release.
644    ("with_statement", NormalizedKind::ResourceRelease),
645    ("import_statement", NormalizedKind::Import),
646    ("import_from_statement", NormalizedKind::Import),
647    ("identifier", NormalizedKind::Identifier),
648    ("string", NormalizedKind::StringLiteral),
649    ("concatenated_string", NormalizedKind::StringLiteral),
650    ("integer", NormalizedKind::NumericLiteral),
651    ("float", NormalizedKind::NumericLiteral),
652    ("true", NormalizedKind::BooleanLiteral),
653    ("false", NormalizedKind::BooleanLiteral),
654    ("none", NormalizedKind::NullLiteral),
655    ("return_statement", NormalizedKind::Return),
656    ("raise_statement", NormalizedKind::Throw),
657    ("except_clause", NormalizedKind::Catch),
658    ("if_statement", NormalizedKind::If),
659    ("for_statement", NormalizedKind::ForLoop),
660    ("list", NormalizedKind::ListLiteral),
661    ("set", NormalizedKind::CollectionLiteral),
662    ("dictionary", NormalizedKind::CollectionLiteral),
663    ("tuple", NormalizedKind::TupleLiteral),
664    ("pair", NormalizedKind::ObjectProperty),
665    ("while_statement", NormalizedKind::WhileLoop),
666    // Python's indented suite.
667    ("block", NormalizedKind::Block),
668    ("decorator", NormalizedKind::Decorator),
669    ("comment", NormalizedKind::Comment),
670];
671
672/// Attach `decorators` edges for a definition wrapped in Python's
673/// `decorated_definition` node (which itself is not normalized).
674fn attach_decorators(sink: &mut RoleSink<'_>, definition: Node<'_>) {
675    let Some(parent) = definition.parent() else {
676        return;
677    };
678    if parent.kind() != "decorated_definition" {
679        return;
680    }
681    for index in 0..parent.named_child_count() {
682        let Some(child) = parent.named_child(index) else {
683            continue;
684        };
685        if child.kind() == "decorator" {
686            attach_role_with_derived_name(sink, Role::Decorator, child, expression_name_node);
687        }
688    }
689}
690
691static PYTHON_OCCURRENCE_ROLE_SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
692    .supported(OccurrenceRole::DeclarationName)
693    .supported(OccurrenceRole::Binder)
694    .supported(OccurrenceRole::LabelOrKey)
695    .supported(OccurrenceRole::TypeOperand)
696    .supported(OccurrenceRole::PathSegment)
697    .supported(OccurrenceRole::ImportAlias)
698    .supported(OccurrenceRole::ImportTarget)
699    .supported(OccurrenceRole::ReceiverPosition)
700    .supported(OccurrenceRole::MemberPosition)
701    .supported(OccurrenceRole::ValueReference)
702    .supported(OccurrenceRole::GlobalDirective)
703    .supported(OccurrenceRole::NonlocalDirective);
704
705/// Whether a `dotted_name` names an imported module rather than an ordinary
706/// attribute chain, which decides whether its tail is an import target.
707///
708/// `relative_import` is the wrapper the grammar puts around the module name of
709/// `from .impl import x`. It is the same import target as the `pkg.impl` of
710/// `from pkg.impl import x`, so it passes through like the other wrappers.
711fn python_dotted_name_is_import(dotted_name: Node<'_>) -> bool {
712    let mut current = dotted_name;
713    loop {
714        let Some(parent) = current.parent() else {
715            return false;
716        };
717        match parent.kind() {
718            "import_statement" | "import_from_statement" | "future_import_statement" => {
719                return true;
720            }
721            "aliased_import" | "dotted_name" | "relative_import" => current = parent,
722            _ => return false,
723        }
724    }
725}
726
727/// Classify one Python identifier token by its AST position.
728///
729/// Python has no separate type-identifier node, so annotation operands are
730/// recognized by their enclosing `type` node — the same field the parser uses
731/// to separate `def f(x: T)`'s binder from its annotation.
732fn python_occurrence_role(node: Node<'_>) -> Option<OccurrenceRole> {
733    if node.kind() != "identifier" {
734        return None;
735    }
736    let parent = node.parent()?;
737    let field = field_name_in_parent(parent, node);
738    let role = match parent.kind() {
739        "global_statement" => OccurrenceRole::GlobalDirective,
740        "nonlocal_statement" => OccurrenceRole::NonlocalDirective,
741        "function_definition" | "class_definition" if field == Some("name") => {
742            OccurrenceRole::DeclarationName
743        }
744        // Every annotation, return type and type-alias operand is wrapped in a
745        // `type` node; `generic_type`/`type_parameter` nest inside one.
746        "type" | "generic_type" | "type_parameter" | "constrained_type" | "union_type" => {
747            OccurrenceRole::TypeOperand
748        }
749        "parameters"
750        | "lambda_parameters"
751        | "typed_parameter"
752        | "list_splat_pattern"
753        | "dictionary_splat_pattern"
754        | "tuple_pattern"
755        | "list_pattern"
756        | "pattern_list"
757        | "as_pattern_target" => OccurrenceRole::Binder,
758        "default_parameter" | "typed_default_parameter" if field == Some("name") => {
759            OccurrenceRole::Binder
760        }
761        "for_statement" | "for_in_clause" if field == Some("left") => OccurrenceRole::Binder,
762        "assignment" if field == Some("left") => OccurrenceRole::Binder,
763        "keyword_argument" if python_keyword_argument_label(node) => OccurrenceRole::LabelOrKey,
764        "attribute" => match field {
765            Some("attribute") => OccurrenceRole::MemberPosition,
766            Some("object") => OccurrenceRole::ReceiverPosition,
767            _ => OccurrenceRole::ValueReference,
768        },
769        "aliased_import" if field == Some("alias") => OccurrenceRole::ImportAlias,
770        "import_from_statement" if field == Some("name") => OccurrenceRole::ImportTarget,
771        "dotted_name" => {
772            let is_tail =
773                parent.named_child(parent.named_child_count().saturating_sub(1)) == Some(node);
774            match (is_tail, python_dotted_name_is_import(parent)) {
775                (true, true) => OccurrenceRole::ImportTarget,
776                (true, false) => OccurrenceRole::ValueReference,
777                (false, _) => OccurrenceRole::PathSegment,
778            }
779        }
780        _ => OccurrenceRole::ValueReference,
781    };
782    Some(role)
783}
784
785/// Whether `node` is the identifier token that binds one formal parameter.
786///
787/// Tree-sitter uses a bare identifier directly under `parameters` or
788/// `lambda_parameters`, and wrapper nodes for annotations, defaults, and
789/// splats. The shared label helper selects the exact name child for each
790/// wrapper so identifiers in annotations/default values do not become
791/// parameter facts.
792fn python_identifier_is_parameter(node: Node<'_>) -> bool {
793    if node.kind() != "identifier" {
794        return false;
795    }
796    let Some(parent) = node.parent() else {
797        return false;
798    };
799    match parent.kind() {
800        "parameters" | "lambda_parameters" => true,
801        "typed_parameter"
802        | "default_parameter"
803        | "typed_default_parameter"
804        | "list_splat_pattern"
805        | "dictionary_splat_pattern"
806        | "keyword_separator" => {
807            crate::declarations::python_parameter_label_node(parent) == Some(node)
808        }
809        _ => false,
810    }
811}
812
813/// Whether a `def` declares a method, that is, whether the suite it sits in
814/// belongs to a `class_definition`.
815///
816/// This reads the parse tree rather than the nearest enclosing normalized kind
817/// because the suite between a class and its methods is itself a normalized
818/// node now (`NormalizedKind::Block`, issue #1474). Walking the concrete
819/// ancestors is also the more direct statement of the rule: a nested `def`
820/// inside a method reaches its enclosing `function_definition` first and stays
821/// a function.
822fn python_definition_is_method(definition: Node<'_>) -> bool {
823    let mut current = definition;
824    while let Some(parent) = current.parent() {
825        match parent.kind() {
826            "class_definition" => return true,
827            // The suite that holds the members, and the wrapper a decorated
828            // definition sits in, are pass-through on the way to the owner.
829            "block" | "decorated_definition" => current = parent,
830            _ => return false,
831        }
832    }
833    false
834}
835
836/// The binding one Python binder token introduces, and the interval it is in
837/// effect over.
838///
839/// Python's function locals are scope-categorical rather than positional: a
840/// name assigned anywhere in a function body is a local of that function for
841/// the whole body, which is why a read above the assignment is an
842/// `UnboundLocalError` rather than a read of an outer name. That is exactly
843/// `ScopeWide`. The one positional exception is a comprehension target, which
844/// lives in the comprehension's own implicit scope; the same exception
845/// `analyzer::python::bindings` records as `PythonComprehensionBinding`.
846fn python_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
847    // A binder outside every form below is an ordinary assignment target --
848    // the members of a module-level or block-level `a, b = ...` pattern list,
849    // for example -- and Python's scope-categorical rule makes it a local of
850    // its declaring scope for the whole scope. Answering `None` here made the
851    // whole file's lexical environment incomplete as soon as one such binder
852    // existed (scripts/test-cost/greedy.py's `s, k = heapq.heappop(heap)` at
853    // module scope), which failed every code-smells gate that derived it.
854    let Some(form) = nearest_ancestor(binder, |kind| {
855        matches!(
856            kind,
857            "parameters"
858                | "lambda_parameters"
859                | "assignment"
860                | "for_statement"
861                | "for_in_clause"
862                | "as_pattern"
863                | "list_comprehension"
864                | "set_comprehension"
865                | "dictionary_comprehension"
866                | "generator_expression"
867        )
868    }) else {
869        return Some(BindingActivation {
870            kind: BindingKind::Local,
871            hoisting: HoistingClass::ScopeWide,
872            activation: scope,
873        });
874    };
875    match form.kind() {
876        "parameters" | "lambda_parameters" => Some(BindingActivation {
877            kind: BindingKind::Parameter,
878            hoisting: HoistingClass::ScopeWide,
879            activation: scope,
880        }),
881        "assignment" => Some(BindingActivation {
882            kind: BindingKind::Local,
883            hoisting: HoistingClass::ScopeWide,
884            activation: scope,
885        }),
886        "for_in_clause" => {
887            // A comprehension clause binds only inside the comprehension.
888            let comprehension = nearest_ancestor(form, |kind| {
889                matches!(
890                    kind,
891                    "list_comprehension"
892                        | "set_comprehension"
893                        | "dictionary_comprehension"
894                        | "generator_expression"
895                )
896            })?;
897            Some(BindingActivation {
898                kind: BindingKind::LoopVariable,
899                hoisting: HoistingClass::DeclaredHead,
900                activation: node_range(comprehension),
901            })
902        }
903        "for_statement" => Some(BindingActivation {
904            kind: BindingKind::LoopVariable,
905            hoisting: HoistingClass::ScopeWide,
906            activation: scope,
907        }),
908        "as_pattern" => Some(BindingActivation {
909            kind: BindingKind::PatternBinder,
910            hoisting: HoistingClass::ScopeWide,
911            activation: scope,
912        }),
913        _ => Some(BindingActivation {
914            kind: BindingKind::Local,
915            hoisting: HoistingClass::ScopeWide,
916            activation: scope,
917        }),
918    }
919}
920
921/// The text one plain string literal denotes, or `None` when the literal is
922/// not plain: an f-string interpolation, an escape sequence this reader does
923/// not decode, or an implicit concatenation. `None` is never an empty name --
924/// it is "this value is computed", which makes the whole surface unreadable.
925/// Whether every member of a curated surface was read from the parse tree.
926#[derive(Debug, Clone, Copy, PartialEq, Eq)]
927enum ReadableSurface {
928    Yes,
929    No,
930}
931
932#[derive(Debug, Clone)]
933struct PythonExportMember {
934    name: String,
935    range: Range,
936}
937
938/// Collect the members of an `__all__` value into `names`, reporting whether
939/// every member was readable. Only a list or tuple display of plain string
940/// literals is; anything else is a value the source computes.
941fn python_collect_all_members(
942    value: Node<'_>,
943    source: &str,
944    members: &mut Vec<PythonExportMember>,
945) -> ReadableSurface {
946    if !matches!(value.kind(), "list" | "tuple") {
947        return ReadableSurface::No;
948    }
949    let mut cursor = value.walk();
950    for element in value
951        .named_children(&mut cursor)
952        .filter(|element| !element.is_extra())
953    {
954        match python_plain_string_literal(element, source) {
955            Some(text) => {
956                members.push(PythonExportMember {
957                    name: text.to_owned(),
958                    range: node_range(element),
959                });
960            }
961            None => return ReadableSurface::No,
962        }
963    }
964    ReadableSurface::Yes
965}
966
967fn python_module_export_bindings(
968    root: Node<'_>,
969    source: &str,
970) -> HashMap<Box<str>, SourceExportBinding> {
971    let mut bindings = Vec::new();
972    let mut stack = vec![root];
973    while let Some(node) = stack.pop() {
974        for binding in python_direct_scope_bindings_bounded(node, source, || true)
975            .expect("unbounded Python module binding scan completes")
976        {
977            let name = node_source_text(binding.declaration, source);
978            if name.is_empty() {
979                continue;
980            }
981            let kind = match binding.kind {
982                PythonDirectScopeBindingKind::ClassDeclaration => SourceExportBindingKind::Class,
983                PythonDirectScopeBindingKind::FunctionDeclaration => {
984                    SourceExportBindingKind::Function
985                }
986                PythonDirectScopeBindingKind::Import => SourceExportBindingKind::Import,
987                PythonDirectScopeBindingKind::Other => SourceExportBindingKind::Binding,
988            };
989            bindings.push(SourceExportBinding {
990                name: name.to_owned(),
991                kind,
992                range: node_range(binding.declaration),
993            });
994        }
995        stack.extend(python_executable_scope_children(node));
996    }
997    bindings.sort_unstable_by_key(|binding| binding.range.start_byte);
998    let mut by_name = HashMap::default();
999    for binding in bindings {
1000        by_name
1001            .entry(binding.name.clone().into())
1002            .or_insert(binding);
1003    }
1004    by_name
1005}
1006
1007fn python_call_function_name<'a>(node: Node<'_>, source: &'a str) -> Option<&'a str> {
1008    if node.kind() != "call" {
1009        return None;
1010    }
1011    let function = node.child_by_field_name("function")?;
1012    (function.kind() == "identifier").then(|| node_source_text(function, source))
1013}
1014
1015fn python_current_module_expression(
1016    expression: Node<'_>,
1017    source: &str,
1018    sys_bindings: &HashSet<Box<str>>,
1019    module_aliases: &HashSet<Box<str>>,
1020) -> bool {
1021    let expression = python_unwrap_parenthesized(expression);
1022    if expression.kind() == "identifier"
1023        && module_aliases.contains(node_source_text(expression, source))
1024    {
1025        return true;
1026    }
1027    if expression.kind() == "subscript"
1028        && let (Some(value), Some(subscript)) = (
1029            expression.child_by_field_name("value"),
1030            expression.child_by_field_name("subscript"),
1031        )
1032        && subscript.kind() == "identifier"
1033        && node_source_text(subscript, source) == "__name__"
1034        && value.kind() == "attribute"
1035        && value
1036            .child_by_field_name("attribute")
1037            .is_some_and(|attribute| node_source_text(attribute, source) == "modules")
1038        && value.child_by_field_name("object").is_some_and(|object| {
1039            object.kind() == "identifier" && sys_bindings.contains(node_source_text(object, source))
1040        })
1041    {
1042        return true;
1043    }
1044    python_call_function_name(expression, source) == Some("__import__")
1045        && expression
1046            .child_by_field_name("arguments")
1047            .and_then(python_first_named_operand)
1048            .is_some_and(|argument| {
1049                argument.kind() == "identifier" && node_source_text(argument, source) == "__name__"
1050            })
1051}
1052
1053fn python_module_definition_is_dynamic(root: Node<'_>, source: &str) -> bool {
1054    let sys_bindings: HashSet<Box<str>> = python_import_bindings_from_tree(root, source)
1055        .into_iter()
1056        .filter(|binding| binding.qualified_name == "sys")
1057        .map(|binding| binding.local_name.into_boxed_str())
1058        .collect();
1059    let mut assignments = Vec::new();
1060    let mut pending = vec![root];
1061    while let Some(node) = pending.pop() {
1062        if node.kind() == "assignment"
1063            && let (Some(left), Some(right)) = (
1064                node.child_by_field_name("left"),
1065                node.child_by_field_name("right"),
1066            )
1067            && left.kind() == "identifier"
1068        {
1069            assignments.push((node.start_byte(), left, right));
1070        }
1071        let mut cursor = node.walk();
1072        pending.extend(node.named_children(&mut cursor));
1073    }
1074    assignments.sort_unstable_by_key(|(start, _, _)| *start);
1075    let mut module_aliases = HashSet::default();
1076    for (_, left, right) in assignments {
1077        if python_current_module_expression(right, source, &sys_bindings, &module_aliases) {
1078            module_aliases.insert(node_source_text(left, source).into());
1079        }
1080    }
1081
1082    let mut pending = vec![root];
1083    while let Some(node) = pending.pop() {
1084        if node.kind() == "wildcard_import"
1085            || python_call_function_name(node, source) == Some("globals")
1086        {
1087            return true;
1088        }
1089        if python_call_function_name(node, source) == Some("setattr")
1090            && node
1091                .child_by_field_name("arguments")
1092                .and_then(python_first_named_operand)
1093                .is_some_and(|object| {
1094                    python_current_module_expression(object, source, &sys_bindings, &module_aliases)
1095                })
1096        {
1097            return true;
1098        }
1099        let mut cursor = node.walk();
1100        pending.extend(node.named_children(&mut cursor));
1101    }
1102    root.has_error()
1103}
1104
1105/// Parse the source-backed member rows for the module's explicit `__all__`.
1106///
1107/// Known literals are retained if another member makes the complete surface
1108/// unreadable. The materialization query then returns those useful rows and
1109/// reports that absence of additional rows is not proof of completeness.
1110fn python_source_export_facts(root: Node<'_>, source: &str) -> SourceExportFacts {
1111    let names_all = |node: Option<Node<'_>>| {
1112        node.is_some_and(|node| {
1113            node.kind() == "identifier" && node_source_text(node, source) == "__all__"
1114        })
1115    };
1116    let mut members = Vec::new();
1117    let mut stated = false;
1118    let mut readable = ReadableSurface::Yes;
1119    let module_bindings = python_module_export_bindings(root, source);
1120    let definitions_complete = !python_module_definition_is_dynamic(root, source);
1121    let mut cursor = root.walk();
1122    for statement in root.named_children(&mut cursor) {
1123        if statement.kind() == "ERROR"
1124            && crate::syntax::python_contains_all_identifier(statement, source)
1125        {
1126            stated = true;
1127            readable = ReadableSurface::No;
1128            continue;
1129        }
1130        if statement.kind() != "expression_statement" {
1131            continue;
1132        }
1133        let mut inner = statement.walk();
1134        for expression in statement.named_children(&mut inner) {
1135            match expression.kind() {
1136                "ERROR" if crate::syntax::python_contains_all_identifier(expression, source) => {
1137                    stated = true;
1138                    readable = ReadableSurface::No;
1139                }
1140                "assignment" | "augmented_assignment" => {
1141                    if !names_all(expression.child_by_field_name("left")) {
1142                        continue;
1143                    }
1144                    stated = true;
1145                    let Some(value) = expression.child_by_field_name("right") else {
1146                        let mut cursor = expression.walk();
1147                        let has_assignment_operator = expression
1148                            .children(&mut cursor)
1149                            .any(|child| child.kind() == "=");
1150                        let annotation_only = expression.kind() == "assignment"
1151                            && expression.child_by_field_name("type").is_some()
1152                            && !has_assignment_operator
1153                            && !expression.has_error();
1154                        if !annotation_only {
1155                            readable = ReadableSurface::No;
1156                        }
1157                        continue;
1158                    };
1159                    if expression.kind() == "assignment" {
1160                        members.clear();
1161                        readable = ReadableSurface::Yes;
1162                    }
1163                    let extends = expression.kind() == "assignment"
1164                        || expression
1165                            .child_by_field_name("operator")
1166                            .is_some_and(|operator| operator.kind() == "+=");
1167                    if !extends
1168                        || python_collect_all_members(value, source, &mut members)
1169                            == ReadableSurface::No
1170                    {
1171                        readable = ReadableSurface::No;
1172                    }
1173                }
1174                "call" => {
1175                    let Some(function) = expression.child_by_field_name("function") else {
1176                        continue;
1177                    };
1178                    if function.kind() == "attribute"
1179                        && names_all(function.child_by_field_name("object"))
1180                    {
1181                        stated = true;
1182                        readable = ReadableSurface::No;
1183                    }
1184                }
1185                _ => {}
1186            }
1187        }
1188    }
1189
1190    let mut names = HashSet::default();
1191    let exports = if stated {
1192        members
1193            .into_iter()
1194            .filter(|member| names.insert(member.name.clone()))
1195            .map(|member| {
1196                let local_definition = match module_bindings.get(member.name.as_str()) {
1197                    Some(binding) => SourceExportLocalDefinition::Defined {
1198                        binding: binding.clone(),
1199                    },
1200                    None if definitions_complete => SourceExportLocalDefinition::Undefined,
1201                    None => SourceExportLocalDefinition::Unknown,
1202                };
1203                SourceExportFact {
1204                    range: member.range,
1205                    exported_name: member.name,
1206                    local_definition,
1207                }
1208            })
1209            .collect()
1210    } else {
1211        Vec::new()
1212    };
1213    SourceExportFacts {
1214        exports,
1215        declared: stated,
1216        complete: !stated || readable == ReadableSurface::Yes,
1217        definitions_complete,
1218    }
1219}
1220
1221/// The names a Python module curates as its public surface: the value of its
1222/// module-level `__all__`.
1223///
1224/// Only the module's own statements are read. An `__all__` inside a function
1225/// is a local rather than the module's surface, and a name the module binds
1226/// conditionally is still bound by the statement this reader already sees.
1227/// A statement that assigns, extends, or mutates `__all__` with anything but a
1228/// list or tuple of plain string literals makes the surface unreadable: the
1229/// members are then unknown, and no import is classified from them.
1230fn python_curated_export_surface(root: Node<'_>, source: &str) -> CuratedExportSurface {
1231    let facts = python_source_export_facts(root, source);
1232    match (facts.declared, facts.complete) {
1233        (false, _) => CuratedExportSurface::Absent,
1234        (true, false) => CuratedExportSurface::Unreadable,
1235        (true, true) => CuratedExportSurface::Listed(
1236            facts
1237                .exports
1238                .into_iter()
1239                .map(|export| export.exported_name)
1240                .collect(),
1241        ),
1242    }
1243}
1244
1245/// Which indirection relation one Python import token participates in.
1246///
1247/// Python's grammar names no re-export, so the relation follows the explicit
1248/// re-export rules the typing ecosystem already enforces (PEP 484 stub
1249/// semantics, applied by pyright and mypy in strict mode), which makes this
1250/// relation agree with what a type checker calls public:
1251///
1252/// 1. A name on the module's `__all__` is a re-export of whatever binding the
1253///    module gives that name.
1254/// 2. The redundant-alias forms `from x import y as y` and `import x as x`
1255///    are re-exports.
1256/// 3. `from x import *` is one star hop that forwards the public surface of
1257///    `x`; the expansion is the import machinery's work, not this producer's,
1258///    so the hop is recorded on the module reference and nothing is
1259///    enumerated here.
1260/// 4. Every other import is an ordinary import, including a plain
1261///    `from .impl import helper` in a package `__init__.py` that states no
1262///    `__all__`. The facade convention alone does not make a name public, and
1263///    a consumer that wants every name a facade imports already has the
1264///    import relation.
1265///
1266/// `None` is the answer for a name whose membership only an unreadable
1267/// `__all__` could settle; the file's relations then report incomplete rather
1268/// than guessing either way.
1269fn python_indirection_relation(
1270    token: Node<'_>,
1271    source: &str,
1272    surface: &CuratedExportSurface,
1273) -> Option<RouteHopKind> {
1274    let statement = nearest_ancestor(token, |kind| {
1275        matches!(
1276            kind,
1277            "import_statement" | "import_from_statement" | "future_import_statement"
1278        )
1279    })?;
1280    // The statement's own child that holds this token: a `module_name` field,
1281    // or one `name` field of the import list.
1282    let mut clause = token;
1283    while let Some(parent) = clause.parent() {
1284        if parent.id() == statement.id() {
1285            break;
1286        }
1287        clause = parent;
1288    }
1289
1290    if field_name_in_parent(statement, clause) == Some("module_name") {
1291        // `from x import a` binds `a`, not `x`, so the module reference
1292        // forwards nothing -- unless the import is the star form, whose one
1293        // hop forwards the whole surface of `x`.
1294        let mut cursor = statement.walk();
1295        let star = statement
1296            .children(&mut cursor)
1297            .any(|child| child.kind() == "wildcard_import");
1298        return Some(if star {
1299            RouteHopKind::ReExport
1300        } else {
1301            RouteHopKind::Import
1302        });
1303    }
1304
1305    let bound = match clause.kind() {
1306        "aliased_import" => {
1307            let name = clause.child_by_field_name("name")?;
1308            let alias = clause.child_by_field_name("alias")?;
1309            if node_source_text(name, source) == node_source_text(alias, source) {
1310                return Some(RouteHopKind::ReExport);
1311            }
1312            alias
1313        }
1314        // `import a.b.c` binds the top package `a`; `from m import a` binds
1315        // the single-segment name the import list spells.
1316        "dotted_name" if statement.kind() == "import_statement" => clause.named_child(0)?,
1317        "dotted_name" => clause,
1318        _ => return None,
1319    };
1320    match surface.lists(node_source_text(bound, source)) {
1321        Some(true) => Some(RouteHopKind::ReExport),
1322        Some(false) => Some(RouteHopKind::Import),
1323        None => None,
1324    }
1325}
1326
1327/// Grammar steps for the shared call-callee spine. A value expression has no step.
1328pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1329    use CalleeSpineStep::{Field, Name};
1330    match kind {
1331        "identifier" => Some(Name),
1332        "attribute" => Some(Field("attribute")),
1333        _ => None,
1334    }
1335}
1336
1337impl StructuralSpec for PythonStructuralSpec {
1338    fn language(&self) -> Language {
1339        Language::Python
1340    }
1341
1342    fn supports_boolean_literal_value(&self) -> bool {
1343        true
1344    }
1345
1346    fn supports_string_literal_parenthesized(&self) -> bool {
1347        true
1348    }
1349
1350    fn supports_string_literal_value(&self) -> bool {
1351        true
1352    }
1353
1354    fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1355        python_string_key_value(node, source)
1356    }
1357
1358    fn supports_assignment_shape(&self) -> bool {
1359        true
1360    }
1361
1362    fn assignment_shape(&self, node: Node<'_>) -> Option<AssignmentShape> {
1363        python_assignment_shape(node)
1364    }
1365
1366    fn supports_integer_literal_value(&self) -> bool {
1367        true
1368    }
1369
1370    fn integer_literal_value(&self, node: Node<'_>, source: &str) -> Option<IntegerLiteralValue> {
1371        python_integer_literal_value(node, source)
1372    }
1373
1374    fn supports_duplicate_dictionary_keys(&self) -> bool {
1375        true
1376    }
1377
1378    fn duplicate_dictionary_key(&self, node: Node<'_>, source: &str) -> Option<bool> {
1379        python_duplicate_dictionary_key(node, source)
1380    }
1381
1382    fn string_literal_parenthesized(&self, node: Node<'_>) -> Option<bool> {
1383        if !matches!(node.kind(), "string" | "concatenated_string") {
1384            return None;
1385        }
1386        let mut current = node;
1387        let mut parenthesized = false;
1388        while let Some(parent) = current.parent() {
1389            if parent.kind() != "parenthesized_expression" {
1390                break;
1391            }
1392            assert_eq!(python_parenthesized_expression_child(parent), Some(current));
1393            current = parent;
1394            parenthesized = true;
1395        }
1396        Some(parenthesized)
1397    }
1398
1399    fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1400        &DEEP_REFERENCE_EDGE_SUPPORT
1401    }
1402
1403    fn identity_route_support(&self) -> &IdentityRouteSupport {
1404        // `import x as y` is an alias, and `python_indirection_relation`
1405        // states which imports re-export (issue #1649).
1406        static SUPPORT: IdentityRouteSupport = DEEP_IDENTITY_AXES
1407            .supported_relation(RouteHopKind::Alias)
1408            .supported_relation(RouteHopKind::Import)
1409            .supported_relation(RouteHopKind::ReExport)
1410            .supported_relation(RouteHopKind::NestedOwner);
1411        &SUPPORT
1412    }
1413
1414    /// Python's one qualified-path chain is `dotted_name`, which is flat
1415    /// rather than left-nested: its named children are the segments in order.
1416    fn qualified_path_root<'tree>(&self, token: Node<'tree>) -> Option<Node<'tree>> {
1417        if token.kind() != "identifier" {
1418            return None;
1419        }
1420        token
1421            .parent()
1422            .filter(|parent| parent.kind() == "dotted_name")
1423    }
1424
1425    fn path_segment_tokens<'tree>(&self, root: Node<'tree>) -> Vec<Node<'tree>> {
1426        if root.kind() != "dotted_name" {
1427            return Vec::new();
1428        }
1429        let mut cursor = root.walk();
1430        root.named_children(&mut cursor)
1431            .filter(|child| child.kind() == "identifier")
1432            .collect()
1433    }
1434
1435    fn curated_export_surface(&self, root: Node<'_>, source: &str) -> CuratedExportSurface {
1436        python_curated_export_surface(root, source)
1437    }
1438
1439    fn indirection_relation(
1440        &self,
1441        token: Node<'_>,
1442        source: &str,
1443        surface: &CuratedExportSurface,
1444    ) -> Option<RouteHopKind> {
1445        python_indirection_relation(token, source, surface)
1446    }
1447
1448    fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1449        match kind {
1450            // The normalized module fact is the file root, not a nested scope.
1451            // Top-level imports and assignments belong to the synthetic file scope.
1452            NormalizedKind::Module => ScopeFormation::NotAScope,
1453            // Python blocks do not introduce local-variable scopes. Names
1454            // assigned in an if/loop/try body remain function or module locals.
1455            NormalizedKind::Block | NormalizedKind::ForLoop | NormalizedKind::WhileLoop => {
1456                ScopeFormation::NotAScope
1457            }
1458            _ => default_scope_formation(kind),
1459        }
1460    }
1461
1462    fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1463        PYTHON_KIND_TABLE
1464    }
1465
1466    fn refine_kind(
1467        &self,
1468        node: Node<'_>,
1469        kind: NormalizedKind,
1470        _enclosing: Option<NormalizedKind>,
1471        _source: &str,
1472        _context: &CallSiteContext,
1473    ) -> NormalizedKind {
1474        match kind {
1475            NormalizedKind::Identifier if python_identifier_is_parameter(node) => {
1476                NormalizedKind::Parameter
1477            }
1478            NormalizedKind::Function if python_definition_is_method(node) => NormalizedKind::Method,
1479            NormalizedKind::Comparison if python_comparison_shape(node).operator_count > 1 => {
1480                NormalizedKind::ComparisonChain
1481            }
1482            _ => kind,
1483        }
1484    }
1485
1486    fn should_extract(&self, node: Node<'_>, kind: NormalizedKind) -> bool {
1487        if kind == NormalizedKind::Module && node.byte_range().is_empty() {
1488            return false;
1489        }
1490        match node.kind() {
1491            "parenthesized_expression" | "unary_operator" => {
1492                kind == NormalizedKind::Literal && python_comparison_constant(node)
1493            }
1494            _ => kind != NormalizedKind::Assignment || node.child_by_field_name("right").is_some(),
1495        }
1496    }
1497
1498    fn supports_kind(&self, kind: NormalizedKind) -> bool {
1499        brokk_bifrost_core::analyzer::structural::spec::supports_kind_from_table(
1500            kind,
1501            &[
1502                NormalizedKind::Method,
1503                // Module facts select the parser's file root for Python policies.
1504                NormalizedKind::Module,
1505                NormalizedKind::Parameter,
1506                NormalizedKind::SyntaxError,
1507                NormalizedKind::ComparisonChain,
1508                NormalizedKind::RegexPattern,
1509                NormalizedKind::RegexSequence,
1510                NormalizedKind::RegexGroup,
1511                NormalizedKind::RegexAlternation,
1512                NormalizedKind::RegexCharacterClass,
1513                NormalizedKind::RegexCharacterRange,
1514                NormalizedKind::RegexQuantifier,
1515                NormalizedKind::RegexAnchor,
1516                NormalizedKind::RegexEscape,
1517                NormalizedKind::RegexLiteral,
1518                NormalizedKind::RegexWildcard,
1519                NormalizedKind::RegexRiskHostname,
1520                NormalizedKind::RegexRiskRange,
1521                NormalizedKind::RegexRiskBadTag,
1522                NormalizedKind::RegexRiskExponential,
1523                NormalizedKind::RegexIncomplete,
1524                NormalizedKind::RegexRiskBackspaceEscape,
1525                NormalizedKind::RegexRiskDuplicateClassCharacter,
1526                NormalizedKind::RegexRiskUnmatchableCaret,
1527                NormalizedKind::RegexRiskUnmatchableDollar,
1528                NormalizedKind::RegexRiskIncompleteSpecialGroup,
1529                NormalizedKind::RegexRiskPolynomial,
1530            ],
1531            self.kind_table(),
1532        )
1533    }
1534
1535    fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1536        &PYTHON_OCCURRENCE_ROLE_SUPPORT
1537    }
1538
1539    fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1540        &DEEP_LEXICAL_ENVIRONMENT_SUPPORT
1541    }
1542
1543    fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1544        &PYTHON_MATERIALIZATION_SUPPORT
1545    }
1546
1547    fn source_export_facts(&self, root: Node<'_>, source: &str) -> Option<SourceExportFacts> {
1548        Some(python_source_export_facts(root, source))
1549    }
1550
1551    fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1552        python_binding_activation(binder, scope)
1553    }
1554
1555    /// Python only classifies a scope segment inside a `dotted_name`, and every
1556    /// non-tail segment of a dotted name is a module.
1557    fn occurrence_namespace(
1558        &self,
1559        role: OccurrenceRole,
1560        declares: Option<NormalizedKind>,
1561    ) -> Option<Namespace> {
1562        module_path_occurrence_namespace(role, declares)
1563    }
1564
1565    fn embedded_leaf_facts(
1566        &self,
1567        node: Node<'_>,
1568        kind: NormalizedKind,
1569        source: &str,
1570        cancellation: Option<&CancellationToken>,
1571    ) -> Vec<EmbeddedLeafFact> {
1572        if kind == NormalizedKind::Module {
1573            return crate::regex::module_assigned_facts(node, source);
1574        }
1575        if kind == NormalizedKind::Call {
1576            return crate::regex::embedded_facts(node, source);
1577        }
1578        if kind != NormalizedKind::StringLiteral {
1579            return Vec::new();
1580        }
1581
1582        let mut facts = crate::regex::embedded_facts(node, source);
1583        if node.kind() != "string" || !python_node_is_in_annotation(node) {
1584            return facts;
1585        }
1586
1587        facts.extend(
1588            python_deferred_annotation_identifier_ranges(node, source, cancellation)
1589                .unwrap_or_default()
1590                .into_iter()
1591                .map(|range| EmbeddedLeafFact {
1592                    kind: NormalizedKind::Identifier,
1593                    range,
1594                    parent: None,
1595                    construct: None,
1596                    occurrence_role: Some(OccurrenceRole::TypeOperand),
1597                }),
1598        );
1599        facts
1600    }
1601
1602    fn comparison_operator_spans(
1603        &self,
1604        node: Node<'_>,
1605        step: &mut dyn FnMut() -> bool,
1606    ) -> Option<Vec<Span>> {
1607        if !step() || node.kind() != "comparison_operator" || node.has_error() || node.is_missing()
1608        {
1609            return None;
1610        }
1611        let mut operators = Vec::new();
1612        let mut operands = 0usize;
1613        let complete = visit_python_comparison_children(node, |child, is_operator| {
1614            if !step() || child.is_missing() || child.has_error() {
1615                return false;
1616            }
1617            if is_operator {
1618                if child.start_byte() >= child.end_byte() {
1619                    return false;
1620                }
1621                operators.push(Span {
1622                    start_byte: child.start_byte(),
1623                    end_byte: child.end_byte(),
1624                });
1625            } else if child.is_named() && !child.is_extra() {
1626                operands += 1;
1627            }
1628            true
1629        });
1630        (complete
1631            && !operators.is_empty()
1632            && operands == operators.len().saturating_add(1)
1633            && step())
1634        .then_some(operators)
1635    }
1636
1637    fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1638        if let Some(role) = python_occurrence_role(node) {
1639            sink.occurrence_role(node, role);
1640        }
1641        match kind {
1642            NormalizedKind::Call => {
1643                if let Some(function) = node.child_by_field_name("function") {
1644                    // A call's own name is its callee's, so
1645                    // { "kind": "call", "name": "eval" } reads naturally.
1646                    attach_terminal_callee(
1647                        sink,
1648                        function,
1649                        terminal_callee_leaf(function, callee_spine_step),
1650                    );
1651                    if function.kind() == "attribute"
1652                        && let Some(object) = function.child_by_field_name("object")
1653                    {
1654                        attach_role_with_derived_name(
1655                            sink,
1656                            Role::Receiver,
1657                            object,
1658                            expression_name_node,
1659                        );
1660                    }
1661                }
1662                if let Some(arguments) = node.child_by_field_name("arguments") {
1663                    for index in 0..arguments.named_child_count() {
1664                        if !sink.should_continue() {
1665                            break;
1666                        }
1667                        let Some(argument) = arguments.named_child(index) else {
1668                            continue;
1669                        };
1670                        if argument.is_extra() {
1671                            continue;
1672                        }
1673                        match argument.kind() {
1674                            "keyword_argument" => {
1675                                if let (Some(keyword), Some(value)) = (
1676                                    argument.child_by_field_name("name"),
1677                                    argument.child_by_field_name("value"),
1678                                ) {
1679                                    sink.kwarg(keyword, value);
1680                                }
1681                            }
1682                            "list_splat" | "dictionary_splat"
1683                                if python_literal_pack_arguments(argument, sink) => {}
1684                            _ => attach_argument_role_with_derived_name(
1685                                sink,
1686                                argument,
1687                                expression_name_node,
1688                            ),
1689                        }
1690                    }
1691                }
1692            }
1693            NormalizedKind::FieldAccess => {
1694                if let Some(attribute) = node.child_by_field_name("attribute") {
1695                    sink.set_name(attribute);
1696                    sink.role_named(Role::Field, attribute, attribute);
1697                }
1698                if let Some(object) = node.child_by_field_name("object") {
1699                    attach_role_with_derived_name(sink, Role::Object, object, expression_name_node);
1700                }
1701            }
1702            NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Class => {
1703                if let Some(name) = node.child_by_field_name("name") {
1704                    sink.set_name(name);
1705                }
1706                attach_decorators(sink, node);
1707            }
1708            NormalizedKind::Assignment => {
1709                if let Some(left) = node.child_by_field_name("left") {
1710                    attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1711                }
1712                if let Some(right) = node.child_by_field_name("right") {
1713                    attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
1714                }
1715            }
1716            NormalizedKind::BinaryExpression => {
1717                if let Some(left) = node.child_by_field_name("left") {
1718                    attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1719                }
1720                if let Some(right) = node.child_by_field_name("right") {
1721                    attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
1722                }
1723                if let Some(operator) = node.child_by_field_name("operator") {
1724                    sink.role_named(Role::Operator, operator, operator);
1725                }
1726            }
1727            NormalizedKind::Comparison => {
1728                let shape = python_comparison_shape(node);
1729                if shape.operator_count == 1 && shape.operand_count == 2 {
1730                    if let Some(left) = shape.left {
1731                        attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1732                    }
1733                    if let Some(right) = shape.right {
1734                        attach_role_with_derived_name(
1735                            sink,
1736                            Role::Right,
1737                            right,
1738                            expression_name_node,
1739                        );
1740                    }
1741                    if let Some(operator) = shape.operator {
1742                        sink.role_named(Role::Operator, operator, operator);
1743                    }
1744                }
1745            }
1746            NormalizedKind::Assertion => {
1747                if let Some(test) = python_first_named_operand(node) {
1748                    sink.role(Role::Test, test);
1749                }
1750            }
1751            NormalizedKind::If => {
1752                // The grammar names the `if` statement condition, but its
1753                // conditional expression has no fields: its three named
1754                // operands are value, condition, and alternative. Grammar
1755                // extras (`comment`, `line_continuation`) can be interleaved
1756                // between them, so select among the operands (#4797).
1757                let test = if node.kind() == "conditional_expression" {
1758                    let [consequence, test, alternative] =
1759                        python_conditional_expression_operands(node)
1760                            .expect("conditional expression operands");
1761                    sink.role(Role::Consequence, consequence);
1762                    sink.role(Role::Alternative, alternative);
1763                    Some(test)
1764                } else {
1765                    node.child_by_field_name("condition")
1766                };
1767                if let Some(test) = test {
1768                    sink.role(Role::Test, test);
1769                }
1770            }
1771            NormalizedKind::Return => {
1772                if let Some(value) = python_first_named_operand(node) {
1773                    sink.role(Role::Value, value);
1774                }
1775            }
1776            NormalizedKind::Throw => {
1777                if let Some(value) = python_first_named_operand(node) {
1778                    sink.role(Role::Value, value);
1779                }
1780            }
1781            NormalizedKind::Import => match node.kind() {
1782                "import_from_statement" => {
1783                    if let Some(module) = node.child_by_field_name("module_name") {
1784                        sink.role_named(Role::Module, module, module);
1785                    }
1786                }
1787                _ => {
1788                    for index in 0..node.named_child_count() {
1789                        if !sink.should_continue() {
1790                            break;
1791                        }
1792                        let Some(child) = node.named_child(index) else {
1793                            continue;
1794                        };
1795                        match child.kind() {
1796                            "dotted_name" => sink.role_named(Role::Module, child, child),
1797                            "aliased_import" => {
1798                                if let Some(name) = child.child_by_field_name("name") {
1799                                    sink.role_named(Role::Module, name, name);
1800                                }
1801                            }
1802                            _ => {}
1803                        }
1804                    }
1805                }
1806            },
1807            NormalizedKind::Identifier | NormalizedKind::Parameter => sink.set_name(node),
1808            NormalizedKind::Decorator => {
1809                if let Some(name) = python_first_named_operand(node).and_then(expression_name_node)
1810                {
1811                    sink.set_name(name);
1812                }
1813            }
1814            NormalizedKind::ForLoop => {
1815                if let Some(right) = node.child_by_field_name("right") {
1816                    attach_role_with_derived_name(
1817                        sink,
1818                        Role::Iterable,
1819                        right,
1820                        expression_name_node,
1821                    );
1822                }
1823            }
1824            NormalizedKind::CollectionLiteral
1825            | NormalizedKind::TupleLiteral
1826            | NormalizedKind::ListLiteral => {
1827                for index in 0..node.named_child_count() {
1828                    let Some(child) = node.named_child(index) else {
1829                        continue;
1830                    };
1831                    if child.is_extra() {
1832                        continue;
1833                    }
1834                    let element = python_unwrap_parenthesized(child);
1835                    attach_role_with_derived_name(
1836                        sink,
1837                        Role::Element,
1838                        element,
1839                        expression_name_node,
1840                    );
1841                }
1842            }
1843            NormalizedKind::ObjectProperty => {
1844                if let Some(key) = node.child_by_field_name("key") {
1845                    sink.role(Role::Key, key);
1846                }
1847                if let Some(value) = node.child_by_field_name("value") {
1848                    sink.role(Role::Value, value);
1849                }
1850            }
1851            NormalizedKind::StringLiteral if node.kind() == "concatenated_string" => {
1852                for index in 0..node.named_child_count() {
1853                    let Some(part) = node.named_child(index) else {
1854                        continue;
1855                    };
1856                    if part.kind() == "string" {
1857                        sink.role(Role::Part, part);
1858                    }
1859                }
1860            }
1861            _ => {}
1862        }
1863    }
1864}
1865
1866#[cfg(test)]
1867mod tests {
1868    use super::*;
1869    use brokk_bifrost_test_macros::hardwon;
1870
1871    fn node_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
1872        let mut pending = vec![root];
1873        while let Some(node) = pending.pop() {
1874            if node.kind() == kind {
1875                return Some(node);
1876            }
1877            let mut cursor = node.walk();
1878            pending.extend(node.named_children(&mut cursor));
1879        }
1880        None
1881    }
1882
1883    fn string_literal_parenthesization(root: Node<'_>) -> (usize, usize) {
1884        let mut pending = vec![root];
1885        let mut string_count = 0;
1886        let mut parenthesized_count = 0;
1887        while let Some(node) = pending.pop() {
1888            if matches!(node.kind(), "string" | "concatenated_string") {
1889                string_count += 1;
1890                if PYTHON_STRUCTURAL_SPEC
1891                    .string_literal_parenthesized(node)
1892                    .expect("Python string-literal parenthesization is supported")
1893                {
1894                    parenthesized_count += 1;
1895                }
1896            }
1897            let mut cursor = node.walk();
1898            pending.extend(node.named_children(&mut cursor));
1899        }
1900        (string_count, parenthesized_count)
1901    }
1902
1903    fn parse_python(source: &str) -> tree_sitter::Tree {
1904        crate::declarations::parse_python_tree(source).expect("Python source tree")
1905    }
1906
1907    #[test]
1908    #[hardwon(
1909        "6b123df7cc68: quicksilver round 6 on brokk-bifrost-python: __all__ replacement retained old exports"
1910    )]
1911    fn qs6_all_replacement_and_extension() {
1912        for (source, expected) in [
1913            ("__all__ = ['old']\n__all__ = ['new']\n", vec!["new"]),
1914            (
1915                "__all__ = ['old']\n__all__ += ['new']\n",
1916                vec!["old", "new"],
1917            ),
1918            ("__all__ = computed\n__all__ = ['new']\n", vec!["new"]),
1919        ] {
1920            let tree = parse_python(source);
1921            let facts = python_source_export_facts(tree.root_node(), source);
1922            assert_eq!(
1923                facts
1924                    .exports
1925                    .iter()
1926                    .map(|e| e.exported_name.as_str())
1927                    .collect::<Vec<_>>(),
1928                expected
1929            );
1930            assert!(facts.complete, "{source}: {facts:?}");
1931        }
1932    }
1933
1934    #[test]
1935    #[hardwon(
1936        "6b123df7cc68: quicksilver round 6 on brokk-bifrost-python: named extras made literal exports and concatenations unreadable"
1937    )]
1938    fn qs6_literal_extras_grammar_boundary() {
1939        let source =
1940            "__all__ = [\n 'first', # comment\n 'second',\n]\nVALUE = ('a' # comment\n 'b')\n";
1941        let tree = parse_python(source);
1942        assert!(!tree.root_node().has_error());
1943        let facts = python_source_export_facts(tree.root_node(), source);
1944        assert!(facts.complete, "{facts:?}");
1945        assert_eq!(
1946            facts
1947                .exports
1948                .iter()
1949                .map(|e| e.exported_name.as_str())
1950                .collect::<Vec<_>>(),
1951            ["first", "second"]
1952        );
1953        let concat = node_of_kind(tree.root_node(), "concatenated_string").unwrap();
1954        assert_eq!(
1955            python_string_key_value(concat, source).as_deref(),
1956            Some("ab")
1957        );
1958    }
1959
1960    #[test]
1961    fn qs6_structural_literal_grammar_matrix() {
1962        // Expected values follow CPython ast literal values; f/named-escape
1963        // strings remain unsupported rather than inventing a decoded value.
1964        for (literal, expected) in [
1965            (r#""{{key}}""#, Some("{{key}}")),
1966            (r#""{key}""#, Some("{key}")),
1967            (r#"f"{{key}}""#, None),
1968            (r#"f"plain""#, None),
1969            (r#"f"{key}""#, None),
1970            (r#""\N{LATIN CAPITAL LETTER A}""#, None),
1971            (r#""A""#, Some("A")),
1972            (
1973                r#""\n{LATIN CAPITAL LETTER A}""#,
1974                Some("\n{LATIN CAPITAL LETTER A}"),
1975            ),
1976        ] {
1977            let source = format!("value = {literal}\n");
1978            let tree = parse_python(&source);
1979            let node = node_of_kind(tree.root_node(), "string").unwrap();
1980            assert_eq!(
1981                python_string_key_value(node, &source).as_deref(),
1982                expected,
1983                "{literal}"
1984            );
1985        }
1986        for (rhs, kind, count, starred) in [
1987            ("[1, 2]", AssignmentSequenceKind::List, 2, false),
1988            ("(1, 2)", AssignmentSequenceKind::Tuple, 2, false),
1989            ("1, 2", AssignmentSequenceKind::Tuple, 2, false),
1990            ("[1, *items]", AssignmentSequenceKind::List, 1, true),
1991            ("(1, *items)", AssignmentSequenceKind::Tuple, 1, true),
1992        ] {
1993            let source = format!("a, *b = {rhs}\n");
1994            let tree = parse_python(&source);
1995            let assignment = node_of_kind(tree.root_node(), "assignment").unwrap();
1996            assert_eq!(
1997                python_assignment_value_shape(assignment.child_by_field_name("right").unwrap()),
1998                Some((kind, count, starred)),
1999                "{rhs}"
2000            );
2001        }
2002        for (value, constant) in [
2003            ("((True))", true),
2004            ("(False)", true),
2005            ("((None))", true),
2006            ("-None", false),
2007            ("-1", true),
2008            ("f'{value}'", false),
2009        ] {
2010            let source = format!("left == {value}\n");
2011            let tree = parse_python(&source);
2012            let comparison = node_of_kind(tree.root_node(), "comparison_operator").unwrap();
2013            let operand = comparison.named_child(1).unwrap();
2014            assert_eq!(python_comparison_constant(operand), constant, "{value}");
2015        }
2016    }
2017
2018    #[test]
2019    fn qs6_source_export_namespace_shapes() {
2020        for (statement, dynamic) in [
2021            ("", false),
2022            (
2023                "import sys as runtime\nsetattr(runtime.modules[__name__], 'late', 1)\n",
2024                true,
2025            ),
2026            ("setattr(__import__(__name__), 'late', 1)\n", true),
2027            (
2028                "import sys as runtime\nalias = runtime.modules[__name__]\nsetattr(alias, 'late', 1)\n",
2029                true,
2030            ),
2031            ("globals()['late'] = 1\n", true),
2032        ] {
2033            let source = format!("__all__ = ['late']\n{statement}");
2034            let tree = parse_python(&source);
2035            let facts = python_source_export_facts(tree.root_node(), &source);
2036            assert_eq!(facts.definitions_complete, !dynamic, "{source}");
2037            assert!(
2038                matches!(
2039                    facts.exports[0].local_definition,
2040                    SourceExportLocalDefinition::Unknown
2041                ) == dynamic,
2042                "{facts:?}"
2043            );
2044            if !dynamic {
2045                assert!(matches!(
2046                    facts.exports[0].local_definition,
2047                    SourceExportLocalDefinition::Undefined
2048                ));
2049            }
2050        }
2051        let source = "__all__ =\n";
2052        let tree = parse_python(source);
2053        assert!(!python_source_export_facts(tree.root_node(), source).definitions_complete);
2054    }
2055
2056    #[test]
2057    fn qs6_binding_kind_grammar_inventory() {
2058        let source = "def run(parameter):\n scalar = 1\n left, right = (1, 2)\n for loop in ():\n  pass\n values = [comp for comp in ()]\n";
2059        let tree = parse_python(source);
2060        let mut pending = vec![tree.root_node()];
2061        let mut found = Vec::new();
2062        while let Some(node) = pending.pop() {
2063            if node.kind() == "identifier" {
2064                let text = node_source_text(node, source);
2065                if text == "comp" && node.parent().unwrap().kind() != "for_in_clause" {
2066                    continue;
2067                }
2068                if let Some(expected) = match text {
2069                    "parameter" => Some(BindingKind::Parameter),
2070                    "scalar" | "left" | "right" => Some(BindingKind::Local),
2071                    "loop" | "comp" => Some(BindingKind::LoopVariable),
2072                    _ => None,
2073                } {
2074                    let activation =
2075                        python_binding_activation(node, node_range(tree.root_node())).unwrap();
2076                    assert_eq!(activation.kind, expected, "{text}: {}", node.to_sexp());
2077                    found.push(text);
2078                }
2079            }
2080            let mut cursor = node.walk();
2081            pending.extend(node.named_children(&mut cursor));
2082        }
2083        assert_eq!(found.len(), 6);
2084    }
2085
2086    #[test]
2087    fn missing_python_all_value_is_incomplete_after_parser_recovery() {
2088        let source = "__all__ =\n";
2089        let mut parser = tree_sitter::Parser::new();
2090        parser
2091            .set_language(&tree_sitter_python::LANGUAGE.into())
2092            .expect("Python parser");
2093        let tree = parser.parse(source, None).expect("Python source tree");
2094        let root = tree.root_node();
2095        let facts = python_source_export_facts(root, source);
2096
2097        assert!(facts.declared, "recovered AST: {}", root.to_sexp());
2098        assert!(!facts.complete, "recovered AST: {}", root.to_sexp());
2099    }
2100
2101    #[test]
2102    fn parenthesized_string_literals_allow_comment_children() {
2103        for (case, source) in [
2104            (
2105                "one comment between concatenated strings",
2106                "VALUE = (\n    r\"\\.\"  # dot\n    r\"(?!-)\"\n)\n",
2107            ),
2108            (
2109                "several comments between concatenated strings",
2110                "VALUE = (\n    r\"a\"  # first\n    # between\n    r\"b\"  # second\n    # trailing\n)\n",
2111            ),
2112            (
2113                "comment before the string",
2114                "VALUE = (\n    # starts below\n    r\"value\"\n)\n",
2115            ),
2116            (
2117                "nested parentheses with a comment",
2118                "VALUE = (\n    (\n        # nested\n        r\"value\"  # closes inner value\n    )\n)\n",
2119            ),
2120        ] {
2121            let tree = parse_python(source);
2122            let root = tree.root_node();
2123            assert!(!root.has_error(), "{case}: {}", root.to_sexp());
2124            let (string_count, parenthesized_count) = string_literal_parenthesization(root);
2125            assert!(string_count > 0, "{case}: {}", root.to_sexp());
2126            assert!(parenthesized_count > 0, "{case}: {}", root.to_sexp());
2127        }
2128
2129        let tree = parse_python("VALUE = (\n    # comment\n    1  # trailing comment\n)\n");
2130        let mut pending = vec![tree.root_node()];
2131        let parenthesized = loop {
2132            let node = pending.pop().expect("parenthesized expression exists");
2133            if node.kind() == "parenthesized_expression" {
2134                break node;
2135            }
2136            let mut cursor = node.walk();
2137            pending.extend(node.named_children(&mut cursor));
2138        };
2139        assert!(python_comparison_constant(parenthesized));
2140        assert_eq!(python_unwrap_parenthesized(parenthesized).kind(), "integer");
2141    }
2142
2143    #[test]
2144    #[hardwon(
2145        "#4797: a backslash continuation before `else` counted as a fourth conditional-expression operand"
2146    )]
2147    fn conditional_expression_selects_condition_past_extras() {
2148        for (case, source) in [
2149            (
2150                "line continuation",
2151                "def f(a, b):\n    return a if b \\\n        else c\n",
2152            ),
2153            (
2154                "comment",
2155                "def f(a, b, c):\n    return (a if b\n            # why not\n            else c)\n",
2156            ),
2157            (
2158                "plain",
2159                "def f(a, b, c):\n    return (a if b\n            else c)\n",
2160            ),
2161        ] {
2162            let tree = parse_python(source);
2163            let root = tree.root_node();
2164            assert!(!root.has_error(), "{case}: {}", root.to_sexp());
2165            let conditional = node_of_kind(root, "conditional_expression").unwrap_or_else(|| {
2166                panic!("{case}: no conditional expression in {}", root.to_sexp())
2167            });
2168            let test = python_conditional_expression_operands(conditional)
2169                .expect("conditional expression operands")[1];
2170            assert_eq!(test.kind(), "identifier", "{case}");
2171            assert_eq!(
2172                test.utf8_text(source.as_bytes()).unwrap(),
2173                "b",
2174                "{case}: {}",
2175                root.to_sexp()
2176            );
2177        }
2178    }
2179
2180    #[test]
2181    #[hardwon(
2182        "#4797: a line continuation inside parentheses hid the sole operand from the parenthesization check"
2183    )]
2184    fn parenthesized_expression_skips_line_continuation() {
2185        let source = "VALUE = (\n    \"value\"\\\n)\n";
2186        let tree = parse_python(source);
2187        let root = tree.root_node();
2188        assert!(!root.has_error(), "{}", root.to_sexp());
2189        let string = node_of_kind(root, "string").expect("string literal");
2190        assert_eq!(
2191            PYTHON_STRUCTURAL_SPEC.string_literal_parenthesized(string),
2192            Some(true),
2193            "{}",
2194            root.to_sexp()
2195        );
2196    }
2197
2198    #[test]
2199    #[hardwon("#4797: a line continuation in a dict literal disabled duplicate-key detection")]
2200    fn duplicate_dictionary_key_skips_extras() {
2201        let source = "D = {1: 2, \\\n 1: 3}\n";
2202        let tree = parse_python(source);
2203        let root = tree.root_node();
2204        assert!(!root.has_error(), "{}", root.to_sexp());
2205        let dictionary = node_of_kind(root, "dictionary").expect("dictionary literal");
2206        let mut cursor = dictionary.walk();
2207        let pairs: Vec<Node<'_>> = dictionary
2208            .named_children(&mut cursor)
2209            .filter(|child| child.kind() == "pair")
2210            .collect();
2211        assert_eq!(pairs.len(), 2, "{}", root.to_sexp());
2212        assert_eq!(
2213            PYTHON_STRUCTURAL_SPEC.duplicate_dictionary_key(pairs[1], source),
2214            Some(true),
2215            "{}",
2216            root.to_sexp()
2217        );
2218    }
2219
2220    #[test]
2221    fn django_domain_validator_strings_parse_without_parenthesization_panics() {
2222        let source = r#"
2223class DomainNameValidator(RegexValidator):
2224    message = _("Enter a valid domain name.")
2225    ul = "\u00a1-\uffff"  # Unicode letters range (must not be a raw string).
2226    # Host patterns.
2227    hostname_re = (
2228        r"[a-z" + ul + r"0-9](?:[a-z" + ul + r"0-9-]{0,61}[a-z" + ul + r"0-9])?"
2229    )
2230    # Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1.
2231    domain_re = r"(?:\.(?!-)[a-z" + ul + r"0-9-]{1,63}(?<!-))*"
2232    # Top-level domain.
2233    tld_no_fqdn_re = (
2234        r"\."  # dot
2235        r"(?!-)"  # can't start with a dash
2236        r"(?:[a-z" + ul + "-]{2,63}"  # domain label
2237        r"|xn--[a-z0-9]{1,59})"  # or punycode label
2238        r"(?<!-)"  # can't end with a dash
2239    )
2240    tld_re = tld_no_fqdn_re + r"\.?"
2241    ascii_only_hostname_re = r"[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
2242    ascii_only_domain_re = r"(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-))*"
2243    ascii_only_tld_re = (
2244        r"\."  # dot
2245        r"(?!-)"  # can't start with a dash
2246        r"(?:[a-zA-Z0-9-]{2,63})"  # domain label
2247        r"(?<!-)"  # can't end with a dash
2248        r"\.?"  # may have a trailing dot
2249    )
2250"#;
2251        let tree = parse_python(source);
2252        let root = tree.root_node();
2253        assert!(!root.has_error(), "{}", root.to_sexp());
2254        let (string_count, parenthesized_count) = string_literal_parenthesization(root);
2255        assert!(string_count > 0, "{}", root.to_sexp());
2256        assert!(parenthesized_count > 0, "{}", root.to_sexp());
2257    }
2258
2259    #[test]
2260    fn decoded_string_values_match_python_escape_and_concatenation_semantics() {
2261        for (source, expected) in [
2262            (
2263                r#"MIDDLEWARE = "\x64jango.middleware.csrf.CsrfViewMiddleware""#,
2264                Some("django.middleware.csrf.CsrfViewMiddleware"),
2265            ),
2266            (
2267                r#"MIDDLEWARE = "django.middleware.csrf." "CsrfViewMiddleware""#,
2268                Some("django.middleware.csrf.CsrfViewMiddleware"),
2269            ),
2270            (
2271                r#"MIDDLEWARE = b"django.middleware.csrf.CsrfViewMiddleware""#,
2272                None,
2273            ),
2274        ] {
2275            let mut parser = tree_sitter::Parser::new();
2276            parser
2277                .set_language(&tree_sitter_python::LANGUAGE.into())
2278                .expect("Python parser");
2279            let tree = parser.parse(source, None).expect("Python source tree");
2280            let mut pending = vec![tree.root_node()];
2281            let assignment = loop {
2282                let node = pending.pop().expect("Python tree has an assignment");
2283                if node.kind() == "assignment" {
2284                    break node;
2285                }
2286                let mut cursor = node.walk();
2287                pending.extend(node.named_children(&mut cursor));
2288            };
2289            let value = assignment
2290                .child_by_field_name("right")
2291                .expect("assignment value");
2292            assert_eq!(
2293                python_string_key_value(value, source).as_deref(),
2294                expected,
2295                "{}",
2296                tree.root_node().to_sexp()
2297            );
2298        }
2299    }
2300    #[test]
2301    fn qs6_flat_import_paths_follow_grammar_fields_and_byte_ranges() {
2302        let source = "# \u{e9}\nimport alpha.beta.gamma as alias\nfrom alpha.beta import gamma\nalpha.beta\n";
2303        let tree = parse_python(source);
2304        let root = tree.root_node();
2305        assert!(!root.has_error(), "{}", root.to_sexp());
2306        let import = root.named_child(1).unwrap();
2307        assert_eq!(import.kind(), "import_statement");
2308        let aliased = import.child_by_field_name("name").unwrap();
2309        assert_eq!(aliased.kind(), "aliased_import");
2310        let dotted = aliased.child_by_field_name("name").unwrap();
2311        assert_eq!(dotted.kind(), "dotted_name");
2312        let tokens = PYTHON_STRUCTURAL_SPEC.path_segment_tokens(dotted);
2313        let start = source.find("alpha").unwrap();
2314        assert_eq!(
2315            tokens
2316                .iter()
2317                .map(|node| (node.kind(), node.byte_range()))
2318                .collect::<Vec<_>>(),
2319            vec![
2320                ("identifier", start..start + 5),
2321                ("identifier", start + 6..start + 10),
2322                ("identifier", start + 11..start + 16)
2323            ]
2324        );
2325        for token in tokens {
2326            assert_eq!(
2327                PYTHON_STRUCTURAL_SPEC.qualified_path_root(token),
2328                Some(dotted)
2329            );
2330        }
2331        let alias = aliased.child_by_field_name("alias").unwrap();
2332        assert_eq!(node_source_text(alias, source), "alias");
2333        assert!(PYTHON_STRUCTURAL_SPEC.qualified_path_root(alias).is_none());
2334        let from = root.named_child(2).unwrap();
2335        let module = from.child_by_field_name("module_name").unwrap();
2336        assert_eq!(module.kind(), "dotted_name");
2337        assert_eq!(
2338            PYTHON_STRUCTURAL_SPEC
2339                .path_segment_tokens(module)
2340                .iter()
2341                .map(|n| node_source_text(*n, source))
2342                .collect::<Vec<_>>(),
2343            vec!["alpha", "beta"]
2344        );
2345        let attribute = root.named_child(3).unwrap().named_child(0).unwrap();
2346        assert_eq!(attribute.kind(), "attribute");
2347        assert!(
2348            PYTHON_STRUCTURAL_SPEC
2349                .path_segment_tokens(attribute)
2350                .is_empty()
2351        );
2352        assert!(
2353            PYTHON_STRUCTURAL_SPEC
2354                .qualified_path_root(attribute.child_by_field_name("attribute").unwrap())
2355                .is_none()
2356        );
2357    }
2358
2359    #[test]
2360    fn qs6_syntactic_reexports_obey_redundant_aliases_and_all_membership() {
2361        for (all, listed, ordinary) in [
2362            ("", Some(RouteHopKind::Import), Some(RouteHopKind::Import)),
2363            (
2364                "__all__ = ['z']\n",
2365                Some(RouteHopKind::ReExport),
2366                Some(RouteHopKind::Import),
2367            ),
2368            ("__all__ = dynamic()\n", None, None),
2369        ] {
2370            let source = format!(
2371                "# \u{e9}\nimport a.b\nimport x as x\nfrom x import y as y\nfrom x import y as z\nfrom x import *\n{all}"
2372            );
2373            let tree = parse_python(&source);
2374            let root = tree.root_node();
2375            assert!(!root.has_error(), "{}", root.to_sexp());
2376            let surface = PYTHON_STRUCTURAL_SPEC.curated_export_surface(root, &source);
2377            for (index, field, expected) in [
2378                (1, "name", ordinary),
2379                (2, "name", Some(RouteHopKind::ReExport)),
2380                (3, "name", Some(RouteHopKind::ReExport)),
2381                (4, "name", listed),
2382                (5, "module_name", Some(RouteHopKind::ReExport)),
2383            ] {
2384                let clause = root
2385                    .named_child(index)
2386                    .unwrap()
2387                    .child_by_field_name(field)
2388                    .unwrap();
2389                let name = if clause.kind() == "aliased_import" {
2390                    clause.child_by_field_name("name").unwrap()
2391                } else {
2392                    clause
2393                };
2394                assert_eq!(name.kind(), "dotted_name");
2395                let token = name.named_child(0).unwrap();
2396                assert_eq!(token.kind(), "identifier");
2397                assert_eq!(
2398                    PYTHON_STRUCTURAL_SPEC.indirection_relation(token, &source, &surface),
2399                    expected,
2400                    "{}",
2401                    clause.to_sexp()
2402                );
2403            }
2404        }
2405    }
2406}