1use brokk_bifrost_core::analyzer::structural::adapter_helpers::{
7 attach_argument_role_with_derived_name, attach_role_with_derived_name, attach_terminal_callee,
8 field_name_in_parent, first_named_child, nearest_ancestor, node_range,
9};
10use brokk_bifrost_core::analyzer::structural::callable::CallSiteContext;
11use brokk_bifrost_core::analyzer::structural::edges::{
12 DEEP_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
13};
14use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
15use brokk_bifrost_core::analyzer::structural::materialization::{
16 DeclarationMaterializationSupport, PYTHON_MATERIALIZATION_SUPPORT,
17};
18use brokk_bifrost_core::analyzer::structural::occurrences::{
19 Namespace, OccurrenceRole, OccurrenceRoleSupport, default_occurrence_namespace,
20};
21use brokk_bifrost_core::analyzer::structural::resolution::{
22 BindingActivation, BindingKind, DEEP_LEXICAL_ENVIRONMENT_SUPPORT, HoistingClass,
23 LexicalEnvironmentSupport,
24};
25use brokk_bifrost_core::analyzer::structural::routes::{
26 DEEP_IDENTITY_AXES, IdentityRouteSupport, RouteHopKind,
27};
28use brokk_bifrost_core::analyzer::structural::spec::{EmbeddedLeafFact, RoleSink, StructuralSpec};
29use brokk_bifrost_core::analyzer::{Language, Range};
30use brokk_bifrost_core::cancellation::CancellationToken;
31use tree_sitter::Node;
32
33use crate::syntax::{
34 expression_name_node, python_deferred_annotation_identifier_ranges,
35 python_keyword_argument_label, python_node_is_in_annotation,
36};
37
38#[derive(Debug, Default)]
39pub struct PythonStructuralSpec;
40
41pub static PYTHON_STRUCTURAL_SPEC: PythonStructuralSpec = PythonStructuralSpec;
42
43pub const PYTHON_KIND_TABLE: &[(&str, NormalizedKind)] = &[
47 ("call", NormalizedKind::Call),
48 ("attribute", NormalizedKind::FieldAccess),
49 ("function_definition", NormalizedKind::Function),
50 ("lambda", NormalizedKind::Lambda),
51 ("class_definition", NormalizedKind::Class),
52 ("assignment", NormalizedKind::Assignment),
53 ("import_statement", NormalizedKind::Import),
54 ("import_from_statement", NormalizedKind::Import),
55 ("identifier", NormalizedKind::Identifier),
56 ("string", NormalizedKind::StringLiteral),
57 ("concatenated_string", NormalizedKind::StringLiteral),
58 ("integer", NormalizedKind::NumericLiteral),
59 ("float", NormalizedKind::NumericLiteral),
60 ("true", NormalizedKind::BooleanLiteral),
61 ("false", NormalizedKind::BooleanLiteral),
62 ("none", NormalizedKind::NullLiteral),
63 ("return_statement", NormalizedKind::Return),
64 ("raise_statement", NormalizedKind::Throw),
65 ("except_clause", NormalizedKind::Catch),
66 ("if_statement", NormalizedKind::If),
67 ("for_statement", NormalizedKind::ForLoop),
68 ("list", NormalizedKind::CollectionLiteral),
69 ("set", NormalizedKind::CollectionLiteral),
70 ("dictionary", NormalizedKind::CollectionLiteral),
71 ("tuple", NormalizedKind::CollectionLiteral),
72 ("while_statement", NormalizedKind::WhileLoop),
73 ("block", NormalizedKind::Block),
78 ("decorator", NormalizedKind::Decorator),
79];
80
81fn attach_decorators(sink: &mut RoleSink<'_>, definition: Node<'_>) {
84 let Some(parent) = definition.parent() else {
85 return;
86 };
87 if parent.kind() != "decorated_definition" {
88 return;
89 }
90 for index in 0..parent.named_child_count() {
91 let Some(child) = parent.named_child(index) else {
92 continue;
93 };
94 if child.kind() == "decorator" {
95 attach_role_with_derived_name(sink, Role::Decorator, child, expression_name_node);
96 }
97 }
98}
99
100static PYTHON_OCCURRENCE_ROLE_SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
101 .supported(OccurrenceRole::DeclarationName)
102 .supported(OccurrenceRole::Binder)
103 .supported(OccurrenceRole::LabelOrKey)
104 .supported(OccurrenceRole::TypeOperand)
105 .supported(OccurrenceRole::PathSegment)
106 .supported(OccurrenceRole::ImportAlias)
107 .supported(OccurrenceRole::ImportTarget)
108 .supported(OccurrenceRole::ReceiverPosition)
109 .supported(OccurrenceRole::MemberPosition)
110 .supported(OccurrenceRole::ValueReference);
111
112fn python_dotted_name_is_import(dotted_name: Node<'_>) -> bool {
115 let mut current = dotted_name;
116 loop {
117 let Some(parent) = current.parent() else {
118 return false;
119 };
120 match parent.kind() {
121 "import_statement" | "import_from_statement" | "future_import_statement" => {
122 return true;
123 }
124 "aliased_import" | "dotted_name" => current = parent,
125 _ => return false,
126 }
127 }
128}
129
130fn python_occurrence_role(node: Node<'_>) -> Option<OccurrenceRole> {
136 if node.kind() != "identifier" {
137 return None;
138 }
139 let parent = node.parent()?;
140 let field = field_name_in_parent(parent, node);
141 let role = match parent.kind() {
142 "function_definition" | "class_definition" if field == Some("name") => {
143 OccurrenceRole::DeclarationName
144 }
145 "type" | "generic_type" | "type_parameter" | "constrained_type" | "union_type" => {
148 OccurrenceRole::TypeOperand
149 }
150 "parameters"
151 | "lambda_parameters"
152 | "typed_parameter"
153 | "list_splat_pattern"
154 | "dictionary_splat_pattern"
155 | "tuple_pattern"
156 | "list_pattern"
157 | "pattern_list"
158 | "as_pattern_target" => OccurrenceRole::Binder,
159 "default_parameter" | "typed_default_parameter" if field == Some("name") => {
160 OccurrenceRole::Binder
161 }
162 "for_statement" | "for_in_clause" if field == Some("left") => OccurrenceRole::Binder,
163 "keyword_argument" if python_keyword_argument_label(node) => OccurrenceRole::LabelOrKey,
164 "attribute" => match field {
165 Some("attribute") => OccurrenceRole::MemberPosition,
166 Some("object") => OccurrenceRole::ReceiverPosition,
167 _ => OccurrenceRole::ValueReference,
168 },
169 "aliased_import" if field == Some("alias") => OccurrenceRole::ImportAlias,
170 "import_from_statement" if field == Some("name") => OccurrenceRole::ImportTarget,
171 "dotted_name" => {
172 let is_tail =
173 parent.named_child(parent.named_child_count().saturating_sub(1)) == Some(node);
174 match (is_tail, python_dotted_name_is_import(parent)) {
175 (true, true) => OccurrenceRole::ImportTarget,
176 (true, false) => OccurrenceRole::ValueReference,
177 (false, _) => OccurrenceRole::PathSegment,
178 }
179 }
180 _ => OccurrenceRole::ValueReference,
181 };
182 Some(role)
183}
184
185fn python_definition_is_method(definition: Node<'_>) -> bool {
195 let mut current = definition;
196 while let Some(parent) = current.parent() {
197 match parent.kind() {
198 "class_definition" => return true,
199 "block" | "decorated_definition" => current = parent,
202 _ => return false,
203 }
204 }
205 false
206}
207
208fn python_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
219 let form = nearest_ancestor(binder, |kind| {
220 matches!(
221 kind,
222 "parameters"
223 | "lambda_parameters"
224 | "for_statement"
225 | "for_in_clause"
226 | "as_pattern"
227 | "list_comprehension"
228 | "set_comprehension"
229 | "dictionary_comprehension"
230 | "generator_expression"
231 | "function_definition"
232 )
233 })?;
234 match form.kind() {
235 "parameters" | "lambda_parameters" | "function_definition" => Some(BindingActivation {
236 kind: BindingKind::Parameter,
237 hoisting: HoistingClass::ScopeWide,
238 activation: scope,
239 }),
240 "for_in_clause" => {
241 let comprehension = nearest_ancestor(form, |kind| {
243 matches!(
244 kind,
245 "list_comprehension"
246 | "set_comprehension"
247 | "dictionary_comprehension"
248 | "generator_expression"
249 )
250 })?;
251 Some(BindingActivation {
252 kind: BindingKind::LoopVariable,
253 hoisting: HoistingClass::DeclaredHead,
254 activation: node_range(comprehension),
255 })
256 }
257 "for_statement" => Some(BindingActivation {
258 kind: BindingKind::LoopVariable,
259 hoisting: HoistingClass::ScopeWide,
260 activation: scope,
261 }),
262 "as_pattern" => Some(BindingActivation {
263 kind: BindingKind::PatternBinder,
264 hoisting: HoistingClass::ScopeWide,
265 activation: scope,
266 }),
267 _ => Some(BindingActivation {
268 kind: BindingKind::Local,
269 hoisting: HoistingClass::ScopeWide,
270 activation: scope,
271 }),
272 }
273}
274
275impl StructuralSpec for PythonStructuralSpec {
276 fn language(&self) -> Language {
277 Language::Python
278 }
279
280 fn supports_boolean_literal_value(&self) -> bool {
281 true
282 }
283
284 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
285 &DEEP_REFERENCE_EDGE_SUPPORT
286 }
287
288 fn identity_route_support(&self) -> &IdentityRouteSupport {
289 static SUPPORT: IdentityRouteSupport = DEEP_IDENTITY_AXES
295 .supported_relation(RouteHopKind::Alias)
296 .supported_relation(RouteHopKind::Import)
297 .supported_relation(RouteHopKind::NestedOwner);
298 &SUPPORT
299 }
300
301 fn qualified_path_root<'tree>(&self, token: Node<'tree>) -> Option<Node<'tree>> {
304 if token.kind() != "identifier" {
305 return None;
306 }
307 token
308 .parent()
309 .filter(|parent| parent.kind() == "dotted_name")
310 }
311
312 fn path_segment_tokens<'tree>(&self, root: Node<'tree>) -> Vec<Node<'tree>> {
313 if root.kind() != "dotted_name" {
314 return Vec::new();
315 }
316 let mut cursor = root.walk();
317 root.named_children(&mut cursor)
318 .filter(|child| child.kind() == "identifier")
319 .collect()
320 }
321
322 fn indirection_relation(&self, token: Node<'_>) -> Option<RouteHopKind> {
323 nearest_ancestor(token, |kind| {
324 matches!(kind, "import_statement" | "import_from_statement")
325 })
326 .map(|_| RouteHopKind::Import)
327 }
328
329 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
330 PYTHON_KIND_TABLE
331 }
332
333 fn refine_kind(
334 &self,
335 node: Node<'_>,
336 kind: NormalizedKind,
337 _enclosing: Option<NormalizedKind>,
338 _source: &str,
339 _context: &CallSiteContext,
340 ) -> NormalizedKind {
341 if kind == NormalizedKind::Function && python_definition_is_method(node) {
342 NormalizedKind::Method
343 } else {
344 kind
345 }
346 }
347
348 fn should_extract(&self, node: Node<'_>, kind: NormalizedKind) -> bool {
349 kind != NormalizedKind::Assignment || node.child_by_field_name("right").is_some()
350 }
351
352 fn supports_kind(&self, kind: NormalizedKind) -> bool {
353 kind == NormalizedKind::Method
354 || self
355 .kind_table()
356 .iter()
357 .any(|(_, fact_kind)| fact_kind.satisfies(kind))
358 }
359
360 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
361 &PYTHON_OCCURRENCE_ROLE_SUPPORT
362 }
363
364 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
365 &DEEP_LEXICAL_ENVIRONMENT_SUPPORT
366 }
367
368 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
369 &PYTHON_MATERIALIZATION_SUPPORT
370 }
371
372 fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
373 python_binding_activation(binder, scope)
374 }
375
376 fn occurrence_namespace(
379 &self,
380 role: OccurrenceRole,
381 declares: Option<NormalizedKind>,
382 ) -> Option<Namespace> {
383 match role {
384 OccurrenceRole::PathSegment => Some(Namespace::Module),
385 _ => default_occurrence_namespace(role, declares),
386 }
387 }
388
389 fn embedded_leaf_facts(
390 &self,
391 node: Node<'_>,
392 kind: NormalizedKind,
393 source: &str,
394 cancellation: Option<&CancellationToken>,
395 ) -> Vec<EmbeddedLeafFact> {
396 if kind != NormalizedKind::StringLiteral
397 || node.kind() != "string"
398 || !python_node_is_in_annotation(node)
399 {
400 return Vec::new();
401 }
402
403 python_deferred_annotation_identifier_ranges(node, source, cancellation)
404 .unwrap_or_default()
405 .into_iter()
406 .map(|range| EmbeddedLeafFact {
407 kind: NormalizedKind::Identifier,
408 range,
409 occurrence_role: OccurrenceRole::TypeOperand,
410 })
411 .collect()
412 }
413
414 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
415 if let Some(role) = python_occurrence_role(node) {
416 sink.occurrence_role(node, role);
417 }
418 match kind {
419 NormalizedKind::Call => {
420 if let Some(function) = node.child_by_field_name("function") {
421 attach_terminal_callee(sink, function, expression_name_node(function));
424 if function.kind() == "attribute"
425 && let Some(object) = function.child_by_field_name("object")
426 {
427 attach_role_with_derived_name(
428 sink,
429 Role::Receiver,
430 object,
431 expression_name_node,
432 );
433 }
434 }
435 if let Some(arguments) = node.child_by_field_name("arguments") {
436 for index in 0..arguments.named_child_count() {
437 if !sink.should_continue() {
438 break;
439 }
440 let Some(argument) = arguments.named_child(index) else {
441 continue;
442 };
443 match argument.kind() {
444 "comment" => {}
445 "keyword_argument" => {
446 if let (Some(keyword), Some(value)) = (
447 argument.child_by_field_name("name"),
448 argument.child_by_field_name("value"),
449 ) {
450 sink.kwarg(keyword, value);
451 }
452 }
453 _ => attach_argument_role_with_derived_name(
454 sink,
455 argument,
456 expression_name_node,
457 ),
458 }
459 }
460 }
461 }
462 NormalizedKind::FieldAccess => {
463 if let Some(attribute) = node.child_by_field_name("attribute") {
464 sink.set_name(attribute);
465 sink.role_named(Role::Field, attribute, attribute);
466 }
467 if let Some(object) = node.child_by_field_name("object") {
468 attach_role_with_derived_name(sink, Role::Object, object, expression_name_node);
469 }
470 }
471 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Class => {
472 if let Some(name) = node.child_by_field_name("name") {
473 sink.set_name(name);
474 }
475 attach_decorators(sink, node);
476 }
477 NormalizedKind::Assignment => {
478 if let Some(left) = node.child_by_field_name("left") {
479 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
480 }
481 if let Some(right) = node.child_by_field_name("right") {
482 attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
483 }
484 }
485 NormalizedKind::Import => match node.kind() {
486 "import_from_statement" => {
487 if let Some(module) = node.child_by_field_name("module_name") {
488 sink.role_named(Role::Module, module, module);
489 }
490 }
491 _ => {
492 for index in 0..node.named_child_count() {
493 if !sink.should_continue() {
494 break;
495 }
496 let Some(child) = node.named_child(index) else {
497 continue;
498 };
499 match child.kind() {
500 "dotted_name" => sink.role_named(Role::Module, child, child),
501 "aliased_import" => {
502 if let Some(name) = child.child_by_field_name("name") {
503 sink.role_named(Role::Module, name, name);
504 }
505 }
506 _ => {}
507 }
508 }
509 }
510 },
511 NormalizedKind::Identifier => sink.set_name(node),
512 NormalizedKind::Decorator => {
513 if let Some(name) = first_named_child(node).and_then(expression_name_node) {
514 sink.set_name(name);
515 }
516 }
517 NormalizedKind::ForLoop => {
518 if let Some(right) = node.child_by_field_name("right") {
519 attach_role_with_derived_name(
520 sink,
521 Role::Iterable,
522 right,
523 expression_name_node,
524 );
525 }
526 }
527 NormalizedKind::CollectionLiteral => {
528 for index in 0..node.named_child_count() {
529 let Some(child) = node.named_child(index) else {
530 continue;
531 };
532 if child.kind() == "comment" {
533 continue;
534 }
535 attach_role_with_derived_name(sink, Role::Element, child, expression_name_node);
536 }
537 }
538 _ => {}
539 }
540 }
541}