use brokk_bifrost_core::analyzer::structural::adapter_helpers::{
attach_argument_role_with_derived_name, attach_role_with_derived_name, attach_terminal_callee,
field_name_in_parent, first_named_child, nearest_ancestor, node_range,
};
use brokk_bifrost_core::analyzer::structural::edges::{
DEEP_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
};
use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
use brokk_bifrost_core::analyzer::structural::materialization::{
DeclarationMaterializationSupport, PYTHON_MATERIALIZATION_SUPPORT,
};
use brokk_bifrost_core::analyzer::structural::occurrences::{
Namespace, OccurrenceRole, OccurrenceRoleSupport, default_occurrence_namespace,
};
use brokk_bifrost_core::analyzer::structural::resolution::{
BindingActivation, BindingKind, DEEP_LEXICAL_ENVIRONMENT_SUPPORT, HoistingClass,
LexicalEnvironmentSupport,
};
use brokk_bifrost_core::analyzer::structural::routes::{
DEEP_IDENTITY_AXES, IdentityRouteSupport, RouteHopKind,
};
use brokk_bifrost_core::analyzer::structural::spec::{EmbeddedLeafFact, RoleSink, StructuralSpec};
use brokk_bifrost_core::analyzer::{Language, Range};
use brokk_bifrost_core::cancellation::CancellationToken;
use tree_sitter::Node;
use crate::syntax::{
expression_name_node, python_deferred_annotation_identifier_ranges,
python_node_is_in_annotation,
};
#[derive(Debug, Default)]
pub struct PythonStructuralSpec;
pub static PYTHON_STRUCTURAL_SPEC: PythonStructuralSpec = PythonStructuralSpec;
pub const PYTHON_KIND_TABLE: &[(&str, NormalizedKind)] = &[
("call", NormalizedKind::Call),
("attribute", NormalizedKind::FieldAccess),
("function_definition", NormalizedKind::Function),
("lambda", NormalizedKind::Lambda),
("class_definition", NormalizedKind::Class),
("assignment", NormalizedKind::Assignment),
("import_statement", NormalizedKind::Import),
("import_from_statement", NormalizedKind::Import),
("identifier", NormalizedKind::Identifier),
("string", NormalizedKind::StringLiteral),
("concatenated_string", NormalizedKind::StringLiteral),
("integer", NormalizedKind::NumericLiteral),
("float", NormalizedKind::NumericLiteral),
("true", NormalizedKind::BooleanLiteral),
("false", NormalizedKind::BooleanLiteral),
("none", NormalizedKind::NullLiteral),
("return_statement", NormalizedKind::Return),
("raise_statement", NormalizedKind::Throw),
("except_clause", NormalizedKind::Catch),
("if_statement", NormalizedKind::If),
("for_statement", NormalizedKind::ForLoop),
("while_statement", NormalizedKind::WhileLoop),
("block", NormalizedKind::Block),
("decorator", NormalizedKind::Decorator),
];
fn attach_decorators(sink: &mut RoleSink<'_>, definition: Node<'_>) {
let Some(parent) = definition.parent() else {
return;
};
if parent.kind() != "decorated_definition" {
return;
}
for index in 0..parent.named_child_count() {
let Some(child) = parent.named_child(index) else {
continue;
};
if child.kind() == "decorator" {
attach_role_with_derived_name(sink, Role::Decorator, child, expression_name_node);
}
}
}
static PYTHON_OCCURRENCE_ROLE_SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
.supported(OccurrenceRole::DeclarationName)
.supported(OccurrenceRole::Binder)
.supported(OccurrenceRole::LabelOrKey)
.supported(OccurrenceRole::TypeOperand)
.supported(OccurrenceRole::PathSegment)
.supported(OccurrenceRole::ImportAlias)
.supported(OccurrenceRole::ImportTarget)
.supported(OccurrenceRole::ReceiverPosition)
.supported(OccurrenceRole::MemberPosition)
.supported(OccurrenceRole::ValueReference);
fn python_dotted_name_is_import(dotted_name: Node<'_>) -> bool {
let mut current = dotted_name;
loop {
let Some(parent) = current.parent() else {
return false;
};
match parent.kind() {
"import_statement" | "import_from_statement" | "future_import_statement" => {
return true;
}
"aliased_import" | "dotted_name" => current = parent,
_ => return false,
}
}
}
fn python_occurrence_role(node: Node<'_>) -> Option<OccurrenceRole> {
if node.kind() != "identifier" {
return None;
}
let parent = node.parent()?;
let field = field_name_in_parent(parent, node);
let role = match parent.kind() {
"function_definition" | "class_definition" if field == Some("name") => {
OccurrenceRole::DeclarationName
}
"type" | "generic_type" | "type_parameter" | "constrained_type" | "union_type" => {
OccurrenceRole::TypeOperand
}
"parameters"
| "lambda_parameters"
| "typed_parameter"
| "list_splat_pattern"
| "dictionary_splat_pattern"
| "tuple_pattern"
| "list_pattern"
| "pattern_list"
| "as_pattern_target" => OccurrenceRole::Binder,
"default_parameter" | "typed_default_parameter" if field == Some("name") => {
OccurrenceRole::Binder
}
"for_statement" | "for_in_clause" if field == Some("left") => OccurrenceRole::Binder,
"keyword_argument" if field == Some("name") => OccurrenceRole::LabelOrKey,
"attribute" => match field {
Some("attribute") => OccurrenceRole::MemberPosition,
Some("object") => OccurrenceRole::ReceiverPosition,
_ => OccurrenceRole::ValueReference,
},
"aliased_import" if field == Some("alias") => OccurrenceRole::ImportAlias,
"import_from_statement" if field == Some("name") => OccurrenceRole::ImportTarget,
"dotted_name" => {
let is_tail =
parent.named_child(parent.named_child_count().saturating_sub(1)) == Some(node);
match (is_tail, python_dotted_name_is_import(parent)) {
(true, true) => OccurrenceRole::ImportTarget,
(true, false) => OccurrenceRole::ValueReference,
(false, _) => OccurrenceRole::PathSegment,
}
}
_ => OccurrenceRole::ValueReference,
};
Some(role)
}
fn python_definition_is_method(definition: Node<'_>) -> bool {
let mut current = definition;
while let Some(parent) = current.parent() {
match parent.kind() {
"class_definition" => return true,
"block" | "decorated_definition" => current = parent,
_ => return false,
}
}
false
}
fn python_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
let form = nearest_ancestor(binder, |kind| {
matches!(
kind,
"parameters"
| "lambda_parameters"
| "for_statement"
| "for_in_clause"
| "as_pattern"
| "list_comprehension"
| "set_comprehension"
| "dictionary_comprehension"
| "generator_expression"
| "function_definition"
)
})?;
match form.kind() {
"parameters" | "lambda_parameters" | "function_definition" => Some(BindingActivation {
kind: BindingKind::Parameter,
hoisting: HoistingClass::ScopeWide,
activation: scope,
}),
"for_in_clause" => {
let comprehension = nearest_ancestor(form, |kind| {
matches!(
kind,
"list_comprehension"
| "set_comprehension"
| "dictionary_comprehension"
| "generator_expression"
)
})?;
Some(BindingActivation {
kind: BindingKind::LoopVariable,
hoisting: HoistingClass::DeclaredHead,
activation: node_range(comprehension),
})
}
"for_statement" => Some(BindingActivation {
kind: BindingKind::LoopVariable,
hoisting: HoistingClass::ScopeWide,
activation: scope,
}),
"as_pattern" => Some(BindingActivation {
kind: BindingKind::PatternBinder,
hoisting: HoistingClass::ScopeWide,
activation: scope,
}),
_ => Some(BindingActivation {
kind: BindingKind::Local,
hoisting: HoistingClass::ScopeWide,
activation: scope,
}),
}
}
impl StructuralSpec for PythonStructuralSpec {
fn language(&self) -> Language {
Language::Python
}
fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
&DEEP_REFERENCE_EDGE_SUPPORT
}
fn identity_route_support(&self) -> &IdentityRouteSupport {
static SUPPORT: IdentityRouteSupport = DEEP_IDENTITY_AXES
.supported_relation(RouteHopKind::Alias)
.supported_relation(RouteHopKind::Import)
.supported_relation(RouteHopKind::NestedOwner);
&SUPPORT
}
fn qualified_path_root<'tree>(&self, token: Node<'tree>) -> Option<Node<'tree>> {
if token.kind() != "identifier" {
return None;
}
token
.parent()
.filter(|parent| parent.kind() == "dotted_name")
}
fn path_segment_tokens<'tree>(&self, root: Node<'tree>) -> Vec<Node<'tree>> {
if root.kind() != "dotted_name" {
return Vec::new();
}
let mut cursor = root.walk();
root.named_children(&mut cursor)
.filter(|child| child.kind() == "identifier")
.collect()
}
fn indirection_relation(&self, token: Node<'_>) -> Option<RouteHopKind> {
nearest_ancestor(token, |kind| {
matches!(kind, "import_statement" | "import_from_statement")
})
.map(|_| RouteHopKind::Import)
}
fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
PYTHON_KIND_TABLE
}
fn refine_kind(
&self,
node: Node<'_>,
kind: NormalizedKind,
_enclosing: Option<NormalizedKind>,
_source: &str,
) -> NormalizedKind {
if kind == NormalizedKind::Function && python_definition_is_method(node) {
NormalizedKind::Method
} else {
kind
}
}
fn should_extract(&self, node: Node<'_>, kind: NormalizedKind) -> bool {
kind != NormalizedKind::Assignment || node.child_by_field_name("right").is_some()
}
fn supports_kind(&self, kind: NormalizedKind) -> bool {
kind == NormalizedKind::Method
|| self
.kind_table()
.iter()
.any(|(_, fact_kind)| fact_kind.satisfies(kind))
}
fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
&PYTHON_OCCURRENCE_ROLE_SUPPORT
}
fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
&DEEP_LEXICAL_ENVIRONMENT_SUPPORT
}
fn materialization_support(&self) -> &DeclarationMaterializationSupport {
&PYTHON_MATERIALIZATION_SUPPORT
}
fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
python_binding_activation(binder, scope)
}
fn occurrence_namespace(
&self,
role: OccurrenceRole,
declares: Option<NormalizedKind>,
) -> Option<Namespace> {
match role {
OccurrenceRole::PathSegment => Some(Namespace::Module),
_ => default_occurrence_namespace(role, declares),
}
}
fn embedded_leaf_facts(
&self,
node: Node<'_>,
kind: NormalizedKind,
source: &str,
cancellation: Option<&CancellationToken>,
) -> Vec<EmbeddedLeafFact> {
if kind != NormalizedKind::StringLiteral
|| node.kind() != "string"
|| !python_node_is_in_annotation(node)
{
return Vec::new();
}
python_deferred_annotation_identifier_ranges(node, source, cancellation)
.unwrap_or_default()
.into_iter()
.map(|range| EmbeddedLeafFact {
kind: NormalizedKind::Identifier,
range,
occurrence_role: OccurrenceRole::TypeOperand,
})
.collect()
}
fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
if let Some(role) = python_occurrence_role(node) {
sink.occurrence_role(node, role);
}
match kind {
NormalizedKind::Call => {
if let Some(function) = node.child_by_field_name("function") {
attach_terminal_callee(sink, function, expression_name_node(function));
if function.kind() == "attribute"
&& let Some(object) = function.child_by_field_name("object")
{
attach_role_with_derived_name(
sink,
Role::Receiver,
object,
expression_name_node,
);
}
}
if let Some(arguments) = node.child_by_field_name("arguments") {
for index in 0..arguments.named_child_count() {
if !sink.should_continue() {
break;
}
let Some(argument) = arguments.named_child(index) else {
continue;
};
match argument.kind() {
"comment" => {}
"keyword_argument" => {
if let (Some(keyword), Some(value)) = (
argument.child_by_field_name("name"),
argument.child_by_field_name("value"),
) {
sink.kwarg(keyword, value);
}
}
_ => attach_argument_role_with_derived_name(
sink,
argument,
expression_name_node,
),
}
}
}
}
NormalizedKind::FieldAccess => {
if let Some(attribute) = node.child_by_field_name("attribute") {
sink.set_name(attribute);
sink.role_named(Role::Field, attribute, attribute);
}
if let Some(object) = node.child_by_field_name("object") {
attach_role_with_derived_name(sink, Role::Object, object, expression_name_node);
}
}
NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Class => {
if let Some(name) = node.child_by_field_name("name") {
sink.set_name(name);
}
attach_decorators(sink, node);
}
NormalizedKind::Assignment => {
if let Some(left) = node.child_by_field_name("left") {
attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
}
if let Some(right) = node.child_by_field_name("right") {
attach_role_with_derived_name(sink, Role::Right, right, expression_name_node);
}
}
NormalizedKind::Import => match node.kind() {
"import_from_statement" => {
if let Some(module) = node.child_by_field_name("module_name") {
sink.role_named(Role::Module, module, module);
}
}
_ => {
for index in 0..node.named_child_count() {
if !sink.should_continue() {
break;
}
let Some(child) = node.named_child(index) else {
continue;
};
match child.kind() {
"dotted_name" => sink.role_named(Role::Module, child, child),
"aliased_import" => {
if let Some(name) = child.child_by_field_name("name") {
sink.role_named(Role::Module, name, name);
}
}
_ => {}
}
}
}
},
NormalizedKind::Identifier => sink.set_name(node),
NormalizedKind::Decorator => {
if let Some(name) = first_named_child(node).and_then(expression_name_node) {
sink.set_name(name);
}
}
_ => {}
}
}
}