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_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 ("while_statement", NormalizedKind::WhileLoop),
69 ("block", NormalizedKind::Block),
74 ("decorator", NormalizedKind::Decorator),
75];
76
77fn attach_decorators(sink: &mut RoleSink<'_>, definition: Node<'_>) {
80 let Some(parent) = definition.parent() else {
81 return;
82 };
83 if parent.kind() != "decorated_definition" {
84 return;
85 }
86 for index in 0..parent.named_child_count() {
87 let Some(child) = parent.named_child(index) else {
88 continue;
89 };
90 if child.kind() == "decorator" {
91 attach_role_with_derived_name(sink, Role::Decorator, child, expression_name_node);
92 }
93 }
94}
95
96static PYTHON_OCCURRENCE_ROLE_SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
97 .supported(OccurrenceRole::DeclarationName)
98 .supported(OccurrenceRole::Binder)
99 .supported(OccurrenceRole::LabelOrKey)
100 .supported(OccurrenceRole::TypeOperand)
101 .supported(OccurrenceRole::PathSegment)
102 .supported(OccurrenceRole::ImportAlias)
103 .supported(OccurrenceRole::ImportTarget)
104 .supported(OccurrenceRole::ReceiverPosition)
105 .supported(OccurrenceRole::MemberPosition)
106 .supported(OccurrenceRole::ValueReference);
107
108fn python_dotted_name_is_import(dotted_name: Node<'_>) -> bool {
111 let mut current = dotted_name;
112 loop {
113 let Some(parent) = current.parent() else {
114 return false;
115 };
116 match parent.kind() {
117 "import_statement" | "import_from_statement" | "future_import_statement" => {
118 return true;
119 }
120 "aliased_import" | "dotted_name" => current = parent,
121 _ => return false,
122 }
123 }
124}
125
126fn python_occurrence_role(node: Node<'_>) -> Option<OccurrenceRole> {
132 if node.kind() != "identifier" {
133 return None;
134 }
135 let parent = node.parent()?;
136 let field = field_name_in_parent(parent, node);
137 let role = match parent.kind() {
138 "function_definition" | "class_definition" if field == Some("name") => {
139 OccurrenceRole::DeclarationName
140 }
141 "type" | "generic_type" | "type_parameter" | "constrained_type" | "union_type" => {
144 OccurrenceRole::TypeOperand
145 }
146 "parameters"
147 | "lambda_parameters"
148 | "typed_parameter"
149 | "list_splat_pattern"
150 | "dictionary_splat_pattern"
151 | "tuple_pattern"
152 | "list_pattern"
153 | "pattern_list"
154 | "as_pattern_target" => OccurrenceRole::Binder,
155 "default_parameter" | "typed_default_parameter" if field == Some("name") => {
156 OccurrenceRole::Binder
157 }
158 "for_statement" | "for_in_clause" if field == Some("left") => OccurrenceRole::Binder,
159 "keyword_argument" if field == Some("name") => OccurrenceRole::LabelOrKey,
160 "attribute" => match field {
161 Some("attribute") => OccurrenceRole::MemberPosition,
162 Some("object") => OccurrenceRole::ReceiverPosition,
163 _ => OccurrenceRole::ValueReference,
164 },
165 "aliased_import" if field == Some("alias") => OccurrenceRole::ImportAlias,
166 "import_from_statement" if field == Some("name") => OccurrenceRole::ImportTarget,
167 "dotted_name" => {
168 let is_tail =
169 parent.named_child(parent.named_child_count().saturating_sub(1)) == Some(node);
170 match (is_tail, python_dotted_name_is_import(parent)) {
171 (true, true) => OccurrenceRole::ImportTarget,
172 (true, false) => OccurrenceRole::ValueReference,
173 (false, _) => OccurrenceRole::PathSegment,
174 }
175 }
176 _ => OccurrenceRole::ValueReference,
177 };
178 Some(role)
179}
180
181fn python_definition_is_method(definition: Node<'_>) -> bool {
191 let mut current = definition;
192 while let Some(parent) = current.parent() {
193 match parent.kind() {
194 "class_definition" => return true,
195 "block" | "decorated_definition" => current = parent,
198 _ => return false,
199 }
200 }
201 false
202}
203
204fn python_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
215 let form = nearest_ancestor(binder, |kind| {
216 matches!(
217 kind,
218 "parameters"
219 | "lambda_parameters"
220 | "for_statement"
221 | "for_in_clause"
222 | "as_pattern"
223 | "list_comprehension"
224 | "set_comprehension"
225 | "dictionary_comprehension"
226 | "generator_expression"
227 | "function_definition"
228 )
229 })?;
230 match form.kind() {
231 "parameters" | "lambda_parameters" | "function_definition" => Some(BindingActivation {
232 kind: BindingKind::Parameter,
233 hoisting: HoistingClass::ScopeWide,
234 activation: scope,
235 }),
236 "for_in_clause" => {
237 let comprehension = nearest_ancestor(form, |kind| {
239 matches!(
240 kind,
241 "list_comprehension"
242 | "set_comprehension"
243 | "dictionary_comprehension"
244 | "generator_expression"
245 )
246 })?;
247 Some(BindingActivation {
248 kind: BindingKind::LoopVariable,
249 hoisting: HoistingClass::DeclaredHead,
250 activation: node_range(comprehension),
251 })
252 }
253 "for_statement" => Some(BindingActivation {
254 kind: BindingKind::LoopVariable,
255 hoisting: HoistingClass::ScopeWide,
256 activation: scope,
257 }),
258 "as_pattern" => Some(BindingActivation {
259 kind: BindingKind::PatternBinder,
260 hoisting: HoistingClass::ScopeWide,
261 activation: scope,
262 }),
263 _ => Some(BindingActivation {
264 kind: BindingKind::Local,
265 hoisting: HoistingClass::ScopeWide,
266 activation: scope,
267 }),
268 }
269}
270
271impl StructuralSpec for PythonStructuralSpec {
272 fn language(&self) -> Language {
273 Language::Python
274 }
275
276 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
277 &DEEP_REFERENCE_EDGE_SUPPORT
278 }
279
280 fn identity_route_support(&self) -> &IdentityRouteSupport {
281 static SUPPORT: IdentityRouteSupport = DEEP_IDENTITY_AXES
287 .supported_relation(RouteHopKind::Alias)
288 .supported_relation(RouteHopKind::Import)
289 .supported_relation(RouteHopKind::NestedOwner);
290 &SUPPORT
291 }
292
293 fn qualified_path_root<'tree>(&self, token: Node<'tree>) -> Option<Node<'tree>> {
296 if token.kind() != "identifier" {
297 return None;
298 }
299 token
300 .parent()
301 .filter(|parent| parent.kind() == "dotted_name")
302 }
303
304 fn path_segment_tokens<'tree>(&self, root: Node<'tree>) -> Vec<Node<'tree>> {
305 if root.kind() != "dotted_name" {
306 return Vec::new();
307 }
308 let mut cursor = root.walk();
309 root.named_children(&mut cursor)
310 .filter(|child| child.kind() == "identifier")
311 .collect()
312 }
313
314 fn indirection_relation(&self, token: Node<'_>) -> Option<RouteHopKind> {
315 nearest_ancestor(token, |kind| {
316 matches!(kind, "import_statement" | "import_from_statement")
317 })
318 .map(|_| RouteHopKind::Import)
319 }
320
321 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
322 PYTHON_KIND_TABLE
323 }
324
325 fn refine_kind(
326 &self,
327 node: Node<'_>,
328 kind: NormalizedKind,
329 _enclosing: Option<NormalizedKind>,
330 _source: &str,
331 _context: &CallSiteContext,
332 ) -> NormalizedKind {
333 if kind == NormalizedKind::Function && python_definition_is_method(node) {
334 NormalizedKind::Method
335 } else {
336 kind
337 }
338 }
339
340 fn should_extract(&self, node: Node<'_>, kind: NormalizedKind) -> bool {
341 kind != NormalizedKind::Assignment || node.child_by_field_name("right").is_some()
342 }
343
344 fn supports_kind(&self, kind: NormalizedKind) -> bool {
345 kind == NormalizedKind::Method
346 || self
347 .kind_table()
348 .iter()
349 .any(|(_, fact_kind)| fact_kind.satisfies(kind))
350 }
351
352 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
353 &PYTHON_OCCURRENCE_ROLE_SUPPORT
354 }
355
356 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
357 &DEEP_LEXICAL_ENVIRONMENT_SUPPORT
358 }
359
360 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
361 &PYTHON_MATERIALIZATION_SUPPORT
362 }
363
364 fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
365 python_binding_activation(binder, scope)
366 }
367
368 fn occurrence_namespace(
371 &self,
372 role: OccurrenceRole,
373 declares: Option<NormalizedKind>,
374 ) -> Option<Namespace> {
375 match role {
376 OccurrenceRole::PathSegment => Some(Namespace::Module),
377 _ => default_occurrence_namespace(role, declares),
378 }
379 }
380
381 fn embedded_leaf_facts(
382 &self,
383 node: Node<'_>,
384 kind: NormalizedKind,
385 source: &str,
386 cancellation: Option<&CancellationToken>,
387 ) -> Vec<EmbeddedLeafFact> {
388 if kind != NormalizedKind::StringLiteral
389 || node.kind() != "string"
390 || !python_node_is_in_annotation(node)
391 {
392 return Vec::new();
393 }
394
395 python_deferred_annotation_identifier_ranges(node, source, cancellation)
396 .unwrap_or_default()
397 .into_iter()
398 .map(|range| EmbeddedLeafFact {
399 kind: NormalizedKind::Identifier,
400 range,
401 occurrence_role: OccurrenceRole::TypeOperand,
402 })
403 .collect()
404 }
405
406 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
407 if let Some(role) = python_occurrence_role(node) {
408 sink.occurrence_role(node, role);
409 }
410 match kind {
411 NormalizedKind::Call => {
412 if let Some(function) = node.child_by_field_name("function") {
413 attach_terminal_callee(sink, function, expression_name_node(function));
416 if function.kind() == "attribute"
417 && let Some(object) = function.child_by_field_name("object")
418 {
419 attach_role_with_derived_name(
420 sink,
421 Role::Receiver,
422 object,
423 expression_name_node,
424 );
425 }
426 }
427 if let Some(arguments) = node.child_by_field_name("arguments") {
428 for index in 0..arguments.named_child_count() {
429 if !sink.should_continue() {
430 break;
431 }
432 let Some(argument) = arguments.named_child(index) else {
433 continue;
434 };
435 match argument.kind() {
436 "comment" => {}
437 "keyword_argument" => {
438 if let (Some(keyword), Some(value)) = (
439 argument.child_by_field_name("name"),
440 argument.child_by_field_name("value"),
441 ) {
442 sink.kwarg(keyword, value);
443 }
444 }
445 _ => attach_argument_role_with_derived_name(
446 sink,
447 argument,
448 expression_name_node,
449 ),
450 }
451 }
452 }
453 }
454 NormalizedKind::FieldAccess => {
455 if let Some(attribute) = node.child_by_field_name("attribute") {
456 sink.set_name(attribute);
457 sink.role_named(Role::Field, attribute, attribute);
458 }
459 if let Some(object) = node.child_by_field_name("object") {
460 attach_role_with_derived_name(sink, Role::Object, object, expression_name_node);
461 }
462 }
463 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Class => {
464 if let Some(name) = node.child_by_field_name("name") {
465 sink.set_name(name);
466 }
467 attach_decorators(sink, node);
468 }
469 NormalizedKind::Assignment => {
470 if let Some(left) = node.child_by_field_name("left") {
471 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
472 }
473 if let Some(right) = node.child_by_field_name("right") {
474 attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
475 }
476 }
477 NormalizedKind::Import => match node.kind() {
478 "import_from_statement" => {
479 if let Some(module) = node.child_by_field_name("module_name") {
480 sink.role_named(Role::Module, module, module);
481 }
482 }
483 _ => {
484 for index in 0..node.named_child_count() {
485 if !sink.should_continue() {
486 break;
487 }
488 let Some(child) = node.named_child(index) else {
489 continue;
490 };
491 match child.kind() {
492 "dotted_name" => sink.role_named(Role::Module, child, child),
493 "aliased_import" => {
494 if let Some(name) = child.child_by_field_name("name") {
495 sink.role_named(Role::Module, name, name);
496 }
497 }
498 _ => {}
499 }
500 }
501 }
502 },
503 NormalizedKind::Identifier => sink.set_name(node),
504 NormalizedKind::Decorator => {
505 if let Some(name) = first_named_child(node).and_then(expression_name_node) {
506 sink.set_name(name);
507 }
508 }
509 _ => {}
510 }
511 }
512}