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_deferred_annotation_identifier_ranges,
48 python_keyword_argument_label, python_node_is_in_annotation, python_plain_string_literal,
49};
50
51#[derive(Debug, Default)]
52pub struct PythonStructuralSpec;
53
54pub static PYTHON_STRUCTURAL_SPEC: PythonStructuralSpec = PythonStructuralSpec;
55
56fn python_first_named_operand(node: Node<'_>) -> Option<Node<'_>> {
62 let mut cursor = node.walk();
63 node.named_children(&mut cursor)
64 .find(|child| !child.is_extra())
65}
66
67fn python_conditional_expression_test(node: Node<'_>) -> Node<'_> {
75 assert_eq!(node.kind(), "conditional_expression");
76 let mut cursor = node.walk();
77 let operands: Vec<Node<'_>> = node
78 .named_children(&mut cursor)
79 .filter(|child| !child.is_extra())
80 .collect();
81 assert_eq!(
82 operands.len(),
83 3,
84 "conditional expression operands: {node:?}"
85 );
86 operands[1]
87}
88
89pub(crate) fn python_parenthesized_expression_child(node: Node<'_>) -> Option<Node<'_>> {
90 assert_eq!(node.kind(), "parenthesized_expression");
91 let mut cursor = node.walk();
92 let mut children = node
93 .named_children(&mut cursor)
94 .filter(|child| !child.is_extra());
95 let child = children.next()?;
96 children.next().is_none().then_some(child)
97}
98
99fn python_unwrap_parenthesized(mut node: Node<'_>) -> Node<'_> {
100 while node.kind() == "parenthesized_expression" {
101 let Some(child) = python_parenthesized_expression_child(node) else {
102 break;
103 };
104 node = child;
105 }
106 node
107}
108
109fn python_unstarred_sequence_shape(node: Node<'_>, starred_node_kinds: &[&str]) -> (u32, bool) {
110 let mut unstarred_count = 0u32;
111 let mut starred = false;
112 for index in 0..node.named_child_count() {
113 let child = node
114 .named_child(index)
115 .expect("named child index is below the named child count");
116 if child.is_extra() {
117 continue;
118 }
119 match child.kind() {
120 kind if starred_node_kinds.contains(&kind) => starred = true,
121 _ => unstarred_count += 1,
122 }
123 }
124 (unstarred_count, starred)
125}
126
127fn python_literal_pack_arguments(argument: Node<'_>, sink: &mut RoleSink<'_>) -> bool {
130 let Some(operand) = python_first_named_operand(argument) else {
131 return false;
132 };
133 let operand = python_unwrap_parenthesized(operand);
134 match (argument.kind(), operand.kind()) {
135 ("list_splat", "tuple" | "list") => {
136 let mut cursor = operand.walk();
137 let elements: Vec<_> = operand
138 .named_children(&mut cursor)
139 .filter(|child| !child.is_extra())
140 .collect();
141 if elements
142 .iter()
143 .any(|element| element.kind() == "list_splat")
144 {
145 return false;
146 }
147 for element in elements {
148 attach_argument_role_with_derived_name(sink, element, expression_name_node);
149 }
150 true
151 }
152 ("dictionary_splat", "dictionary") => {
153 let mut entries = Vec::new();
154 let mut indices = HashMap::default();
155 let mut cursor = operand.walk();
156 for pair in operand
157 .named_children(&mut cursor)
158 .filter(|child| !child.is_extra())
159 {
160 if pair.kind() != "pair" {
161 return false;
162 }
163 let (Some(key), Some(value)) = (
164 pair.child_by_field_name("key"),
165 pair.child_by_field_name("value"),
166 ) else {
167 return false;
168 };
169 let Some(name) = python_plain_string_literal(key, sink.source()) else {
170 return false;
171 };
172 let mut key_cursor = key.walk();
175 let Some(content) = key
176 .named_children(&mut key_cursor)
177 .find(|child| matches!(child.kind(), "string_content" | "string_end"))
178 else {
179 return false;
180 };
181 let span = Span {
182 start_byte: content.start_byte(),
183 end_byte: content.start_byte() + name.len(),
184 };
185 if let Some(&index) = indices.get(name) {
188 entries[index] = (span, value);
189 } else {
190 indices.insert(name, entries.len());
191 entries.push((span, value));
192 }
193 }
194 for (keyword, value) in entries {
195 sink.kwarg_span(keyword, value);
196 }
197 true
198 }
199 _ => false,
200 }
201}
202
203fn python_assignment_target_shape(node: Node<'_>) -> Option<(AssignmentSequenceKind, u32, bool)> {
204 let node = python_unwrap_parenthesized(node);
205 let kind = match node.kind() {
206 "tuple_pattern" | "pattern_list" => AssignmentSequenceKind::Tuple,
207 "list_pattern" => AssignmentSequenceKind::List,
208 _ => return None,
209 };
210 let (count, starred) =
211 python_unstarred_sequence_shape(node, &["list_splat_pattern", "splat_pattern"]);
212 Some((kind, count, starred))
213}
214
215fn python_assignment_value_shape(node: Node<'_>) -> Option<(AssignmentSequenceKind, u32, bool)> {
216 let node = python_unwrap_parenthesized(node);
217 let kind = match node.kind() {
218 "tuple" | "expression_list" => AssignmentSequenceKind::Tuple,
219 "list" => AssignmentSequenceKind::List,
220 _ => return None,
221 };
222 let (count, starred) = python_unstarred_sequence_shape(node, &["list_splat"]);
223 Some((kind, count, starred))
224}
225
226fn python_assignment_shape(node: Node<'_>) -> Option<AssignmentShape> {
227 if node.kind() != "assignment" {
228 return None;
229 }
230 let (target_kind, target_unstarred_count, target_starred) =
231 python_assignment_target_shape(node.child_by_field_name("left")?)?;
232 let (value_kind, value_unstarred_count, value_starred) =
233 python_assignment_value_shape(node.child_by_field_name("right")?)?;
234 Some(AssignmentShape {
235 target_kind,
236 value_kind,
237 target_unstarred_count,
238 value_unstarred_count,
239 target_starred,
240 value_starred,
241 })
242}
243
244fn python_integer_literal_value(node: Node<'_>, source: &str) -> Option<IntegerLiteralValue> {
245 if node.kind() != "integer" {
246 return None;
247 }
248
249 let text = node_source_text(node, source).as_bytes();
250 let (radix, digits) = match (text.first(), text.get(1)) {
251 (Some(b'0'), Some(b'b' | b'B')) => (2u32, &text[2..]),
252 (Some(b'0'), Some(b'o' | b'O')) => (8u32, &text[2..]),
253 (Some(b'0'), Some(b'x' | b'X')) => (16u32, &text[2..]),
254 _ => (10u32, text),
255 };
256 decode_integer_literal_digits(radix, digits)
257}
258
259#[derive(Debug, PartialEq, Eq)]
260enum PythonDictionaryKeyValue {
261 String(String),
262 Integer { negative: bool, magnitude: String },
263 Boolean(bool),
264 None,
265}
266
267impl PythonDictionaryKeyValue {
268 fn equals(&self, other: &Self) -> bool {
269 match (self, other) {
270 (Self::String(left), Self::String(right)) => left == right,
271 (Self::None, Self::None) => true,
272 (
273 Self::Integer {
274 negative: left_negative,
275 magnitude: left_magnitude,
276 },
277 Self::Integer {
278 negative: right_negative,
279 magnitude: right_magnitude,
280 },
281 ) => left_negative == right_negative && left_magnitude == right_magnitude,
282 (Self::Boolean(value), Self::Boolean(other)) => value == other,
283 (
284 Self::Boolean(value),
285 Self::Integer {
286 negative,
287 magnitude,
288 },
289 )
290 | (
291 Self::Integer {
292 negative,
293 magnitude,
294 },
295 Self::Boolean(value),
296 ) => !negative && magnitude == if *value { "1" } else { "0" },
297 _ => false,
298 }
299 }
300}
301
302fn python_string_key_value(node: Node<'_>, source: &str) -> Option<String> {
303 if node.kind() == "concatenated_string" {
304 let mut value = String::new();
305 let mut cursor = node.walk();
306 for part in node.named_children(&mut cursor) {
307 if part.kind() != "string" {
308 return None;
309 }
310 value.push_str(&python_string_key_value(part, source)?);
311 }
312 return Some(value);
313 }
314 if node.kind() != "string" {
315 return None;
316 }
317
318 let mut raw = false;
319 let mut content = String::new();
320 let mut cursor = node.walk();
321 for child in node.children(&mut cursor) {
322 match child.kind() {
323 "string_start" => {
324 let start = node_source_text(child, source).as_bytes();
325 let prefix_end = start
326 .iter()
327 .position(|byte| matches!(byte, b'\'' | b'"'))
328 .unwrap_or(start.len());
329 let prefix = std::str::from_utf8(&start[..prefix_end]).ok()?;
330 if !prefix.bytes().all(|byte| byte.is_ascii_alphabetic()) {
331 return None;
332 }
333 let prefix = prefix.to_ascii_lowercase();
334 if prefix.contains('b') || prefix.contains('f') {
335 return None;
336 }
337 raw = prefix.contains('r');
338 }
339 "string_content" => content.push_str(node_source_text(child, source)),
340 "escape_sequence" => content.push_str(node_source_text(child, source)),
341 "string_end" => {}
342 _ => return None,
343 }
344 }
345 python_decode_string_content(&content, raw)
346}
347
348fn python_decode_string_content(content: &str, raw: bool) -> Option<String> {
349 if raw {
350 return Some(content.to_owned());
351 }
352 let mut chars = content.chars().peekable();
353 let mut decoded = String::with_capacity(content.len());
354 while let Some(character) = chars.next() {
355 if character != '\\' {
356 decoded.push(character);
357 continue;
358 }
359 let escaped = chars.next()?;
360 let replacement = match escaped {
361 '\n' => continue,
362 '\r' => {
363 chars.next_if_eq(&'\n')?;
364 continue;
365 }
366 '\\' => '\\',
367 '\'' => '\'',
368 '"' => '"',
369 'a' => '\u{0007}',
370 'b' => '\u{0008}',
371 'f' => '\u{000c}',
372 'n' => '\n',
373 'r' => '\r',
374 't' => '\t',
375 'v' => '\u{000b}',
376 'x' => python_escape_codepoint(&mut chars, 2)?,
377 'u' => python_escape_codepoint(&mut chars, 4)?,
378 'U' => python_escape_codepoint(&mut chars, 8)?,
379 'N' => return None,
380 '0'..='7' => {
381 let mut value = escaped.to_digit(8)?;
382 for _ in 0..2 {
383 let Some(next) = chars.peek().copied().filter(char::is_ascii_digit) else {
384 break;
385 };
386 let Some(digit) = next.to_digit(8) else {
387 break;
388 };
389 chars.next();
390 value = value * 8 + digit;
391 }
392 char::from_u32(value)?
393 }
394 other => {
395 decoded.push('\\');
396 other
397 }
398 };
399 decoded.push(replacement);
400 }
401 Some(decoded)
402}
403
404fn python_escape_codepoint(
405 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
406 digits: usize,
407) -> Option<char> {
408 let mut value = 0u32;
409 for _ in 0..digits {
410 value = value
411 .checked_mul(16)?
412 .checked_add(chars.next()?.to_digit(16)?)?;
413 }
414 char::from_u32(value)
415}
416
417fn python_dictionary_key_value(node: Node<'_>, source: &str) -> Option<PythonDictionaryKeyValue> {
418 let node = python_unwrap_parenthesized(node);
419 if matches!(node.kind(), "string" | "concatenated_string") {
420 return python_string_key_value(node, source).map(PythonDictionaryKeyValue::String);
421 }
422 if matches!(node.kind(), "true" | "false") {
423 return Some(PythonDictionaryKeyValue::Boolean(node.kind() == "true"));
424 }
425 if node.kind() == "none" {
426 return Some(PythonDictionaryKeyValue::None);
427 }
428
429 let (integer, negative) = if node.kind() == "unary_operator" {
430 let operator = node.child_by_field_name("operator")?;
431 match node_source_text(operator, source) {
432 "+" => (
433 python_unwrap_parenthesized(node.child_by_field_name("argument")?),
434 false,
435 ),
436 "-" => (
437 python_unwrap_parenthesized(node.child_by_field_name("argument")?),
438 true,
439 ),
440 _ => return None,
441 }
442 } else {
443 (node, false)
444 };
445 let value = python_integer_literal_value(integer, source)?;
446 let magnitude = value.decimal?;
447 let negative = negative && magnitude != "0";
448 Some(PythonDictionaryKeyValue::Integer {
449 negative,
450 magnitude,
451 })
452}
453
454fn python_duplicate_dictionary_key(node: Node<'_>, source: &str) -> Option<bool> {
455 if node.kind() != "pair" {
456 return None;
457 }
458 let dictionary = node
459 .parent()
460 .filter(|parent| parent.kind() == "dictionary")?;
461 let current_key = python_dictionary_key_value(node.child_by_field_name("key")?, source)?;
462 let mut earlier_unknown = false;
463 let mut all_entries = dictionary.walk();
464 let has_unpacked_entry = dictionary
465 .named_children(&mut all_entries)
466 .any(|entry| !entry.is_extra() && entry.kind() != "pair");
467 let mut cursor = dictionary.walk();
468 for entry in dictionary.named_children(&mut cursor) {
469 if entry.id() == node.id() {
470 return if earlier_unknown || has_unpacked_entry {
471 None
472 } else {
473 Some(false)
474 };
475 }
476 match entry.kind() {
477 "pair" => {
478 let previous_key = entry
479 .child_by_field_name("key")
480 .and_then(|key| python_dictionary_key_value(key, source));
481 if previous_key
482 .as_ref()
483 .is_some_and(|previous| current_key.equals(previous))
484 {
485 return Some(true);
486 }
487 earlier_unknown |= previous_key.is_none();
488 }
489 _ if entry.is_extra() => {}
490 _ => {
491 earlier_unknown = true;
492 }
493 }
494 }
495 None
496}
497
498#[derive(Clone, Copy)]
499struct PythonComparisonShape<'tree> {
500 operator_count: usize,
501 operand_count: usize,
502 left: Option<Node<'tree>>,
503 right: Option<Node<'tree>>,
504 operator: Option<Node<'tree>>,
505}
506
507fn python_comparison_shape(node: Node<'_>) -> PythonComparisonShape<'_> {
511 let mut operator_count = 0usize;
512 let mut operand_count = 0usize;
513 let mut left = None;
514 let mut right = None;
515 let mut operator = None;
516 let mut cursor = node.walk();
517 if cursor.goto_first_child() {
518 loop {
519 let child = cursor.node();
520 if cursor.field_name() == Some("operators") {
521 operator_count += 1;
522 operator.get_or_insert(child);
523 } else if child.is_named() && !child.is_extra() {
524 operand_count += 1;
525 if left.is_none() {
526 left = Some(child);
527 } else if right.is_none() {
528 right = Some(child);
529 }
530 }
531 if !cursor.goto_next_sibling() {
532 break;
533 }
534 }
535 }
536 debug_assert!(
537 node.has_error() || operand_count == operator_count.saturating_add(1),
538 "well-formed Python comparison must have one more operand than operators: {node:?}"
539 );
540 PythonComparisonShape {
541 operator_count,
542 operand_count,
543 left,
544 right,
545 operator,
546 }
547}
548
549fn python_comparison_constant(node: Node<'_>) -> bool {
553 let mut current = node;
554 let mut signed = false;
555 loop {
556 match current.kind() {
557 "parenthesized_expression" => {
558 let Some(expression) = python_parenthesized_expression_child(current) else {
559 return false;
560 };
561 current = expression;
562 }
563 "unary_operator" => {
564 if !current
565 .child_by_field_name("operator")
566 .is_some_and(|operator| matches!(operator.kind(), "+" | "-"))
567 {
568 return false;
569 }
570 let Some(argument) = current.child_by_field_name("argument") else {
571 return false;
572 };
573 signed = true;
574 current = argument;
575 }
576 "integer" | "float" => return true,
577 "true" | "false" | "none" => return !signed,
578 "string" | "concatenated_string" => {
579 return !signed && python_plain_string_constant(current);
580 }
581 _ => return false,
582 }
583 }
584}
585
586fn python_plain_string_constant(node: Node<'_>) -> bool {
590 let mut pending = vec![node];
591 let mut saw_string = false;
592 while let Some(current) = pending.pop() {
593 match current.kind() {
594 "string" => {
595 saw_string = true;
596 let mut cursor = current.walk();
597 if current
598 .named_children(&mut cursor)
599 .any(|child| child.kind() == "interpolation")
600 {
601 return false;
602 }
603 }
604 "concatenated_string" => {
605 let mut cursor = current.walk();
606 let mut parts = 0;
607 for child in current.named_children(&mut cursor) {
608 if child.kind() != "string" {
609 return false;
610 }
611 parts += 1;
612 pending.push(child);
613 }
614 if parts == 0 {
615 return false;
616 }
617 }
618 _ => return false,
619 }
620 }
621 saw_string
622}
623
624pub const PYTHON_KIND_TABLE: &[(&str, NormalizedKind)] = &[
628 ("call", NormalizedKind::Call),
629 ("attribute", NormalizedKind::FieldAccess),
630 ("function_definition", NormalizedKind::Function),
631 ("lambda", NormalizedKind::Lambda),
632 ("await", NormalizedKind::Await),
633 ("yield", NormalizedKind::Yield),
634 ("module", NormalizedKind::Module),
635 ("class_definition", NormalizedKind::Class),
636 ("assignment", NormalizedKind::Assignment),
637 ("binary_operator", NormalizedKind::BinaryExpression),
638 ("boolean_operator", NormalizedKind::BinaryExpression),
639 ("comparison_operator", NormalizedKind::Comparison),
640 ("assert_statement", NormalizedKind::Assertion),
641 ("conditional_expression", NormalizedKind::If),
642 ("parenthesized_expression", NormalizedKind::Literal),
645 ("unary_operator", NormalizedKind::Literal),
646 ("with_statement", NormalizedKind::ResourceRelease),
650 ("import_statement", NormalizedKind::Import),
651 ("import_from_statement", NormalizedKind::Import),
652 ("identifier", NormalizedKind::Identifier),
653 ("string", NormalizedKind::StringLiteral),
654 ("concatenated_string", NormalizedKind::StringLiteral),
655 ("integer", NormalizedKind::NumericLiteral),
656 ("float", NormalizedKind::NumericLiteral),
657 ("true", NormalizedKind::BooleanLiteral),
658 ("false", NormalizedKind::BooleanLiteral),
659 ("none", NormalizedKind::NullLiteral),
660 ("return_statement", NormalizedKind::Return),
661 ("raise_statement", NormalizedKind::Throw),
662 ("except_clause", NormalizedKind::Catch),
663 ("if_statement", NormalizedKind::If),
664 ("for_statement", NormalizedKind::ForLoop),
665 ("list", NormalizedKind::ListLiteral),
666 ("set", NormalizedKind::CollectionLiteral),
667 ("dictionary", NormalizedKind::CollectionLiteral),
668 ("tuple", NormalizedKind::TupleLiteral),
669 ("pair", NormalizedKind::ObjectProperty),
670 ("while_statement", NormalizedKind::WhileLoop),
671 ("block", NormalizedKind::Block),
673 ("decorator", NormalizedKind::Decorator),
674 ("comment", NormalizedKind::Comment),
675];
676
677fn attach_decorators(sink: &mut RoleSink<'_>, definition: Node<'_>) {
680 let Some(parent) = definition.parent() else {
681 return;
682 };
683 if parent.kind() != "decorated_definition" {
684 return;
685 }
686 for index in 0..parent.named_child_count() {
687 let Some(child) = parent.named_child(index) else {
688 continue;
689 };
690 if child.kind() == "decorator" {
691 attach_role_with_derived_name(sink, Role::Decorator, child, expression_name_node);
692 }
693 }
694}
695
696static PYTHON_OCCURRENCE_ROLE_SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
697 .supported(OccurrenceRole::DeclarationName)
698 .supported(OccurrenceRole::Binder)
699 .supported(OccurrenceRole::LabelOrKey)
700 .supported(OccurrenceRole::TypeOperand)
701 .supported(OccurrenceRole::PathSegment)
702 .supported(OccurrenceRole::ImportAlias)
703 .supported(OccurrenceRole::ImportTarget)
704 .supported(OccurrenceRole::ReceiverPosition)
705 .supported(OccurrenceRole::MemberPosition)
706 .supported(OccurrenceRole::ValueReference)
707 .supported(OccurrenceRole::GlobalDirective)
708 .supported(OccurrenceRole::NonlocalDirective);
709
710fn python_dotted_name_is_import(dotted_name: Node<'_>) -> bool {
717 let mut current = dotted_name;
718 loop {
719 let Some(parent) = current.parent() else {
720 return false;
721 };
722 match parent.kind() {
723 "import_statement" | "import_from_statement" | "future_import_statement" => {
724 return true;
725 }
726 "aliased_import" | "dotted_name" | "relative_import" => current = parent,
727 _ => return false,
728 }
729 }
730}
731
732fn python_occurrence_role(node: Node<'_>) -> Option<OccurrenceRole> {
738 if node.kind() != "identifier" {
739 return None;
740 }
741 let parent = node.parent()?;
742 let field = field_name_in_parent(parent, node);
743 let role = match parent.kind() {
744 "global_statement" => OccurrenceRole::GlobalDirective,
745 "nonlocal_statement" => OccurrenceRole::NonlocalDirective,
746 "function_definition" | "class_definition" if field == Some("name") => {
747 OccurrenceRole::DeclarationName
748 }
749 "type" | "generic_type" | "type_parameter" | "constrained_type" | "union_type" => {
752 OccurrenceRole::TypeOperand
753 }
754 "parameters"
755 | "lambda_parameters"
756 | "typed_parameter"
757 | "list_splat_pattern"
758 | "dictionary_splat_pattern"
759 | "tuple_pattern"
760 | "list_pattern"
761 | "pattern_list"
762 | "as_pattern_target" => OccurrenceRole::Binder,
763 "default_parameter" | "typed_default_parameter" if field == Some("name") => {
764 OccurrenceRole::Binder
765 }
766 "for_statement" | "for_in_clause" if field == Some("left") => OccurrenceRole::Binder,
767 "assignment" if field == Some("left") => OccurrenceRole::Binder,
768 "keyword_argument" if python_keyword_argument_label(node) => OccurrenceRole::LabelOrKey,
769 "attribute" => match field {
770 Some("attribute") => OccurrenceRole::MemberPosition,
771 Some("object") => OccurrenceRole::ReceiverPosition,
772 _ => OccurrenceRole::ValueReference,
773 },
774 "aliased_import" if field == Some("alias") => OccurrenceRole::ImportAlias,
775 "import_from_statement" if field == Some("name") => OccurrenceRole::ImportTarget,
776 "dotted_name" => {
777 let is_tail =
778 parent.named_child(parent.named_child_count().saturating_sub(1)) == Some(node);
779 match (is_tail, python_dotted_name_is_import(parent)) {
780 (true, true) => OccurrenceRole::ImportTarget,
781 (true, false) => OccurrenceRole::ValueReference,
782 (false, _) => OccurrenceRole::PathSegment,
783 }
784 }
785 _ => OccurrenceRole::ValueReference,
786 };
787 Some(role)
788}
789
790fn python_identifier_is_parameter(node: Node<'_>) -> bool {
798 if node.kind() != "identifier" {
799 return false;
800 }
801 let Some(parent) = node.parent() else {
802 return false;
803 };
804 match parent.kind() {
805 "parameters" | "lambda_parameters" => true,
806 "typed_parameter"
807 | "default_parameter"
808 | "typed_default_parameter"
809 | "list_splat_pattern"
810 | "dictionary_splat_pattern"
811 | "keyword_separator" => {
812 crate::declarations::python_parameter_label_node(parent) == Some(node)
813 }
814 _ => false,
815 }
816}
817
818fn python_definition_is_method(definition: Node<'_>) -> bool {
828 let mut current = definition;
829 while let Some(parent) = current.parent() {
830 match parent.kind() {
831 "class_definition" => return true,
832 "block" | "decorated_definition" => current = parent,
835 _ => return false,
836 }
837 }
838 false
839}
840
841fn python_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
852 let Some(form) = nearest_ancestor(binder, |kind| {
860 matches!(
861 kind,
862 "parameters"
863 | "lambda_parameters"
864 | "assignment"
865 | "for_statement"
866 | "for_in_clause"
867 | "as_pattern"
868 | "list_comprehension"
869 | "set_comprehension"
870 | "dictionary_comprehension"
871 | "generator_expression"
872 )
873 }) else {
874 return Some(BindingActivation {
875 kind: BindingKind::Local,
876 hoisting: HoistingClass::ScopeWide,
877 activation: scope,
878 });
879 };
880 match form.kind() {
881 "parameters" | "lambda_parameters" => Some(BindingActivation {
882 kind: BindingKind::Parameter,
883 hoisting: HoistingClass::ScopeWide,
884 activation: scope,
885 }),
886 "assignment" => Some(BindingActivation {
887 kind: BindingKind::Local,
888 hoisting: HoistingClass::ScopeWide,
889 activation: scope,
890 }),
891 "for_in_clause" => {
892 let comprehension = nearest_ancestor(form, |kind| {
894 matches!(
895 kind,
896 "list_comprehension"
897 | "set_comprehension"
898 | "dictionary_comprehension"
899 | "generator_expression"
900 )
901 })?;
902 Some(BindingActivation {
903 kind: BindingKind::LoopVariable,
904 hoisting: HoistingClass::DeclaredHead,
905 activation: node_range(comprehension),
906 })
907 }
908 "for_statement" => Some(BindingActivation {
909 kind: BindingKind::LoopVariable,
910 hoisting: HoistingClass::ScopeWide,
911 activation: scope,
912 }),
913 "as_pattern" => Some(BindingActivation {
914 kind: BindingKind::PatternBinder,
915 hoisting: HoistingClass::ScopeWide,
916 activation: scope,
917 }),
918 _ => Some(BindingActivation {
919 kind: BindingKind::Local,
920 hoisting: HoistingClass::ScopeWide,
921 activation: scope,
922 }),
923 }
924}
925
926#[derive(Debug, Clone, Copy, PartialEq, Eq)]
932enum ReadableSurface {
933 Yes,
934 No,
935}
936
937#[derive(Debug, Clone)]
938struct PythonExportMember {
939 name: String,
940 range: Range,
941}
942
943fn python_collect_all_members(
947 value: Node<'_>,
948 source: &str,
949 members: &mut Vec<PythonExportMember>,
950) -> ReadableSurface {
951 if !matches!(value.kind(), "list" | "tuple") {
952 return ReadableSurface::No;
953 }
954 let mut cursor = value.walk();
955 for element in value.named_children(&mut cursor) {
956 match python_plain_string_literal(element, source) {
957 Some(text) => {
958 members.push(PythonExportMember {
959 name: text.to_owned(),
960 range: node_range(element),
961 });
962 }
963 None => return ReadableSurface::No,
964 }
965 }
966 ReadableSurface::Yes
967}
968
969fn python_error_contains_all_identifier(error: Node<'_>, source: &str) -> bool {
971 let mut pending = vec![error];
972 while let Some(node) = pending.pop() {
973 if node.kind() == "identifier" && node_source_text(node, source) == "__all__" {
974 return true;
975 }
976 let mut cursor = node.walk();
977 pending.extend(node.named_children(&mut cursor));
978 }
979 false
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" && python_error_contains_all_identifier(statement, source) {
1139 stated = true;
1140 readable = ReadableSurface::No;
1141 continue;
1142 }
1143 if statement.kind() != "expression_statement" {
1144 continue;
1145 }
1146 let mut inner = statement.walk();
1147 for expression in statement.named_children(&mut inner) {
1148 match expression.kind() {
1149 "ERROR" if python_error_contains_all_identifier(expression, source) => {
1150 stated = true;
1151 readable = ReadableSurface::No;
1152 }
1153 "assignment" | "augmented_assignment" => {
1154 if !names_all(expression.child_by_field_name("left")) {
1155 continue;
1156 }
1157 stated = true;
1158 let Some(value) = expression.child_by_field_name("right") else {
1159 let mut cursor = expression.walk();
1160 let has_assignment_operator = expression
1161 .children(&mut cursor)
1162 .any(|child| child.kind() == "=");
1163 let annotation_only = expression.kind() == "assignment"
1164 && expression.child_by_field_name("type").is_some()
1165 && !has_assignment_operator
1166 && !expression.has_error();
1167 if !annotation_only {
1168 readable = ReadableSurface::No;
1169 }
1170 continue;
1171 };
1172 let extends = expression.kind() == "assignment"
1173 || expression
1174 .child_by_field_name("operator")
1175 .is_some_and(|operator| operator.kind() == "+=");
1176 if !extends
1177 || python_collect_all_members(value, source, &mut members)
1178 == ReadableSurface::No
1179 {
1180 readable = ReadableSurface::No;
1181 }
1182 }
1183 "call" => {
1184 let Some(function) = expression.child_by_field_name("function") else {
1185 continue;
1186 };
1187 if function.kind() == "attribute"
1188 && names_all(function.child_by_field_name("object"))
1189 {
1190 stated = true;
1191 readable = ReadableSurface::No;
1192 }
1193 }
1194 _ => {}
1195 }
1196 }
1197 }
1198
1199 let mut names = HashSet::default();
1200 let exports = if stated {
1201 members
1202 .into_iter()
1203 .filter(|member| names.insert(member.name.clone()))
1204 .map(|member| {
1205 let local_definition = match module_bindings.get(member.name.as_str()) {
1206 Some(binding) => SourceExportLocalDefinition::Defined {
1207 binding: binding.clone(),
1208 },
1209 None if definitions_complete => SourceExportLocalDefinition::Undefined,
1210 None => SourceExportLocalDefinition::Unknown,
1211 };
1212 SourceExportFact {
1213 range: member.range,
1214 exported_name: member.name,
1215 local_definition,
1216 }
1217 })
1218 .collect()
1219 } else {
1220 Vec::new()
1221 };
1222 SourceExportFacts {
1223 exports,
1224 declared: stated,
1225 complete: !stated || readable == ReadableSurface::Yes,
1226 definitions_complete,
1227 }
1228}
1229
1230fn python_curated_export_surface(root: Node<'_>, source: &str) -> CuratedExportSurface {
1240 let facts = python_source_export_facts(root, source);
1241 match (facts.declared, facts.complete) {
1242 (false, _) => CuratedExportSurface::Absent,
1243 (true, false) => CuratedExportSurface::Unreadable,
1244 (true, true) => CuratedExportSurface::Listed(
1245 facts
1246 .exports
1247 .into_iter()
1248 .map(|export| export.exported_name)
1249 .collect(),
1250 ),
1251 }
1252}
1253
1254fn python_indirection_relation(
1279 token: Node<'_>,
1280 source: &str,
1281 surface: &CuratedExportSurface,
1282) -> Option<RouteHopKind> {
1283 let statement = nearest_ancestor(token, |kind| {
1284 matches!(
1285 kind,
1286 "import_statement" | "import_from_statement" | "future_import_statement"
1287 )
1288 })?;
1289 let mut clause = token;
1292 while let Some(parent) = clause.parent() {
1293 if parent.id() == statement.id() {
1294 break;
1295 }
1296 clause = parent;
1297 }
1298
1299 if field_name_in_parent(statement, clause) == Some("module_name") {
1300 let mut cursor = statement.walk();
1304 let star = statement
1305 .children(&mut cursor)
1306 .any(|child| child.kind() == "wildcard_import");
1307 return Some(if star {
1308 RouteHopKind::ReExport
1309 } else {
1310 RouteHopKind::Import
1311 });
1312 }
1313
1314 let bound = match clause.kind() {
1315 "aliased_import" => {
1316 let name = clause.child_by_field_name("name")?;
1317 let alias = clause.child_by_field_name("alias")?;
1318 if node_source_text(name, source) == node_source_text(alias, source) {
1319 return Some(RouteHopKind::ReExport);
1320 }
1321 alias
1322 }
1323 "dotted_name" if statement.kind() == "import_statement" => clause.named_child(0)?,
1326 "dotted_name" => clause,
1327 _ => return None,
1328 };
1329 match surface.lists(node_source_text(bound, source)) {
1330 Some(true) => Some(RouteHopKind::ReExport),
1331 Some(false) => Some(RouteHopKind::Import),
1332 None => None,
1333 }
1334}
1335
1336pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1338 use CalleeSpineStep::{Field, Name};
1339 match kind {
1340 "identifier" => Some(Name),
1341 "attribute" => Some(Field("attribute")),
1342 _ => None,
1343 }
1344}
1345
1346impl StructuralSpec for PythonStructuralSpec {
1347 fn language(&self) -> Language {
1348 Language::Python
1349 }
1350
1351 fn supports_boolean_literal_value(&self) -> bool {
1352 true
1353 }
1354
1355 fn supports_string_literal_parenthesized(&self) -> bool {
1356 true
1357 }
1358
1359 fn supports_string_literal_value(&self) -> bool {
1360 true
1361 }
1362
1363 fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1364 python_string_key_value(node, source)
1365 }
1366
1367 fn supports_assignment_shape(&self) -> bool {
1368 true
1369 }
1370
1371 fn assignment_shape(&self, node: Node<'_>) -> Option<AssignmentShape> {
1372 python_assignment_shape(node)
1373 }
1374
1375 fn supports_integer_literal_value(&self) -> bool {
1376 true
1377 }
1378
1379 fn integer_literal_value(&self, node: Node<'_>, source: &str) -> Option<IntegerLiteralValue> {
1380 python_integer_literal_value(node, source)
1381 }
1382
1383 fn supports_duplicate_dictionary_keys(&self) -> bool {
1384 true
1385 }
1386
1387 fn duplicate_dictionary_key(&self, node: Node<'_>, source: &str) -> Option<bool> {
1388 python_duplicate_dictionary_key(node, source)
1389 }
1390
1391 fn string_literal_parenthesized(&self, node: Node<'_>) -> Option<bool> {
1392 if !matches!(node.kind(), "string" | "concatenated_string") {
1393 return None;
1394 }
1395 let mut current = node;
1396 let mut parenthesized = false;
1397 while let Some(parent) = current.parent() {
1398 if parent.kind() != "parenthesized_expression" {
1399 break;
1400 }
1401 assert_eq!(python_parenthesized_expression_child(parent), Some(current));
1402 current = parent;
1403 parenthesized = true;
1404 }
1405 Some(parenthesized)
1406 }
1407
1408 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1409 &DEEP_REFERENCE_EDGE_SUPPORT
1410 }
1411
1412 fn identity_route_support(&self) -> &IdentityRouteSupport {
1413 static SUPPORT: IdentityRouteSupport = DEEP_IDENTITY_AXES
1416 .supported_relation(RouteHopKind::Alias)
1417 .supported_relation(RouteHopKind::Import)
1418 .supported_relation(RouteHopKind::ReExport)
1419 .supported_relation(RouteHopKind::NestedOwner);
1420 &SUPPORT
1421 }
1422
1423 fn qualified_path_root<'tree>(&self, token: Node<'tree>) -> Option<Node<'tree>> {
1426 if token.kind() != "identifier" {
1427 return None;
1428 }
1429 token
1430 .parent()
1431 .filter(|parent| parent.kind() == "dotted_name")
1432 }
1433
1434 fn path_segment_tokens<'tree>(&self, root: Node<'tree>) -> Vec<Node<'tree>> {
1435 if root.kind() != "dotted_name" {
1436 return Vec::new();
1437 }
1438 let mut cursor = root.walk();
1439 root.named_children(&mut cursor)
1440 .filter(|child| child.kind() == "identifier")
1441 .collect()
1442 }
1443
1444 fn curated_export_surface(&self, root: Node<'_>, source: &str) -> CuratedExportSurface {
1445 python_curated_export_surface(root, source)
1446 }
1447
1448 fn indirection_relation(
1449 &self,
1450 token: Node<'_>,
1451 source: &str,
1452 surface: &CuratedExportSurface,
1453 ) -> Option<RouteHopKind> {
1454 python_indirection_relation(token, source, surface)
1455 }
1456
1457 fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1458 match kind {
1459 NormalizedKind::Module => ScopeFormation::NotAScope,
1462 NormalizedKind::Block | NormalizedKind::ForLoop | NormalizedKind::WhileLoop => {
1465 ScopeFormation::NotAScope
1466 }
1467 _ => default_scope_formation(kind),
1468 }
1469 }
1470
1471 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1472 PYTHON_KIND_TABLE
1473 }
1474
1475 fn refine_kind(
1476 &self,
1477 node: Node<'_>,
1478 kind: NormalizedKind,
1479 _enclosing: Option<NormalizedKind>,
1480 _source: &str,
1481 _context: &CallSiteContext,
1482 ) -> NormalizedKind {
1483 match kind {
1484 NormalizedKind::Identifier if python_identifier_is_parameter(node) => {
1485 NormalizedKind::Parameter
1486 }
1487 NormalizedKind::Function if python_definition_is_method(node) => NormalizedKind::Method,
1488 NormalizedKind::Comparison if python_comparison_shape(node).operator_count > 1 => {
1489 NormalizedKind::ComparisonChain
1490 }
1491 _ => kind,
1492 }
1493 }
1494
1495 fn should_extract(&self, node: Node<'_>, kind: NormalizedKind) -> bool {
1496 if kind == NormalizedKind::Module && node.byte_range().is_empty() {
1497 return false;
1498 }
1499 match node.kind() {
1500 "parenthesized_expression" | "unary_operator" => {
1501 kind == NormalizedKind::Literal && python_comparison_constant(node)
1502 }
1503 _ => kind != NormalizedKind::Assignment || node.child_by_field_name("right").is_some(),
1504 }
1505 }
1506
1507 fn supports_kind(&self, kind: NormalizedKind) -> bool {
1508 brokk_bifrost_core::analyzer::structural::spec::supports_kind_from_table(
1509 kind,
1510 &[
1511 NormalizedKind::Method,
1512 NormalizedKind::Module,
1514 NormalizedKind::Parameter,
1515 NormalizedKind::SyntaxError,
1516 NormalizedKind::ComparisonChain,
1517 NormalizedKind::RegexPattern,
1518 NormalizedKind::RegexSequence,
1519 NormalizedKind::RegexGroup,
1520 NormalizedKind::RegexAlternation,
1521 NormalizedKind::RegexCharacterClass,
1522 NormalizedKind::RegexCharacterRange,
1523 NormalizedKind::RegexQuantifier,
1524 NormalizedKind::RegexAnchor,
1525 NormalizedKind::RegexEscape,
1526 NormalizedKind::RegexLiteral,
1527 NormalizedKind::RegexWildcard,
1528 NormalizedKind::RegexRiskHostname,
1529 NormalizedKind::RegexRiskRange,
1530 NormalizedKind::RegexRiskBadTag,
1531 NormalizedKind::RegexRiskExponential,
1532 NormalizedKind::RegexIncomplete,
1533 NormalizedKind::RegexRiskBackspaceEscape,
1534 NormalizedKind::RegexRiskDuplicateClassCharacter,
1535 NormalizedKind::RegexRiskUnmatchableCaret,
1536 NormalizedKind::RegexRiskUnmatchableDollar,
1537 NormalizedKind::RegexRiskIncompleteSpecialGroup,
1538 NormalizedKind::RegexRiskPolynomial,
1539 ],
1540 self.kind_table(),
1541 )
1542 }
1543
1544 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1545 &PYTHON_OCCURRENCE_ROLE_SUPPORT
1546 }
1547
1548 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1549 &DEEP_LEXICAL_ENVIRONMENT_SUPPORT
1550 }
1551
1552 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1553 &PYTHON_MATERIALIZATION_SUPPORT
1554 }
1555
1556 fn source_export_facts(&self, root: Node<'_>, source: &str) -> Option<SourceExportFacts> {
1557 Some(python_source_export_facts(root, source))
1558 }
1559
1560 fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1561 python_binding_activation(binder, scope)
1562 }
1563
1564 fn occurrence_namespace(
1567 &self,
1568 role: OccurrenceRole,
1569 declares: Option<NormalizedKind>,
1570 ) -> Option<Namespace> {
1571 module_path_occurrence_namespace(role, declares)
1572 }
1573
1574 fn embedded_leaf_facts(
1575 &self,
1576 node: Node<'_>,
1577 kind: NormalizedKind,
1578 source: &str,
1579 cancellation: Option<&CancellationToken>,
1580 ) -> Vec<EmbeddedLeafFact> {
1581 if kind == NormalizedKind::Module {
1582 return crate::regex::module_assigned_facts(node, source);
1583 }
1584 if kind == NormalizedKind::Call {
1585 return crate::regex::embedded_facts(node, source);
1586 }
1587 if kind != NormalizedKind::StringLiteral {
1588 return Vec::new();
1589 }
1590
1591 let mut facts = crate::regex::embedded_facts(node, source);
1592 if node.kind() != "string" || !python_node_is_in_annotation(node) {
1593 return facts;
1594 }
1595
1596 facts.extend(
1597 python_deferred_annotation_identifier_ranges(node, source, cancellation)
1598 .unwrap_or_default()
1599 .into_iter()
1600 .map(|range| EmbeddedLeafFact {
1601 kind: NormalizedKind::Identifier,
1602 range,
1603 parent: None,
1604 construct: None,
1605 occurrence_role: Some(OccurrenceRole::TypeOperand),
1606 }),
1607 );
1608 facts
1609 }
1610
1611 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1612 if let Some(role) = python_occurrence_role(node) {
1613 sink.occurrence_role(node, role);
1614 }
1615 match kind {
1616 NormalizedKind::Call => {
1617 if let Some(function) = node.child_by_field_name("function") {
1618 attach_terminal_callee(
1621 sink,
1622 function,
1623 terminal_callee_leaf(function, callee_spine_step),
1624 );
1625 if function.kind() == "attribute"
1626 && let Some(object) = function.child_by_field_name("object")
1627 {
1628 attach_role_with_derived_name(
1629 sink,
1630 Role::Receiver,
1631 object,
1632 expression_name_node,
1633 );
1634 }
1635 }
1636 if let Some(arguments) = node.child_by_field_name("arguments") {
1637 for index in 0..arguments.named_child_count() {
1638 if !sink.should_continue() {
1639 break;
1640 }
1641 let Some(argument) = arguments.named_child(index) else {
1642 continue;
1643 };
1644 if argument.is_extra() {
1645 continue;
1646 }
1647 match argument.kind() {
1648 "keyword_argument" => {
1649 if let (Some(keyword), Some(value)) = (
1650 argument.child_by_field_name("name"),
1651 argument.child_by_field_name("value"),
1652 ) {
1653 sink.kwarg(keyword, value);
1654 }
1655 }
1656 "list_splat" | "dictionary_splat"
1657 if python_literal_pack_arguments(argument, sink) => {}
1658 _ => attach_argument_role_with_derived_name(
1659 sink,
1660 argument,
1661 expression_name_node,
1662 ),
1663 }
1664 }
1665 }
1666 }
1667 NormalizedKind::FieldAccess => {
1668 if let Some(attribute) = node.child_by_field_name("attribute") {
1669 sink.set_name(attribute);
1670 sink.role_named(Role::Field, attribute, attribute);
1671 }
1672 if let Some(object) = node.child_by_field_name("object") {
1673 attach_role_with_derived_name(sink, Role::Object, object, expression_name_node);
1674 }
1675 }
1676 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Class => {
1677 if let Some(name) = node.child_by_field_name("name") {
1678 sink.set_name(name);
1679 }
1680 attach_decorators(sink, node);
1681 }
1682 NormalizedKind::Assignment => {
1683 if let Some(left) = node.child_by_field_name("left") {
1684 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1685 }
1686 if let Some(right) = node.child_by_field_name("right") {
1687 attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
1688 }
1689 }
1690 NormalizedKind::BinaryExpression => {
1691 if let Some(left) = node.child_by_field_name("left") {
1692 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1693 }
1694 if let Some(right) = node.child_by_field_name("right") {
1695 attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
1696 }
1697 if let Some(operator) = node.child_by_field_name("operator") {
1698 sink.role_named(Role::Operator, operator, operator);
1699 }
1700 }
1701 NormalizedKind::Comparison => {
1702 let shape = python_comparison_shape(node);
1703 if shape.operator_count == 1 && shape.operand_count == 2 {
1704 if let Some(left) = shape.left {
1705 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1706 }
1707 if let Some(right) = shape.right {
1708 attach_role_with_derived_name(
1709 sink,
1710 Role::Right,
1711 right,
1712 expression_name_node,
1713 );
1714 }
1715 if let Some(operator) = shape.operator {
1716 sink.role_named(Role::Operator, operator, operator);
1717 }
1718 }
1719 }
1720 NormalizedKind::Assertion => {
1721 if let Some(test) = python_first_named_operand(node) {
1722 sink.role(Role::Test, test);
1723 }
1724 }
1725 NormalizedKind::If => {
1726 let test = if node.kind() == "conditional_expression" {
1732 Some(python_conditional_expression_test(node))
1733 } else {
1734 node.child_by_field_name("condition")
1735 };
1736 if let Some(test) = test {
1737 sink.role(Role::Test, test);
1738 }
1739 }
1740 NormalizedKind::Return => {
1741 if let Some(value) = python_first_named_operand(node) {
1742 sink.role(Role::Value, value);
1743 }
1744 }
1745 NormalizedKind::Throw => {
1746 if let Some(value) = python_first_named_operand(node) {
1747 sink.role(Role::Value, value);
1748 }
1749 }
1750 NormalizedKind::Import => match node.kind() {
1751 "import_from_statement" => {
1752 if let Some(module) = node.child_by_field_name("module_name") {
1753 sink.role_named(Role::Module, module, module);
1754 }
1755 }
1756 _ => {
1757 for index in 0..node.named_child_count() {
1758 if !sink.should_continue() {
1759 break;
1760 }
1761 let Some(child) = node.named_child(index) else {
1762 continue;
1763 };
1764 match child.kind() {
1765 "dotted_name" => sink.role_named(Role::Module, child, child),
1766 "aliased_import" => {
1767 if let Some(name) = child.child_by_field_name("name") {
1768 sink.role_named(Role::Module, name, name);
1769 }
1770 }
1771 _ => {}
1772 }
1773 }
1774 }
1775 },
1776 NormalizedKind::Identifier | NormalizedKind::Parameter => sink.set_name(node),
1777 NormalizedKind::Decorator => {
1778 if let Some(name) = python_first_named_operand(node).and_then(expression_name_node)
1779 {
1780 sink.set_name(name);
1781 }
1782 }
1783 NormalizedKind::ForLoop => {
1784 if let Some(right) = node.child_by_field_name("right") {
1785 attach_role_with_derived_name(
1786 sink,
1787 Role::Iterable,
1788 right,
1789 expression_name_node,
1790 );
1791 }
1792 }
1793 NormalizedKind::CollectionLiteral
1794 | NormalizedKind::TupleLiteral
1795 | NormalizedKind::ListLiteral => {
1796 for index in 0..node.named_child_count() {
1797 let Some(child) = node.named_child(index) else {
1798 continue;
1799 };
1800 if child.is_extra() {
1801 continue;
1802 }
1803 let element = python_unwrap_parenthesized(child);
1804 attach_role_with_derived_name(
1805 sink,
1806 Role::Element,
1807 element,
1808 expression_name_node,
1809 );
1810 }
1811 }
1812 NormalizedKind::ObjectProperty => {
1813 if let Some(key) = node.child_by_field_name("key") {
1814 sink.role(Role::Key, key);
1815 }
1816 if let Some(value) = node.child_by_field_name("value") {
1817 sink.role(Role::Value, value);
1818 }
1819 }
1820 NormalizedKind::StringLiteral if node.kind() == "concatenated_string" => {
1821 for index in 0..node.named_child_count() {
1822 let Some(part) = node.named_child(index) else {
1823 continue;
1824 };
1825 if part.kind() == "string" {
1826 sink.role(Role::Part, part);
1827 }
1828 }
1829 }
1830 _ => {}
1831 }
1832 }
1833}
1834
1835#[cfg(test)]
1836mod tests {
1837 use super::*;
1838 use brokk_bifrost_test_macros::hardwon;
1839
1840 fn node_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
1841 let mut pending = vec![root];
1842 while let Some(node) = pending.pop() {
1843 if node.kind() == kind {
1844 return Some(node);
1845 }
1846 let mut cursor = node.walk();
1847 pending.extend(node.named_children(&mut cursor));
1848 }
1849 None
1850 }
1851
1852 fn string_literal_parenthesization(root: Node<'_>) -> (usize, usize) {
1853 let mut pending = vec![root];
1854 let mut string_count = 0;
1855 let mut parenthesized_count = 0;
1856 while let Some(node) = pending.pop() {
1857 if matches!(node.kind(), "string" | "concatenated_string") {
1858 string_count += 1;
1859 if PYTHON_STRUCTURAL_SPEC
1860 .string_literal_parenthesized(node)
1861 .expect("Python string-literal parenthesization is supported")
1862 {
1863 parenthesized_count += 1;
1864 }
1865 }
1866 let mut cursor = node.walk();
1867 pending.extend(node.named_children(&mut cursor));
1868 }
1869 (string_count, parenthesized_count)
1870 }
1871
1872 fn parse_python(source: &str) -> tree_sitter::Tree {
1873 crate::declarations::parse_python_tree(source).expect("Python source tree")
1874 }
1875
1876 #[test]
1877 fn missing_python_all_value_is_incomplete_after_parser_recovery() {
1878 let source = "__all__ =\n";
1879 let mut parser = tree_sitter::Parser::new();
1880 parser
1881 .set_language(&tree_sitter_python::LANGUAGE.into())
1882 .expect("Python parser");
1883 let tree = parser.parse(source, None).expect("Python source tree");
1884 let root = tree.root_node();
1885 let facts = python_source_export_facts(root, source);
1886
1887 assert!(facts.declared, "recovered AST: {}", root.to_sexp());
1888 assert!(!facts.complete, "recovered AST: {}", root.to_sexp());
1889 }
1890
1891 #[test]
1892 fn parenthesized_string_literals_allow_comment_children() {
1893 for (case, source) in [
1894 (
1895 "one comment between concatenated strings",
1896 "VALUE = (\n r\"\\.\" # dot\n r\"(?!-)\"\n)\n",
1897 ),
1898 (
1899 "several comments between concatenated strings",
1900 "VALUE = (\n r\"a\" # first\n # between\n r\"b\" # second\n # trailing\n)\n",
1901 ),
1902 (
1903 "comment before the string",
1904 "VALUE = (\n # starts below\n r\"value\"\n)\n",
1905 ),
1906 (
1907 "nested parentheses with a comment",
1908 "VALUE = (\n (\n # nested\n r\"value\" # closes inner value\n )\n)\n",
1909 ),
1910 ] {
1911 let tree = parse_python(source);
1912 let root = tree.root_node();
1913 assert!(!root.has_error(), "{case}: {}", root.to_sexp());
1914 let (string_count, parenthesized_count) = string_literal_parenthesization(root);
1915 assert!(string_count > 0, "{case}: {}", root.to_sexp());
1916 assert!(parenthesized_count > 0, "{case}: {}", root.to_sexp());
1917 }
1918
1919 let tree = parse_python("VALUE = (\n # comment\n 1 # trailing comment\n)\n");
1920 let mut pending = vec![tree.root_node()];
1921 let parenthesized = loop {
1922 let node = pending.pop().expect("parenthesized expression exists");
1923 if node.kind() == "parenthesized_expression" {
1924 break node;
1925 }
1926 let mut cursor = node.walk();
1927 pending.extend(node.named_children(&mut cursor));
1928 };
1929 assert!(python_comparison_constant(parenthesized));
1930 assert_eq!(python_unwrap_parenthesized(parenthesized).kind(), "integer");
1931 }
1932
1933 #[test]
1934 #[hardwon(
1935 "#4797: a backslash continuation before `else` counted as a fourth conditional-expression operand"
1936 )]
1937 fn conditional_expression_selects_condition_past_extras() {
1938 for (case, source) in [
1939 (
1940 "line continuation",
1941 "def f(a, b):\n return a if b \\\n else c\n",
1942 ),
1943 (
1944 "comment",
1945 "def f(a, b, c):\n return (a if b\n # why not\n else c)\n",
1946 ),
1947 (
1948 "plain",
1949 "def f(a, b, c):\n return (a if b\n else c)\n",
1950 ),
1951 ] {
1952 let tree = parse_python(source);
1953 let root = tree.root_node();
1954 assert!(!root.has_error(), "{case}: {}", root.to_sexp());
1955 let conditional = node_of_kind(root, "conditional_expression").unwrap_or_else(|| {
1956 panic!("{case}: no conditional expression in {}", root.to_sexp())
1957 });
1958 let test = python_conditional_expression_test(conditional);
1959 assert_eq!(test.kind(), "identifier", "{case}");
1960 assert_eq!(
1961 test.utf8_text(source.as_bytes()).unwrap(),
1962 "b",
1963 "{case}: {}",
1964 root.to_sexp()
1965 );
1966 }
1967 }
1968
1969 #[test]
1970 #[hardwon(
1971 "#4797: a line continuation inside parentheses hid the sole operand from the parenthesization check"
1972 )]
1973 fn parenthesized_expression_skips_line_continuation() {
1974 let source = "VALUE = (\n \"value\"\\\n)\n";
1975 let tree = parse_python(source);
1976 let root = tree.root_node();
1977 assert!(!root.has_error(), "{}", root.to_sexp());
1978 let string = node_of_kind(root, "string").expect("string literal");
1979 assert_eq!(
1980 PYTHON_STRUCTURAL_SPEC.string_literal_parenthesized(string),
1981 Some(true),
1982 "{}",
1983 root.to_sexp()
1984 );
1985 }
1986
1987 #[test]
1988 #[hardwon("#4797: a line continuation in a dict literal disabled duplicate-key detection")]
1989 fn duplicate_dictionary_key_skips_extras() {
1990 let source = "D = {1: 2, \\\n 1: 3}\n";
1991 let tree = parse_python(source);
1992 let root = tree.root_node();
1993 assert!(!root.has_error(), "{}", root.to_sexp());
1994 let dictionary = node_of_kind(root, "dictionary").expect("dictionary literal");
1995 let mut cursor = dictionary.walk();
1996 let pairs: Vec<Node<'_>> = dictionary
1997 .named_children(&mut cursor)
1998 .filter(|child| child.kind() == "pair")
1999 .collect();
2000 assert_eq!(pairs.len(), 2, "{}", root.to_sexp());
2001 assert_eq!(
2002 PYTHON_STRUCTURAL_SPEC.duplicate_dictionary_key(pairs[1], source),
2003 Some(true),
2004 "{}",
2005 root.to_sexp()
2006 );
2007 }
2008
2009 #[test]
2010 fn django_domain_validator_strings_parse_without_parenthesization_panics() {
2011 let source = r#"
2012class DomainNameValidator(RegexValidator):
2013 message = _("Enter a valid domain name.")
2014 ul = "\u00a1-\uffff" # Unicode letters range (must not be a raw string).
2015 # Host patterns.
2016 hostname_re = (
2017 r"[a-z" + ul + r"0-9](?:[a-z" + ul + r"0-9-]{0,61}[a-z" + ul + r"0-9])?"
2018 )
2019 # Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1.
2020 domain_re = r"(?:\.(?!-)[a-z" + ul + r"0-9-]{1,63}(?<!-))*"
2021 # Top-level domain.
2022 tld_no_fqdn_re = (
2023 r"\." # dot
2024 r"(?!-)" # can't start with a dash
2025 r"(?:[a-z" + ul + "-]{2,63}" # domain label
2026 r"|xn--[a-z0-9]{1,59})" # or punycode label
2027 r"(?<!-)" # can't end with a dash
2028 )
2029 tld_re = tld_no_fqdn_re + r"\.?"
2030 ascii_only_hostname_re = r"[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
2031 ascii_only_domain_re = r"(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-))*"
2032 ascii_only_tld_re = (
2033 r"\." # dot
2034 r"(?!-)" # can't start with a dash
2035 r"(?:[a-zA-Z0-9-]{2,63})" # domain label
2036 r"(?<!-)" # can't end with a dash
2037 r"\.?" # may have a trailing dot
2038 )
2039"#;
2040 let tree = parse_python(source);
2041 let root = tree.root_node();
2042 assert!(!root.has_error(), "{}", root.to_sexp());
2043 let (string_count, parenthesized_count) = string_literal_parenthesization(root);
2044 assert!(string_count > 0, "{}", root.to_sexp());
2045 assert!(parenthesized_count > 0, "{}", root.to_sexp());
2046 }
2047
2048 #[test]
2049 fn decoded_string_values_match_python_escape_and_concatenation_semantics() {
2050 for (source, expected) in [
2051 (
2052 r#"MIDDLEWARE = "\x64jango.middleware.csrf.CsrfViewMiddleware""#,
2053 Some("django.middleware.csrf.CsrfViewMiddleware"),
2054 ),
2055 (
2056 r#"MIDDLEWARE = "django.middleware.csrf." "CsrfViewMiddleware""#,
2057 Some("django.middleware.csrf.CsrfViewMiddleware"),
2058 ),
2059 (
2060 r#"MIDDLEWARE = b"django.middleware.csrf.CsrfViewMiddleware""#,
2061 None,
2062 ),
2063 ] {
2064 let mut parser = tree_sitter::Parser::new();
2065 parser
2066 .set_language(&tree_sitter_python::LANGUAGE.into())
2067 .expect("Python parser");
2068 let tree = parser.parse(source, None).expect("Python source tree");
2069 let mut pending = vec![tree.root_node()];
2070 let assignment = loop {
2071 let node = pending.pop().expect("Python tree has an assignment");
2072 if node.kind() == "assignment" {
2073 break node;
2074 }
2075 let mut cursor = node.walk();
2076 pending.extend(node.named_children(&mut cursor));
2077 };
2078 let value = assignment
2079 .child_by_field_name("right")
2080 .expect("assignment value");
2081 assert_eq!(
2082 python_string_key_value(value, source).as_deref(),
2083 expected,
2084 "{}",
2085 tree.root_node().to_sexp()
2086 );
2087 }
2088 }
2089}