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 visit_python_comparison_children(node, |child, is_operator| {
517 if is_operator {
518 operator_count += 1;
519 operator.get_or_insert(child);
520 } else if child.is_named() && !child.is_extra() {
521 operand_count += 1;
522 if left.is_none() {
523 left = Some(child);
524 } else if right.is_none() {
525 right = Some(child);
526 }
527 }
528 true
529 });
530 debug_assert!(
531 node.has_error() || operand_count == operator_count.saturating_add(1),
532 "well-formed Python comparison must have one more operand than operators: {node:?}"
533 );
534 PythonComparisonShape {
535 operator_count,
536 operand_count,
537 left,
538 right,
539 operator,
540 }
541}
542
543fn visit_python_comparison_children<'tree>(
545 node: Node<'tree>,
546 mut visit: impl FnMut(Node<'tree>, bool) -> bool,
547) -> bool {
548 let mut cursor = node.walk();
549 if cursor.goto_first_child() {
550 loop {
551 if !visit(cursor.node(), cursor.field_name() == Some("operators")) {
552 return false;
553 }
554 if !cursor.goto_next_sibling() {
555 break;
556 }
557 }
558 }
559 true
560}
561
562fn python_comparison_constant(node: Node<'_>) -> bool {
566 let mut current = node;
567 let mut signed = false;
568 loop {
569 match current.kind() {
570 "parenthesized_expression" => {
571 let Some(expression) = python_parenthesized_expression_child(current) else {
572 return false;
573 };
574 current = expression;
575 }
576 "unary_operator" => {
577 if !current
578 .child_by_field_name("operator")
579 .is_some_and(|operator| matches!(operator.kind(), "+" | "-"))
580 {
581 return false;
582 }
583 let Some(argument) = current.child_by_field_name("argument") else {
584 return false;
585 };
586 signed = true;
587 current = argument;
588 }
589 "integer" | "float" => return true,
590 "true" | "false" | "none" => return !signed,
591 "string" | "concatenated_string" => {
592 return !signed && python_plain_string_constant(current);
593 }
594 _ => return false,
595 }
596 }
597}
598
599fn python_plain_string_constant(node: Node<'_>) -> bool {
603 let mut pending = vec![node];
604 let mut saw_string = false;
605 while let Some(current) = pending.pop() {
606 match current.kind() {
607 "string" => {
608 saw_string = true;
609 let mut cursor = current.walk();
610 if current
611 .named_children(&mut cursor)
612 .any(|child| child.kind() == "interpolation")
613 {
614 return false;
615 }
616 }
617 "concatenated_string" => {
618 let mut cursor = current.walk();
619 let mut parts = 0;
620 for child in current.named_children(&mut cursor) {
621 if child.kind() != "string" {
622 return false;
623 }
624 parts += 1;
625 pending.push(child);
626 }
627 if parts == 0 {
628 return false;
629 }
630 }
631 _ => return false,
632 }
633 }
634 saw_string
635}
636
637pub const PYTHON_KIND_TABLE: &[(&str, NormalizedKind)] = &[
641 ("call", NormalizedKind::Call),
642 ("attribute", NormalizedKind::FieldAccess),
643 ("function_definition", NormalizedKind::Function),
644 ("lambda", NormalizedKind::Lambda),
645 ("await", NormalizedKind::Await),
646 ("yield", NormalizedKind::Yield),
647 ("module", NormalizedKind::Module),
648 ("class_definition", NormalizedKind::Class),
649 ("assignment", NormalizedKind::Assignment),
650 ("binary_operator", NormalizedKind::BinaryExpression),
651 ("boolean_operator", NormalizedKind::BinaryExpression),
652 ("comparison_operator", NormalizedKind::Comparison),
653 ("assert_statement", NormalizedKind::Assertion),
654 ("conditional_expression", NormalizedKind::If),
655 ("parenthesized_expression", NormalizedKind::Literal),
658 ("unary_operator", NormalizedKind::Literal),
659 ("with_statement", NormalizedKind::ResourceRelease),
663 ("import_statement", NormalizedKind::Import),
664 ("import_from_statement", NormalizedKind::Import),
665 ("identifier", NormalizedKind::Identifier),
666 ("string", NormalizedKind::StringLiteral),
667 ("concatenated_string", NormalizedKind::StringLiteral),
668 ("integer", NormalizedKind::NumericLiteral),
669 ("float", NormalizedKind::NumericLiteral),
670 ("true", NormalizedKind::BooleanLiteral),
671 ("false", NormalizedKind::BooleanLiteral),
672 ("none", NormalizedKind::NullLiteral),
673 ("return_statement", NormalizedKind::Return),
674 ("raise_statement", NormalizedKind::Throw),
675 ("except_clause", NormalizedKind::Catch),
676 ("if_statement", NormalizedKind::If),
677 ("for_statement", NormalizedKind::ForLoop),
678 ("list", NormalizedKind::ListLiteral),
679 ("set", NormalizedKind::CollectionLiteral),
680 ("dictionary", NormalizedKind::CollectionLiteral),
681 ("tuple", NormalizedKind::TupleLiteral),
682 ("pair", NormalizedKind::ObjectProperty),
683 ("while_statement", NormalizedKind::WhileLoop),
684 ("block", NormalizedKind::Block),
686 ("decorator", NormalizedKind::Decorator),
687 ("comment", NormalizedKind::Comment),
688];
689
690fn attach_decorators(sink: &mut RoleSink<'_>, definition: Node<'_>) {
693 let Some(parent) = definition.parent() else {
694 return;
695 };
696 if parent.kind() != "decorated_definition" {
697 return;
698 }
699 for index in 0..parent.named_child_count() {
700 let Some(child) = parent.named_child(index) else {
701 continue;
702 };
703 if child.kind() == "decorator" {
704 attach_role_with_derived_name(sink, Role::Decorator, child, expression_name_node);
705 }
706 }
707}
708
709static PYTHON_OCCURRENCE_ROLE_SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
710 .supported(OccurrenceRole::DeclarationName)
711 .supported(OccurrenceRole::Binder)
712 .supported(OccurrenceRole::LabelOrKey)
713 .supported(OccurrenceRole::TypeOperand)
714 .supported(OccurrenceRole::PathSegment)
715 .supported(OccurrenceRole::ImportAlias)
716 .supported(OccurrenceRole::ImportTarget)
717 .supported(OccurrenceRole::ReceiverPosition)
718 .supported(OccurrenceRole::MemberPosition)
719 .supported(OccurrenceRole::ValueReference)
720 .supported(OccurrenceRole::GlobalDirective)
721 .supported(OccurrenceRole::NonlocalDirective);
722
723fn python_dotted_name_is_import(dotted_name: Node<'_>) -> bool {
730 let mut current = dotted_name;
731 loop {
732 let Some(parent) = current.parent() else {
733 return false;
734 };
735 match parent.kind() {
736 "import_statement" | "import_from_statement" | "future_import_statement" => {
737 return true;
738 }
739 "aliased_import" | "dotted_name" | "relative_import" => current = parent,
740 _ => return false,
741 }
742 }
743}
744
745fn python_occurrence_role(node: Node<'_>) -> Option<OccurrenceRole> {
751 if node.kind() != "identifier" {
752 return None;
753 }
754 let parent = node.parent()?;
755 let field = field_name_in_parent(parent, node);
756 let role = match parent.kind() {
757 "global_statement" => OccurrenceRole::GlobalDirective,
758 "nonlocal_statement" => OccurrenceRole::NonlocalDirective,
759 "function_definition" | "class_definition" if field == Some("name") => {
760 OccurrenceRole::DeclarationName
761 }
762 "type" | "generic_type" | "type_parameter" | "constrained_type" | "union_type" => {
765 OccurrenceRole::TypeOperand
766 }
767 "parameters"
768 | "lambda_parameters"
769 | "typed_parameter"
770 | "list_splat_pattern"
771 | "dictionary_splat_pattern"
772 | "tuple_pattern"
773 | "list_pattern"
774 | "pattern_list"
775 | "as_pattern_target" => OccurrenceRole::Binder,
776 "default_parameter" | "typed_default_parameter" if field == Some("name") => {
777 OccurrenceRole::Binder
778 }
779 "for_statement" | "for_in_clause" if field == Some("left") => OccurrenceRole::Binder,
780 "assignment" if field == Some("left") => OccurrenceRole::Binder,
781 "keyword_argument" if python_keyword_argument_label(node) => OccurrenceRole::LabelOrKey,
782 "attribute" => match field {
783 Some("attribute") => OccurrenceRole::MemberPosition,
784 Some("object") => OccurrenceRole::ReceiverPosition,
785 _ => OccurrenceRole::ValueReference,
786 },
787 "aliased_import" if field == Some("alias") => OccurrenceRole::ImportAlias,
788 "import_from_statement" if field == Some("name") => OccurrenceRole::ImportTarget,
789 "dotted_name" => {
790 let is_tail =
791 parent.named_child(parent.named_child_count().saturating_sub(1)) == Some(node);
792 match (is_tail, python_dotted_name_is_import(parent)) {
793 (true, true) => OccurrenceRole::ImportTarget,
794 (true, false) => OccurrenceRole::ValueReference,
795 (false, _) => OccurrenceRole::PathSegment,
796 }
797 }
798 _ => OccurrenceRole::ValueReference,
799 };
800 Some(role)
801}
802
803fn python_identifier_is_parameter(node: Node<'_>) -> bool {
811 if node.kind() != "identifier" {
812 return false;
813 }
814 let Some(parent) = node.parent() else {
815 return false;
816 };
817 match parent.kind() {
818 "parameters" | "lambda_parameters" => true,
819 "typed_parameter"
820 | "default_parameter"
821 | "typed_default_parameter"
822 | "list_splat_pattern"
823 | "dictionary_splat_pattern"
824 | "keyword_separator" => {
825 crate::declarations::python_parameter_label_node(parent) == Some(node)
826 }
827 _ => false,
828 }
829}
830
831fn python_definition_is_method(definition: Node<'_>) -> bool {
841 let mut current = definition;
842 while let Some(parent) = current.parent() {
843 match parent.kind() {
844 "class_definition" => return true,
845 "block" | "decorated_definition" => current = parent,
848 _ => return false,
849 }
850 }
851 false
852}
853
854fn python_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
865 let Some(form) = nearest_ancestor(binder, |kind| {
873 matches!(
874 kind,
875 "parameters"
876 | "lambda_parameters"
877 | "assignment"
878 | "for_statement"
879 | "for_in_clause"
880 | "as_pattern"
881 | "list_comprehension"
882 | "set_comprehension"
883 | "dictionary_comprehension"
884 | "generator_expression"
885 )
886 }) else {
887 return Some(BindingActivation {
888 kind: BindingKind::Local,
889 hoisting: HoistingClass::ScopeWide,
890 activation: scope,
891 });
892 };
893 match form.kind() {
894 "parameters" | "lambda_parameters" => Some(BindingActivation {
895 kind: BindingKind::Parameter,
896 hoisting: HoistingClass::ScopeWide,
897 activation: scope,
898 }),
899 "assignment" => Some(BindingActivation {
900 kind: BindingKind::Local,
901 hoisting: HoistingClass::ScopeWide,
902 activation: scope,
903 }),
904 "for_in_clause" => {
905 let comprehension = nearest_ancestor(form, |kind| {
907 matches!(
908 kind,
909 "list_comprehension"
910 | "set_comprehension"
911 | "dictionary_comprehension"
912 | "generator_expression"
913 )
914 })?;
915 Some(BindingActivation {
916 kind: BindingKind::LoopVariable,
917 hoisting: HoistingClass::DeclaredHead,
918 activation: node_range(comprehension),
919 })
920 }
921 "for_statement" => Some(BindingActivation {
922 kind: BindingKind::LoopVariable,
923 hoisting: HoistingClass::ScopeWide,
924 activation: scope,
925 }),
926 "as_pattern" => Some(BindingActivation {
927 kind: BindingKind::PatternBinder,
928 hoisting: HoistingClass::ScopeWide,
929 activation: scope,
930 }),
931 _ => Some(BindingActivation {
932 kind: BindingKind::Local,
933 hoisting: HoistingClass::ScopeWide,
934 activation: scope,
935 }),
936 }
937}
938
939#[derive(Debug, Clone, Copy, PartialEq, Eq)]
945enum ReadableSurface {
946 Yes,
947 No,
948}
949
950#[derive(Debug, Clone)]
951struct PythonExportMember {
952 name: String,
953 range: Range,
954}
955
956fn python_collect_all_members(
960 value: Node<'_>,
961 source: &str,
962 members: &mut Vec<PythonExportMember>,
963) -> ReadableSurface {
964 if !matches!(value.kind(), "list" | "tuple") {
965 return ReadableSurface::No;
966 }
967 let mut cursor = value.walk();
968 for element in value.named_children(&mut cursor) {
969 match python_plain_string_literal(element, source) {
970 Some(text) => {
971 members.push(PythonExportMember {
972 name: text.to_owned(),
973 range: node_range(element),
974 });
975 }
976 None => return ReadableSurface::No,
977 }
978 }
979 ReadableSurface::Yes
980}
981
982fn python_module_export_bindings(
983 root: Node<'_>,
984 source: &str,
985) -> HashMap<Box<str>, SourceExportBinding> {
986 let mut bindings = Vec::new();
987 let mut stack = vec![root];
988 while let Some(node) = stack.pop() {
989 for binding in python_direct_scope_bindings_bounded(node, source, || true)
990 .expect("unbounded Python module binding scan completes")
991 {
992 let name = node_source_text(binding.declaration, source);
993 if name.is_empty() {
994 continue;
995 }
996 let kind = match binding.kind {
997 PythonDirectScopeBindingKind::ClassDeclaration => SourceExportBindingKind::Class,
998 PythonDirectScopeBindingKind::FunctionDeclaration => {
999 SourceExportBindingKind::Function
1000 }
1001 PythonDirectScopeBindingKind::Import => SourceExportBindingKind::Import,
1002 PythonDirectScopeBindingKind::Other => SourceExportBindingKind::Binding,
1003 };
1004 bindings.push(SourceExportBinding {
1005 name: name.to_owned(),
1006 kind,
1007 range: node_range(binding.declaration),
1008 });
1009 }
1010 stack.extend(python_executable_scope_children(node));
1011 }
1012 bindings.sort_unstable_by_key(|binding| binding.range.start_byte);
1013 let mut by_name = HashMap::default();
1014 for binding in bindings {
1015 by_name
1016 .entry(binding.name.clone().into())
1017 .or_insert(binding);
1018 }
1019 by_name
1020}
1021
1022fn python_call_function_name<'a>(node: Node<'_>, source: &'a str) -> Option<&'a str> {
1023 if node.kind() != "call" {
1024 return None;
1025 }
1026 let function = node.child_by_field_name("function")?;
1027 (function.kind() == "identifier").then(|| node_source_text(function, source))
1028}
1029
1030fn python_current_module_expression(
1031 expression: Node<'_>,
1032 source: &str,
1033 sys_bindings: &HashSet<Box<str>>,
1034 module_aliases: &HashSet<Box<str>>,
1035) -> bool {
1036 let expression = python_unwrap_parenthesized(expression);
1037 if expression.kind() == "identifier"
1038 && module_aliases.contains(node_source_text(expression, source))
1039 {
1040 return true;
1041 }
1042 if expression.kind() == "subscript"
1043 && let (Some(value), Some(subscript)) = (
1044 expression.child_by_field_name("value"),
1045 expression.child_by_field_name("subscript"),
1046 )
1047 && subscript.kind() == "identifier"
1048 && node_source_text(subscript, source) == "__name__"
1049 && value.kind() == "attribute"
1050 && value
1051 .child_by_field_name("attribute")
1052 .is_some_and(|attribute| node_source_text(attribute, source) == "modules")
1053 && value.child_by_field_name("object").is_some_and(|object| {
1054 object.kind() == "identifier" && sys_bindings.contains(node_source_text(object, source))
1055 })
1056 {
1057 return true;
1058 }
1059 python_call_function_name(expression, source) == Some("__import__")
1060 && expression
1061 .child_by_field_name("arguments")
1062 .and_then(python_first_named_operand)
1063 .is_some_and(|argument| {
1064 argument.kind() == "identifier" && node_source_text(argument, source) == "__name__"
1065 })
1066}
1067
1068fn python_module_definition_is_dynamic(root: Node<'_>, source: &str) -> bool {
1069 let sys_bindings: HashSet<Box<str>> = python_import_bindings_from_tree(root, source)
1070 .into_iter()
1071 .filter(|binding| binding.qualified_name == "sys")
1072 .map(|binding| binding.local_name.into_boxed_str())
1073 .collect();
1074 let mut assignments = Vec::new();
1075 let mut pending = vec![root];
1076 while let Some(node) = pending.pop() {
1077 if node.kind() == "assignment"
1078 && let (Some(left), Some(right)) = (
1079 node.child_by_field_name("left"),
1080 node.child_by_field_name("right"),
1081 )
1082 && left.kind() == "identifier"
1083 {
1084 assignments.push((node.start_byte(), left, right));
1085 }
1086 let mut cursor = node.walk();
1087 pending.extend(node.named_children(&mut cursor));
1088 }
1089 assignments.sort_unstable_by_key(|(start, _, _)| *start);
1090 let mut module_aliases = HashSet::default();
1091 for (_, left, right) in assignments {
1092 if python_current_module_expression(right, source, &sys_bindings, &module_aliases) {
1093 module_aliases.insert(node_source_text(left, source).into());
1094 }
1095 }
1096
1097 let mut pending = vec![root];
1098 while let Some(node) = pending.pop() {
1099 if node.kind() == "wildcard_import"
1100 || python_call_function_name(node, source) == Some("globals")
1101 {
1102 return true;
1103 }
1104 if python_call_function_name(node, source) == Some("setattr")
1105 && node
1106 .child_by_field_name("arguments")
1107 .and_then(python_first_named_operand)
1108 .is_some_and(|object| {
1109 python_current_module_expression(object, source, &sys_bindings, &module_aliases)
1110 })
1111 {
1112 return true;
1113 }
1114 let mut cursor = node.walk();
1115 pending.extend(node.named_children(&mut cursor));
1116 }
1117 root.has_error()
1118}
1119
1120fn python_source_export_facts(root: Node<'_>, source: &str) -> SourceExportFacts {
1126 let names_all = |node: Option<Node<'_>>| {
1127 node.is_some_and(|node| {
1128 node.kind() == "identifier" && node_source_text(node, source) == "__all__"
1129 })
1130 };
1131 let mut members = Vec::new();
1132 let mut stated = false;
1133 let mut readable = ReadableSurface::Yes;
1134 let module_bindings = python_module_export_bindings(root, source);
1135 let definitions_complete = !python_module_definition_is_dynamic(root, source);
1136 let mut cursor = root.walk();
1137 for statement in root.named_children(&mut cursor) {
1138 if statement.kind() == "ERROR"
1139 && crate::syntax::python_contains_all_identifier(statement, source)
1140 {
1141 stated = true;
1142 readable = ReadableSurface::No;
1143 continue;
1144 }
1145 if statement.kind() != "expression_statement" {
1146 continue;
1147 }
1148 let mut inner = statement.walk();
1149 for expression in statement.named_children(&mut inner) {
1150 match expression.kind() {
1151 "ERROR" if crate::syntax::python_contains_all_identifier(expression, source) => {
1152 stated = true;
1153 readable = ReadableSurface::No;
1154 }
1155 "assignment" | "augmented_assignment" => {
1156 if !names_all(expression.child_by_field_name("left")) {
1157 continue;
1158 }
1159 stated = true;
1160 let Some(value) = expression.child_by_field_name("right") else {
1161 let mut cursor = expression.walk();
1162 let has_assignment_operator = expression
1163 .children(&mut cursor)
1164 .any(|child| child.kind() == "=");
1165 let annotation_only = expression.kind() == "assignment"
1166 && expression.child_by_field_name("type").is_some()
1167 && !has_assignment_operator
1168 && !expression.has_error();
1169 if !annotation_only {
1170 readable = ReadableSurface::No;
1171 }
1172 continue;
1173 };
1174 let extends = expression.kind() == "assignment"
1175 || expression
1176 .child_by_field_name("operator")
1177 .is_some_and(|operator| operator.kind() == "+=");
1178 if !extends
1179 || python_collect_all_members(value, source, &mut members)
1180 == ReadableSurface::No
1181 {
1182 readable = ReadableSurface::No;
1183 }
1184 }
1185 "call" => {
1186 let Some(function) = expression.child_by_field_name("function") else {
1187 continue;
1188 };
1189 if function.kind() == "attribute"
1190 && names_all(function.child_by_field_name("object"))
1191 {
1192 stated = true;
1193 readable = ReadableSurface::No;
1194 }
1195 }
1196 _ => {}
1197 }
1198 }
1199 }
1200
1201 let mut names = HashSet::default();
1202 let exports = if stated {
1203 members
1204 .into_iter()
1205 .filter(|member| names.insert(member.name.clone()))
1206 .map(|member| {
1207 let local_definition = match module_bindings.get(member.name.as_str()) {
1208 Some(binding) => SourceExportLocalDefinition::Defined {
1209 binding: binding.clone(),
1210 },
1211 None if definitions_complete => SourceExportLocalDefinition::Undefined,
1212 None => SourceExportLocalDefinition::Unknown,
1213 };
1214 SourceExportFact {
1215 range: member.range,
1216 exported_name: member.name,
1217 local_definition,
1218 }
1219 })
1220 .collect()
1221 } else {
1222 Vec::new()
1223 };
1224 SourceExportFacts {
1225 exports,
1226 declared: stated,
1227 complete: !stated || readable == ReadableSurface::Yes,
1228 definitions_complete,
1229 }
1230}
1231
1232fn python_curated_export_surface(root: Node<'_>, source: &str) -> CuratedExportSurface {
1242 let facts = python_source_export_facts(root, source);
1243 match (facts.declared, facts.complete) {
1244 (false, _) => CuratedExportSurface::Absent,
1245 (true, false) => CuratedExportSurface::Unreadable,
1246 (true, true) => CuratedExportSurface::Listed(
1247 facts
1248 .exports
1249 .into_iter()
1250 .map(|export| export.exported_name)
1251 .collect(),
1252 ),
1253 }
1254}
1255
1256fn python_indirection_relation(
1281 token: Node<'_>,
1282 source: &str,
1283 surface: &CuratedExportSurface,
1284) -> Option<RouteHopKind> {
1285 let statement = nearest_ancestor(token, |kind| {
1286 matches!(
1287 kind,
1288 "import_statement" | "import_from_statement" | "future_import_statement"
1289 )
1290 })?;
1291 let mut clause = token;
1294 while let Some(parent) = clause.parent() {
1295 if parent.id() == statement.id() {
1296 break;
1297 }
1298 clause = parent;
1299 }
1300
1301 if field_name_in_parent(statement, clause) == Some("module_name") {
1302 let mut cursor = statement.walk();
1306 let star = statement
1307 .children(&mut cursor)
1308 .any(|child| child.kind() == "wildcard_import");
1309 return Some(if star {
1310 RouteHopKind::ReExport
1311 } else {
1312 RouteHopKind::Import
1313 });
1314 }
1315
1316 let bound = match clause.kind() {
1317 "aliased_import" => {
1318 let name = clause.child_by_field_name("name")?;
1319 let alias = clause.child_by_field_name("alias")?;
1320 if node_source_text(name, source) == node_source_text(alias, source) {
1321 return Some(RouteHopKind::ReExport);
1322 }
1323 alias
1324 }
1325 "dotted_name" if statement.kind() == "import_statement" => clause.named_child(0)?,
1328 "dotted_name" => clause,
1329 _ => return None,
1330 };
1331 match surface.lists(node_source_text(bound, source)) {
1332 Some(true) => Some(RouteHopKind::ReExport),
1333 Some(false) => Some(RouteHopKind::Import),
1334 None => None,
1335 }
1336}
1337
1338pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1340 use CalleeSpineStep::{Field, Name};
1341 match kind {
1342 "identifier" => Some(Name),
1343 "attribute" => Some(Field("attribute")),
1344 _ => None,
1345 }
1346}
1347
1348impl StructuralSpec for PythonStructuralSpec {
1349 fn language(&self) -> Language {
1350 Language::Python
1351 }
1352
1353 fn supports_boolean_literal_value(&self) -> bool {
1354 true
1355 }
1356
1357 fn supports_string_literal_parenthesized(&self) -> bool {
1358 true
1359 }
1360
1361 fn supports_string_literal_value(&self) -> bool {
1362 true
1363 }
1364
1365 fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1366 python_string_key_value(node, source)
1367 }
1368
1369 fn supports_assignment_shape(&self) -> bool {
1370 true
1371 }
1372
1373 fn assignment_shape(&self, node: Node<'_>) -> Option<AssignmentShape> {
1374 python_assignment_shape(node)
1375 }
1376
1377 fn supports_integer_literal_value(&self) -> bool {
1378 true
1379 }
1380
1381 fn integer_literal_value(&self, node: Node<'_>, source: &str) -> Option<IntegerLiteralValue> {
1382 python_integer_literal_value(node, source)
1383 }
1384
1385 fn supports_duplicate_dictionary_keys(&self) -> bool {
1386 true
1387 }
1388
1389 fn duplicate_dictionary_key(&self, node: Node<'_>, source: &str) -> Option<bool> {
1390 python_duplicate_dictionary_key(node, source)
1391 }
1392
1393 fn string_literal_parenthesized(&self, node: Node<'_>) -> Option<bool> {
1394 if !matches!(node.kind(), "string" | "concatenated_string") {
1395 return None;
1396 }
1397 let mut current = node;
1398 let mut parenthesized = false;
1399 while let Some(parent) = current.parent() {
1400 if parent.kind() != "parenthesized_expression" {
1401 break;
1402 }
1403 assert_eq!(python_parenthesized_expression_child(parent), Some(current));
1404 current = parent;
1405 parenthesized = true;
1406 }
1407 Some(parenthesized)
1408 }
1409
1410 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1411 &DEEP_REFERENCE_EDGE_SUPPORT
1412 }
1413
1414 fn identity_route_support(&self) -> &IdentityRouteSupport {
1415 static SUPPORT: IdentityRouteSupport = DEEP_IDENTITY_AXES
1418 .supported_relation(RouteHopKind::Alias)
1419 .supported_relation(RouteHopKind::Import)
1420 .supported_relation(RouteHopKind::ReExport)
1421 .supported_relation(RouteHopKind::NestedOwner);
1422 &SUPPORT
1423 }
1424
1425 fn qualified_path_root<'tree>(&self, token: Node<'tree>) -> Option<Node<'tree>> {
1428 if token.kind() != "identifier" {
1429 return None;
1430 }
1431 token
1432 .parent()
1433 .filter(|parent| parent.kind() == "dotted_name")
1434 }
1435
1436 fn path_segment_tokens<'tree>(&self, root: Node<'tree>) -> Vec<Node<'tree>> {
1437 if root.kind() != "dotted_name" {
1438 return Vec::new();
1439 }
1440 let mut cursor = root.walk();
1441 root.named_children(&mut cursor)
1442 .filter(|child| child.kind() == "identifier")
1443 .collect()
1444 }
1445
1446 fn curated_export_surface(&self, root: Node<'_>, source: &str) -> CuratedExportSurface {
1447 python_curated_export_surface(root, source)
1448 }
1449
1450 fn indirection_relation(
1451 &self,
1452 token: Node<'_>,
1453 source: &str,
1454 surface: &CuratedExportSurface,
1455 ) -> Option<RouteHopKind> {
1456 python_indirection_relation(token, source, surface)
1457 }
1458
1459 fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1460 match kind {
1461 NormalizedKind::Module => ScopeFormation::NotAScope,
1464 NormalizedKind::Block | NormalizedKind::ForLoop | NormalizedKind::WhileLoop => {
1467 ScopeFormation::NotAScope
1468 }
1469 _ => default_scope_formation(kind),
1470 }
1471 }
1472
1473 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1474 PYTHON_KIND_TABLE
1475 }
1476
1477 fn refine_kind(
1478 &self,
1479 node: Node<'_>,
1480 kind: NormalizedKind,
1481 _enclosing: Option<NormalizedKind>,
1482 _source: &str,
1483 _context: &CallSiteContext,
1484 ) -> NormalizedKind {
1485 match kind {
1486 NormalizedKind::Identifier if python_identifier_is_parameter(node) => {
1487 NormalizedKind::Parameter
1488 }
1489 NormalizedKind::Function if python_definition_is_method(node) => NormalizedKind::Method,
1490 NormalizedKind::Comparison if python_comparison_shape(node).operator_count > 1 => {
1491 NormalizedKind::ComparisonChain
1492 }
1493 _ => kind,
1494 }
1495 }
1496
1497 fn should_extract(&self, node: Node<'_>, kind: NormalizedKind) -> bool {
1498 if kind == NormalizedKind::Module && node.byte_range().is_empty() {
1499 return false;
1500 }
1501 match node.kind() {
1502 "parenthesized_expression" | "unary_operator" => {
1503 kind == NormalizedKind::Literal && python_comparison_constant(node)
1504 }
1505 _ => kind != NormalizedKind::Assignment || node.child_by_field_name("right").is_some(),
1506 }
1507 }
1508
1509 fn supports_kind(&self, kind: NormalizedKind) -> bool {
1510 brokk_bifrost_core::analyzer::structural::spec::supports_kind_from_table(
1511 kind,
1512 &[
1513 NormalizedKind::Method,
1514 NormalizedKind::Module,
1516 NormalizedKind::Parameter,
1517 NormalizedKind::SyntaxError,
1518 NormalizedKind::ComparisonChain,
1519 NormalizedKind::RegexPattern,
1520 NormalizedKind::RegexSequence,
1521 NormalizedKind::RegexGroup,
1522 NormalizedKind::RegexAlternation,
1523 NormalizedKind::RegexCharacterClass,
1524 NormalizedKind::RegexCharacterRange,
1525 NormalizedKind::RegexQuantifier,
1526 NormalizedKind::RegexAnchor,
1527 NormalizedKind::RegexEscape,
1528 NormalizedKind::RegexLiteral,
1529 NormalizedKind::RegexWildcard,
1530 NormalizedKind::RegexRiskHostname,
1531 NormalizedKind::RegexRiskRange,
1532 NormalizedKind::RegexRiskBadTag,
1533 NormalizedKind::RegexRiskExponential,
1534 NormalizedKind::RegexIncomplete,
1535 NormalizedKind::RegexRiskBackspaceEscape,
1536 NormalizedKind::RegexRiskDuplicateClassCharacter,
1537 NormalizedKind::RegexRiskUnmatchableCaret,
1538 NormalizedKind::RegexRiskUnmatchableDollar,
1539 NormalizedKind::RegexRiskIncompleteSpecialGroup,
1540 NormalizedKind::RegexRiskPolynomial,
1541 ],
1542 self.kind_table(),
1543 )
1544 }
1545
1546 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1547 &PYTHON_OCCURRENCE_ROLE_SUPPORT
1548 }
1549
1550 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1551 &DEEP_LEXICAL_ENVIRONMENT_SUPPORT
1552 }
1553
1554 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1555 &PYTHON_MATERIALIZATION_SUPPORT
1556 }
1557
1558 fn source_export_facts(&self, root: Node<'_>, source: &str) -> Option<SourceExportFacts> {
1559 Some(python_source_export_facts(root, source))
1560 }
1561
1562 fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1563 python_binding_activation(binder, scope)
1564 }
1565
1566 fn occurrence_namespace(
1569 &self,
1570 role: OccurrenceRole,
1571 declares: Option<NormalizedKind>,
1572 ) -> Option<Namespace> {
1573 module_path_occurrence_namespace(role, declares)
1574 }
1575
1576 fn embedded_leaf_facts(
1577 &self,
1578 node: Node<'_>,
1579 kind: NormalizedKind,
1580 source: &str,
1581 cancellation: Option<&CancellationToken>,
1582 ) -> Vec<EmbeddedLeafFact> {
1583 if kind == NormalizedKind::Module {
1584 return crate::regex::module_assigned_facts(node, source);
1585 }
1586 if kind == NormalizedKind::Call {
1587 return crate::regex::embedded_facts(node, source);
1588 }
1589 if kind != NormalizedKind::StringLiteral {
1590 return Vec::new();
1591 }
1592
1593 let mut facts = crate::regex::embedded_facts(node, source);
1594 if node.kind() != "string" || !python_node_is_in_annotation(node) {
1595 return facts;
1596 }
1597
1598 facts.extend(
1599 python_deferred_annotation_identifier_ranges(node, source, cancellation)
1600 .unwrap_or_default()
1601 .into_iter()
1602 .map(|range| EmbeddedLeafFact {
1603 kind: NormalizedKind::Identifier,
1604 range,
1605 parent: None,
1606 construct: None,
1607 occurrence_role: Some(OccurrenceRole::TypeOperand),
1608 }),
1609 );
1610 facts
1611 }
1612
1613 fn comparison_operator_spans(
1614 &self,
1615 node: Node<'_>,
1616 step: &mut dyn FnMut() -> bool,
1617 ) -> Option<Vec<Span>> {
1618 if !step() || node.kind() != "comparison_operator" || node.has_error() || node.is_missing()
1619 {
1620 return None;
1621 }
1622 let mut operators = Vec::new();
1623 let mut operands = 0usize;
1624 let complete = visit_python_comparison_children(node, |child, is_operator| {
1625 if !step() || child.is_missing() || child.has_error() {
1626 return false;
1627 }
1628 if is_operator {
1629 if child.start_byte() >= child.end_byte() {
1630 return false;
1631 }
1632 operators.push(Span {
1633 start_byte: child.start_byte(),
1634 end_byte: child.end_byte(),
1635 });
1636 } else if child.is_named() && !child.is_extra() {
1637 operands += 1;
1638 }
1639 true
1640 });
1641 (complete
1642 && !operators.is_empty()
1643 && operands == operators.len().saturating_add(1)
1644 && step())
1645 .then_some(operators)
1646 }
1647
1648 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1649 if let Some(role) = python_occurrence_role(node) {
1650 sink.occurrence_role(node, role);
1651 }
1652 match kind {
1653 NormalizedKind::Call => {
1654 if let Some(function) = node.child_by_field_name("function") {
1655 attach_terminal_callee(
1658 sink,
1659 function,
1660 terminal_callee_leaf(function, callee_spine_step),
1661 );
1662 if function.kind() == "attribute"
1663 && let Some(object) = function.child_by_field_name("object")
1664 {
1665 attach_role_with_derived_name(
1666 sink,
1667 Role::Receiver,
1668 object,
1669 expression_name_node,
1670 );
1671 }
1672 }
1673 if let Some(arguments) = node.child_by_field_name("arguments") {
1674 for index in 0..arguments.named_child_count() {
1675 if !sink.should_continue() {
1676 break;
1677 }
1678 let Some(argument) = arguments.named_child(index) else {
1679 continue;
1680 };
1681 if argument.is_extra() {
1682 continue;
1683 }
1684 match argument.kind() {
1685 "keyword_argument" => {
1686 if let (Some(keyword), Some(value)) = (
1687 argument.child_by_field_name("name"),
1688 argument.child_by_field_name("value"),
1689 ) {
1690 sink.kwarg(keyword, value);
1691 }
1692 }
1693 "list_splat" | "dictionary_splat"
1694 if python_literal_pack_arguments(argument, sink) => {}
1695 _ => attach_argument_role_with_derived_name(
1696 sink,
1697 argument,
1698 expression_name_node,
1699 ),
1700 }
1701 }
1702 }
1703 }
1704 NormalizedKind::FieldAccess => {
1705 if let Some(attribute) = node.child_by_field_name("attribute") {
1706 sink.set_name(attribute);
1707 sink.role_named(Role::Field, attribute, attribute);
1708 }
1709 if let Some(object) = node.child_by_field_name("object") {
1710 attach_role_with_derived_name(sink, Role::Object, object, expression_name_node);
1711 }
1712 }
1713 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Class => {
1714 if let Some(name) = node.child_by_field_name("name") {
1715 sink.set_name(name);
1716 }
1717 attach_decorators(sink, node);
1718 }
1719 NormalizedKind::Assignment => {
1720 if let Some(left) = node.child_by_field_name("left") {
1721 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1722 }
1723 if let Some(right) = node.child_by_field_name("right") {
1724 attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
1725 }
1726 }
1727 NormalizedKind::BinaryExpression => {
1728 if let Some(left) = node.child_by_field_name("left") {
1729 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1730 }
1731 if let Some(right) = node.child_by_field_name("right") {
1732 attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
1733 }
1734 if let Some(operator) = node.child_by_field_name("operator") {
1735 sink.role_named(Role::Operator, operator, operator);
1736 }
1737 }
1738 NormalizedKind::Comparison => {
1739 let shape = python_comparison_shape(node);
1740 if shape.operator_count == 1 && shape.operand_count == 2 {
1741 if let Some(left) = shape.left {
1742 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1743 }
1744 if let Some(right) = shape.right {
1745 attach_role_with_derived_name(
1746 sink,
1747 Role::Right,
1748 right,
1749 expression_name_node,
1750 );
1751 }
1752 if let Some(operator) = shape.operator {
1753 sink.role_named(Role::Operator, operator, operator);
1754 }
1755 }
1756 }
1757 NormalizedKind::Assertion => {
1758 if let Some(test) = python_first_named_operand(node) {
1759 sink.role(Role::Test, test);
1760 }
1761 }
1762 NormalizedKind::If => {
1763 let test = if node.kind() == "conditional_expression" {
1769 Some(python_conditional_expression_test(node))
1770 } else {
1771 node.child_by_field_name("condition")
1772 };
1773 if let Some(test) = test {
1774 sink.role(Role::Test, test);
1775 }
1776 }
1777 NormalizedKind::Return => {
1778 if let Some(value) = python_first_named_operand(node) {
1779 sink.role(Role::Value, value);
1780 }
1781 }
1782 NormalizedKind::Throw => {
1783 if let Some(value) = python_first_named_operand(node) {
1784 sink.role(Role::Value, value);
1785 }
1786 }
1787 NormalizedKind::Import => match node.kind() {
1788 "import_from_statement" => {
1789 if let Some(module) = node.child_by_field_name("module_name") {
1790 sink.role_named(Role::Module, module, module);
1791 }
1792 }
1793 _ => {
1794 for index in 0..node.named_child_count() {
1795 if !sink.should_continue() {
1796 break;
1797 }
1798 let Some(child) = node.named_child(index) else {
1799 continue;
1800 };
1801 match child.kind() {
1802 "dotted_name" => sink.role_named(Role::Module, child, child),
1803 "aliased_import" => {
1804 if let Some(name) = child.child_by_field_name("name") {
1805 sink.role_named(Role::Module, name, name);
1806 }
1807 }
1808 _ => {}
1809 }
1810 }
1811 }
1812 },
1813 NormalizedKind::Identifier | NormalizedKind::Parameter => sink.set_name(node),
1814 NormalizedKind::Decorator => {
1815 if let Some(name) = python_first_named_operand(node).and_then(expression_name_node)
1816 {
1817 sink.set_name(name);
1818 }
1819 }
1820 NormalizedKind::ForLoop => {
1821 if let Some(right) = node.child_by_field_name("right") {
1822 attach_role_with_derived_name(
1823 sink,
1824 Role::Iterable,
1825 right,
1826 expression_name_node,
1827 );
1828 }
1829 }
1830 NormalizedKind::CollectionLiteral
1831 | NormalizedKind::TupleLiteral
1832 | NormalizedKind::ListLiteral => {
1833 for index in 0..node.named_child_count() {
1834 let Some(child) = node.named_child(index) else {
1835 continue;
1836 };
1837 if child.is_extra() {
1838 continue;
1839 }
1840 let element = python_unwrap_parenthesized(child);
1841 attach_role_with_derived_name(
1842 sink,
1843 Role::Element,
1844 element,
1845 expression_name_node,
1846 );
1847 }
1848 }
1849 NormalizedKind::ObjectProperty => {
1850 if let Some(key) = node.child_by_field_name("key") {
1851 sink.role(Role::Key, key);
1852 }
1853 if let Some(value) = node.child_by_field_name("value") {
1854 sink.role(Role::Value, value);
1855 }
1856 }
1857 NormalizedKind::StringLiteral if node.kind() == "concatenated_string" => {
1858 for index in 0..node.named_child_count() {
1859 let Some(part) = node.named_child(index) else {
1860 continue;
1861 };
1862 if part.kind() == "string" {
1863 sink.role(Role::Part, part);
1864 }
1865 }
1866 }
1867 _ => {}
1868 }
1869 }
1870}
1871
1872#[cfg(test)]
1873mod tests {
1874 use super::*;
1875 use brokk_bifrost_test_macros::hardwon;
1876
1877 fn node_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
1878 let mut pending = vec![root];
1879 while let Some(node) = pending.pop() {
1880 if node.kind() == kind {
1881 return Some(node);
1882 }
1883 let mut cursor = node.walk();
1884 pending.extend(node.named_children(&mut cursor));
1885 }
1886 None
1887 }
1888
1889 fn string_literal_parenthesization(root: Node<'_>) -> (usize, usize) {
1890 let mut pending = vec![root];
1891 let mut string_count = 0;
1892 let mut parenthesized_count = 0;
1893 while let Some(node) = pending.pop() {
1894 if matches!(node.kind(), "string" | "concatenated_string") {
1895 string_count += 1;
1896 if PYTHON_STRUCTURAL_SPEC
1897 .string_literal_parenthesized(node)
1898 .expect("Python string-literal parenthesization is supported")
1899 {
1900 parenthesized_count += 1;
1901 }
1902 }
1903 let mut cursor = node.walk();
1904 pending.extend(node.named_children(&mut cursor));
1905 }
1906 (string_count, parenthesized_count)
1907 }
1908
1909 fn parse_python(source: &str) -> tree_sitter::Tree {
1910 crate::declarations::parse_python_tree(source).expect("Python source tree")
1911 }
1912
1913 #[test]
1914 fn missing_python_all_value_is_incomplete_after_parser_recovery() {
1915 let source = "__all__ =\n";
1916 let mut parser = tree_sitter::Parser::new();
1917 parser
1918 .set_language(&tree_sitter_python::LANGUAGE.into())
1919 .expect("Python parser");
1920 let tree = parser.parse(source, None).expect("Python source tree");
1921 let root = tree.root_node();
1922 let facts = python_source_export_facts(root, source);
1923
1924 assert!(facts.declared, "recovered AST: {}", root.to_sexp());
1925 assert!(!facts.complete, "recovered AST: {}", root.to_sexp());
1926 }
1927
1928 #[test]
1929 fn parenthesized_string_literals_allow_comment_children() {
1930 for (case, source) in [
1931 (
1932 "one comment between concatenated strings",
1933 "VALUE = (\n r\"\\.\" # dot\n r\"(?!-)\"\n)\n",
1934 ),
1935 (
1936 "several comments between concatenated strings",
1937 "VALUE = (\n r\"a\" # first\n # between\n r\"b\" # second\n # trailing\n)\n",
1938 ),
1939 (
1940 "comment before the string",
1941 "VALUE = (\n # starts below\n r\"value\"\n)\n",
1942 ),
1943 (
1944 "nested parentheses with a comment",
1945 "VALUE = (\n (\n # nested\n r\"value\" # closes inner value\n )\n)\n",
1946 ),
1947 ] {
1948 let tree = parse_python(source);
1949 let root = tree.root_node();
1950 assert!(!root.has_error(), "{case}: {}", root.to_sexp());
1951 let (string_count, parenthesized_count) = string_literal_parenthesization(root);
1952 assert!(string_count > 0, "{case}: {}", root.to_sexp());
1953 assert!(parenthesized_count > 0, "{case}: {}", root.to_sexp());
1954 }
1955
1956 let tree = parse_python("VALUE = (\n # comment\n 1 # trailing comment\n)\n");
1957 let mut pending = vec![tree.root_node()];
1958 let parenthesized = loop {
1959 let node = pending.pop().expect("parenthesized expression exists");
1960 if node.kind() == "parenthesized_expression" {
1961 break node;
1962 }
1963 let mut cursor = node.walk();
1964 pending.extend(node.named_children(&mut cursor));
1965 };
1966 assert!(python_comparison_constant(parenthesized));
1967 assert_eq!(python_unwrap_parenthesized(parenthesized).kind(), "integer");
1968 }
1969
1970 #[test]
1971 #[hardwon(
1972 "#4797: a backslash continuation before `else` counted as a fourth conditional-expression operand"
1973 )]
1974 fn conditional_expression_selects_condition_past_extras() {
1975 for (case, source) in [
1976 (
1977 "line continuation",
1978 "def f(a, b):\n return a if b \\\n else c\n",
1979 ),
1980 (
1981 "comment",
1982 "def f(a, b, c):\n return (a if b\n # why not\n else c)\n",
1983 ),
1984 (
1985 "plain",
1986 "def f(a, b, c):\n return (a if b\n else c)\n",
1987 ),
1988 ] {
1989 let tree = parse_python(source);
1990 let root = tree.root_node();
1991 assert!(!root.has_error(), "{case}: {}", root.to_sexp());
1992 let conditional = node_of_kind(root, "conditional_expression").unwrap_or_else(|| {
1993 panic!("{case}: no conditional expression in {}", root.to_sexp())
1994 });
1995 let test = python_conditional_expression_test(conditional);
1996 assert_eq!(test.kind(), "identifier", "{case}");
1997 assert_eq!(
1998 test.utf8_text(source.as_bytes()).unwrap(),
1999 "b",
2000 "{case}: {}",
2001 root.to_sexp()
2002 );
2003 }
2004 }
2005
2006 #[test]
2007 #[hardwon(
2008 "#4797: a line continuation inside parentheses hid the sole operand from the parenthesization check"
2009 )]
2010 fn parenthesized_expression_skips_line_continuation() {
2011 let source = "VALUE = (\n \"value\"\\\n)\n";
2012 let tree = parse_python(source);
2013 let root = tree.root_node();
2014 assert!(!root.has_error(), "{}", root.to_sexp());
2015 let string = node_of_kind(root, "string").expect("string literal");
2016 assert_eq!(
2017 PYTHON_STRUCTURAL_SPEC.string_literal_parenthesized(string),
2018 Some(true),
2019 "{}",
2020 root.to_sexp()
2021 );
2022 }
2023
2024 #[test]
2025 #[hardwon("#4797: a line continuation in a dict literal disabled duplicate-key detection")]
2026 fn duplicate_dictionary_key_skips_extras() {
2027 let source = "D = {1: 2, \\\n 1: 3}\n";
2028 let tree = parse_python(source);
2029 let root = tree.root_node();
2030 assert!(!root.has_error(), "{}", root.to_sexp());
2031 let dictionary = node_of_kind(root, "dictionary").expect("dictionary literal");
2032 let mut cursor = dictionary.walk();
2033 let pairs: Vec<Node<'_>> = dictionary
2034 .named_children(&mut cursor)
2035 .filter(|child| child.kind() == "pair")
2036 .collect();
2037 assert_eq!(pairs.len(), 2, "{}", root.to_sexp());
2038 assert_eq!(
2039 PYTHON_STRUCTURAL_SPEC.duplicate_dictionary_key(pairs[1], source),
2040 Some(true),
2041 "{}",
2042 root.to_sexp()
2043 );
2044 }
2045
2046 #[test]
2047 fn django_domain_validator_strings_parse_without_parenthesization_panics() {
2048 let source = r#"
2049class DomainNameValidator(RegexValidator):
2050 message = _("Enter a valid domain name.")
2051 ul = "\u00a1-\uffff" # Unicode letters range (must not be a raw string).
2052 # Host patterns.
2053 hostname_re = (
2054 r"[a-z" + ul + r"0-9](?:[a-z" + ul + r"0-9-]{0,61}[a-z" + ul + r"0-9])?"
2055 )
2056 # Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1.
2057 domain_re = r"(?:\.(?!-)[a-z" + ul + r"0-9-]{1,63}(?<!-))*"
2058 # Top-level domain.
2059 tld_no_fqdn_re = (
2060 r"\." # dot
2061 r"(?!-)" # can't start with a dash
2062 r"(?:[a-z" + ul + "-]{2,63}" # domain label
2063 r"|xn--[a-z0-9]{1,59})" # or punycode label
2064 r"(?<!-)" # can't end with a dash
2065 )
2066 tld_re = tld_no_fqdn_re + r"\.?"
2067 ascii_only_hostname_re = r"[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
2068 ascii_only_domain_re = r"(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-))*"
2069 ascii_only_tld_re = (
2070 r"\." # dot
2071 r"(?!-)" # can't start with a dash
2072 r"(?:[a-zA-Z0-9-]{2,63})" # domain label
2073 r"(?<!-)" # can't end with a dash
2074 r"\.?" # may have a trailing dot
2075 )
2076"#;
2077 let tree = parse_python(source);
2078 let root = tree.root_node();
2079 assert!(!root.has_error(), "{}", root.to_sexp());
2080 let (string_count, parenthesized_count) = string_literal_parenthesization(root);
2081 assert!(string_count > 0, "{}", root.to_sexp());
2082 assert!(parenthesized_count > 0, "{}", root.to_sexp());
2083 }
2084
2085 #[test]
2086 fn decoded_string_values_match_python_escape_and_concatenation_semantics() {
2087 for (source, expected) in [
2088 (
2089 r#"MIDDLEWARE = "\x64jango.middleware.csrf.CsrfViewMiddleware""#,
2090 Some("django.middleware.csrf.CsrfViewMiddleware"),
2091 ),
2092 (
2093 r#"MIDDLEWARE = "django.middleware.csrf." "CsrfViewMiddleware""#,
2094 Some("django.middleware.csrf.CsrfViewMiddleware"),
2095 ),
2096 (
2097 r#"MIDDLEWARE = b"django.middleware.csrf.CsrfViewMiddleware""#,
2098 None,
2099 ),
2100 ] {
2101 let mut parser = tree_sitter::Parser::new();
2102 parser
2103 .set_language(&tree_sitter_python::LANGUAGE.into())
2104 .expect("Python parser");
2105 let tree = parser.parse(source, None).expect("Python source tree");
2106 let mut pending = vec![tree.root_node()];
2107 let assignment = loop {
2108 let node = pending.pop().expect("Python tree has an assignment");
2109 if node.kind() == "assignment" {
2110 break node;
2111 }
2112 let mut cursor = node.walk();
2113 pending.extend(node.named_children(&mut cursor));
2114 };
2115 let value = assignment
2116 .child_by_field_name("right")
2117 .expect("assignment value");
2118 assert_eq!(
2119 python_string_key_value(value, source).as_deref(),
2120 expected,
2121 "{}",
2122 tree.root_node().to_sexp()
2123 );
2124 }
2125 }
2126}