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