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_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
106fn 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 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 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
489pub 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
525fn 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
544fn 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
581fn 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
619pub 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 ("parenthesized_expression", NormalizedKind::Literal),
640 ("unary_operator", NormalizedKind::Literal),
641 ("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 ("block", NormalizedKind::Block),
668 ("decorator", NormalizedKind::Decorator),
669 ("comment", NormalizedKind::Comment),
670];
671
672fn 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
705fn 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
727fn 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 "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
785fn 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
813fn 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 "block" | "decorated_definition" => current = parent,
830 _ => return false,
831 }
832 }
833 false
834}
835
836fn python_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
847 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 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#[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
938fn 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
1105fn 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
1221fn 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
1245fn 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 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 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 "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
1327pub 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 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 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 NormalizedKind::Module => ScopeFormation::NotAScope,
1453 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 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 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 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 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 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}