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