use std::ops::Deref; use ruff_db::files::{File, FileRange}; use ruff_db::parsed::{ParsedModuleRef, parsed_module}; use ruff_python_ast as ast; use ruff_text_size::{Ranged, TextRange}; use crate::Db; use crate::ast_node_ref::AstNodeRef; use crate::node_key::NodeKey; use crate::semantic_index::place::ScopedPlaceId; use crate::semantic_index::scope::{FileScopeId, ScopeId}; use crate::semantic_index::symbol::ScopedSymbolId; use crate::unpack::{Unpack, UnpackPosition}; /// A definition of a place. /// /// ## ID stability /// The `Definition`'s ID is stable when the only field that change is its `kind` (AST node). /// /// The `Definition` changes when the `file`, `scope`, or `place` change. This can be /// because a new scope gets inserted before the `Definition` or a new place is inserted /// before this `Definition`. However, the ID can be considered stable and it is okay to use /// `Definition` in cross-module` salsa queries or as a field on other salsa tracked structs. /// /// # Ordering /// Ordering is based on the definition's salsa-assigned id and not on its values. /// The id may change between runs, or when the definition was garbage collected and recreated. #[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)] #[derive(Ord, PartialOrd)] pub struct Definition<'db> { /// The file in which the definition occurs. pub file: File, /// The scope in which the definition occurs. pub(crate) file_scope: FileScopeId, /// The place ID of the definition. pub(crate) place: ScopedPlaceId, /// WARNING: Only access this field when doing type inference for the same /// file as where `Definition` is defined to avoid cross-file query dependencies. #[no_eq] #[returns(ref)] #[tracked] pub kind: DefinitionKind<'db>, /// This is a dedicated field to avoid accessing `kind` to compute this value. pub(crate) is_reexported: bool, } // The Salsa heap is tracked separately. impl get_size2::GetSize for Definition<'_> {} impl<'db> Definition<'db> { pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> { self.file_scope(db).to_scope_id(db, self.file(db)) } pub fn full_range(self, db: &'db dyn Db, module: &ParsedModuleRef) -> FileRange { FileRange::new(self.file(db), self.kind(db).full_range(module)) } pub fn focus_range(self, db: &'db dyn Db, module: &ParsedModuleRef) -> FileRange { FileRange::new(self.file(db), self.kind(db).target_range(module)) } /// Returns the name of the item being defined, if applicable. pub fn name(self, db: &'db dyn Db) -> Option { let file = self.file(db); let module = parsed_module(db, file).load(db); let kind = self.kind(db); match kind { DefinitionKind::Function(def) => { let node = def.node(&module); Some(node.name.as_str().to_string()) } DefinitionKind::Class(def) => { let node = def.node(&module); Some(node.name.as_str().to_string()) } DefinitionKind::TypeAlias(def) => { let node = def.node(&module); Some( node.name .as_name_expr() .expect("type alias name should be a NameExpr") .id .as_str() .to_string(), ) } DefinitionKind::Assignment(assignment) => { let target_node = assignment.target.node(&module); target_node .as_name_expr() .map(|name_expr| name_expr.id.as_str().to_string()) } _ => None, } } /// Extract a docstring from this definition, if applicable. /// This method returns a docstring for function and class definitions. /// The docstring is extracted from the first statement in the body if it's a string literal. pub fn docstring(self, db: &'db dyn Db) -> Option { let file = self.file(db); let module = parsed_module(db, file).load(db); let kind = self.kind(db); match kind { DefinitionKind::Function(function_def) => { let function_node = function_def.node(&module); docstring_from_body(&function_node.body) .map(|docstring_expr| docstring_expr.value.to_str().to_owned()) } DefinitionKind::Class(class_def) => { let class_node = class_def.node(&module); docstring_from_body(&class_node.body) .map(|docstring_expr| docstring_expr.value.to_str().to_owned()) } _ => None, } } } /// Get the module-level docstring for the given file pub(crate) fn module_docstring(db: &dyn Db, file: File) -> Option { let module = parsed_module(db, file).load(db); docstring_from_body(module.suite()) .map(|docstring_expr| docstring_expr.value.to_str().to_owned()) } /// Extract a docstring from a function, module, or class body. fn docstring_from_body(body: &[ast::Stmt]) -> Option<&ast::ExprStringLiteral> { let stmt = body.first()?; // Require the docstring to be a standalone expression. let ast::Stmt::Expr(ast::StmtExpr { value, range: _, node_index: _, }) = stmt else { return None; }; // Only match string literals. value.as_string_literal_expr() } /// One or more [`Definition`]s. #[derive(Debug, Default, PartialEq, Eq, salsa::Update, get_size2::GetSize)] pub struct Definitions<'db> { definitions: smallvec::SmallVec<[Definition<'db>; 1]>, } impl<'db> Definitions<'db> { pub(crate) fn single(definition: Definition<'db>) -> Self { Self { definitions: smallvec::smallvec_inline![definition], } } pub(crate) fn push(&mut self, definition: Definition<'db>) { self.definitions.push(definition); } } impl<'db> Deref for Definitions<'db> { type Target = [Definition<'db>]; fn deref(&self) -> &Self::Target { &self.definitions } } impl<'a, 'db> IntoIterator for &'a Definitions<'db> { type Item = &'a Definition<'db>; type IntoIter = std::slice::Iter<'a, Definition<'db>>; fn into_iter(self) -> Self::IntoIter { self.definitions.iter() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, salsa::Update, get_size2::GetSize)] pub(crate) enum DefinitionState<'db> { Defined(Definition<'db>), /// Represents the implicit "unbound"/"undeclared" definition of every place. Undefined, /// Represents a definition that has been deleted. /// This used when an attribute/subscript definition (such as `x.y = ...`, `x[0] = ...`) becomes obsolete due to a reassignment of the root place. Deleted, } impl<'db> DefinitionState<'db> { pub(crate) fn is_defined_and(self, f: impl Fn(Definition<'db>) -> bool) -> bool { matches!(self, DefinitionState::Defined(def) if f(def)) } pub(crate) fn is_undefined_or(self, f: impl Fn(Definition<'db>) -> bool) -> bool { matches!(self, DefinitionState::Undefined) || matches!(self, DefinitionState::Defined(def) if f(def)) } #[allow(unused)] pub(crate) fn definition(self) -> Option> { match self { DefinitionState::Defined(def) => Some(def), DefinitionState::Deleted | DefinitionState::Undefined => None, } } } #[derive(Copy, Clone, Debug)] pub(crate) enum DefinitionNodeRef<'ast, 'db> { Import(ImportDefinitionNodeRef<'ast>), ImportFrom(ImportFromDefinitionNodeRef<'ast>), ImportFromSubmodule(ImportFromSubmoduleDefinitionNodeRef<'ast>), ImportStar(StarImportDefinitionNodeRef<'ast>), For(ForStmtDefinitionNodeRef<'ast, 'db>), Function(&'ast ast::StmtFunctionDef), Class(&'ast ast::StmtClassDef), TypeAlias(&'ast ast::StmtTypeAlias), NamedExpression(&'ast ast::ExprNamed), Assignment(AssignmentDefinitionNodeRef<'ast, 'db>), AnnotatedAssignment(AnnotatedAssignmentDefinitionNodeRef<'ast>), AugmentedAssignment(&'ast ast::StmtAugAssign), Comprehension(ComprehensionDefinitionNodeRef<'ast, 'db>), VariadicPositionalParameter(&'ast ast::Parameter), VariadicKeywordParameter(&'ast ast::Parameter), Parameter(&'ast ast::ParameterWithDefault), WithItem(WithItemDefinitionNodeRef<'ast, 'db>), MatchPattern(MatchPatternDefinitionNodeRef<'ast>), ExceptHandler(ExceptHandlerDefinitionNodeRef<'ast>), TypeVar(&'ast ast::TypeParamTypeVar), ParamSpec(&'ast ast::TypeParamParamSpec), TypeVarTuple(&'ast ast::TypeParamTypeVarTuple), } impl<'ast> From<&'ast ast::StmtFunctionDef> for DefinitionNodeRef<'ast, '_> { fn from(node: &'ast ast::StmtFunctionDef) -> Self { Self::Function(node) } } impl<'ast> From<&'ast ast::StmtClassDef> for DefinitionNodeRef<'ast, '_> { fn from(node: &'ast ast::StmtClassDef) -> Self { Self::Class(node) } } impl<'ast> From<&'ast ast::StmtTypeAlias> for DefinitionNodeRef<'ast, '_> { fn from(node: &'ast ast::StmtTypeAlias) -> Self { Self::TypeAlias(node) } } impl<'ast> From<&'ast ast::ExprNamed> for DefinitionNodeRef<'ast, '_> { fn from(node: &'ast ast::ExprNamed) -> Self { Self::NamedExpression(node) } } impl<'ast> From<&'ast ast::StmtAugAssign> for DefinitionNodeRef<'ast, '_> { fn from(node: &'ast ast::StmtAugAssign) -> Self { Self::AugmentedAssignment(node) } } impl<'ast> From<&'ast ast::TypeParamTypeVar> for DefinitionNodeRef<'ast, '_> { fn from(value: &'ast ast::TypeParamTypeVar) -> Self { Self::TypeVar(value) } } impl<'ast> From<&'ast ast::TypeParamParamSpec> for DefinitionNodeRef<'ast, '_> { fn from(value: &'ast ast::TypeParamParamSpec) -> Self { Self::ParamSpec(value) } } impl<'ast> From<&'ast ast::TypeParamTypeVarTuple> for DefinitionNodeRef<'ast, '_> { fn from(value: &'ast ast::TypeParamTypeVarTuple) -> Self { Self::TypeVarTuple(value) } } impl<'ast> From> for DefinitionNodeRef<'ast, '_> { fn from(node_ref: ImportDefinitionNodeRef<'ast>) -> Self { Self::Import(node_ref) } } impl<'ast> From> for DefinitionNodeRef<'ast, '_> { fn from(node_ref: ImportFromDefinitionNodeRef<'ast>) -> Self { Self::ImportFrom(node_ref) } } impl<'ast> From> for DefinitionNodeRef<'ast, '_> { fn from(node_ref: ImportFromSubmoduleDefinitionNodeRef<'ast>) -> Self { Self::ImportFromSubmodule(node_ref) } } impl<'ast, 'db> From> for DefinitionNodeRef<'ast, 'db> { fn from(value: ForStmtDefinitionNodeRef<'ast, 'db>) -> Self { Self::For(value) } } impl<'ast, 'db> From> for DefinitionNodeRef<'ast, 'db> { fn from(node_ref: AssignmentDefinitionNodeRef<'ast, 'db>) -> Self { Self::Assignment(node_ref) } } impl<'ast> From> for DefinitionNodeRef<'ast, '_> { fn from(node_ref: AnnotatedAssignmentDefinitionNodeRef<'ast>) -> Self { Self::AnnotatedAssignment(node_ref) } } impl<'ast, 'db> From> for DefinitionNodeRef<'ast, 'db> { fn from(node_ref: WithItemDefinitionNodeRef<'ast, 'db>) -> Self { Self::WithItem(node_ref) } } impl<'ast, 'db> From> for DefinitionNodeRef<'ast, 'db> { fn from(node: ComprehensionDefinitionNodeRef<'ast, 'db>) -> Self { Self::Comprehension(node) } } impl<'ast> From<&'ast ast::ParameterWithDefault> for DefinitionNodeRef<'ast, '_> { fn from(node: &'ast ast::ParameterWithDefault) -> Self { Self::Parameter(node) } } impl<'ast> From> for DefinitionNodeRef<'ast, '_> { fn from(node: MatchPatternDefinitionNodeRef<'ast>) -> Self { Self::MatchPattern(node) } } impl<'ast> From> for DefinitionNodeRef<'ast, '_> { fn from(node: StarImportDefinitionNodeRef<'ast>) -> Self { Self::ImportStar(node) } } #[derive(Copy, Clone, Debug)] pub(crate) struct ImportDefinitionNodeRef<'ast> { pub(crate) node: &'ast ast::StmtImport, pub(crate) alias_index: usize, pub(crate) is_reexported: bool, } #[derive(Copy, Clone, Debug)] pub(crate) struct StarImportDefinitionNodeRef<'ast> { pub(crate) node: &'ast ast::StmtImportFrom, pub(crate) symbol_id: ScopedSymbolId, } #[derive(Copy, Clone, Debug)] pub(crate) struct ImportFromDefinitionNodeRef<'ast> { pub(crate) node: &'ast ast::StmtImportFrom, pub(crate) alias_index: usize, pub(crate) is_reexported: bool, } #[derive(Copy, Clone, Debug)] pub(crate) struct ImportFromSubmoduleDefinitionNodeRef<'ast> { pub(crate) node: &'ast ast::StmtImportFrom, } #[derive(Copy, Clone, Debug)] pub(crate) struct AssignmentDefinitionNodeRef<'ast, 'db> { pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>, pub(crate) value: &'ast ast::Expr, pub(crate) target: &'ast ast::Expr, } #[derive(Copy, Clone, Debug)] pub(crate) struct AnnotatedAssignmentDefinitionNodeRef<'ast> { pub(crate) node: &'ast ast::StmtAnnAssign, pub(crate) annotation: &'ast ast::Expr, pub(crate) value: Option<&'ast ast::Expr>, pub(crate) target: &'ast ast::Expr, } #[derive(Copy, Clone, Debug)] pub(crate) struct WithItemDefinitionNodeRef<'ast, 'db> { pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>, pub(crate) context_expr: &'ast ast::Expr, pub(crate) target: &'ast ast::Expr, pub(crate) is_async: bool, } #[derive(Copy, Clone, Debug)] pub(crate) struct ForStmtDefinitionNodeRef<'ast, 'db> { pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>, pub(crate) iterable: &'ast ast::Expr, pub(crate) target: &'ast ast::Expr, pub(crate) is_async: bool, } #[derive(Copy, Clone, Debug)] pub(crate) struct ExceptHandlerDefinitionNodeRef<'ast> { pub(crate) handler: &'ast ast::ExceptHandlerExceptHandler, pub(crate) is_star: bool, } #[derive(Copy, Clone, Debug)] pub(crate) struct ComprehensionDefinitionNodeRef<'ast, 'db> { pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>, pub(crate) iterable: &'ast ast::Expr, pub(crate) target: &'ast ast::Expr, pub(crate) first: bool, pub(crate) is_async: bool, } #[derive(Copy, Clone, Debug)] pub(crate) struct MatchPatternDefinitionNodeRef<'ast> { /// The outermost pattern node in which the identifier being defined occurs. pub(crate) pattern: &'ast ast::Pattern, /// The identifier being defined. pub(crate) identifier: &'ast ast::Identifier, /// The index of the identifier in the pattern when visiting the `pattern` node in evaluation /// order. pub(crate) index: u32, } impl<'db> DefinitionNodeRef<'_, 'db> { pub(super) fn into_owned(self, parsed: &ParsedModuleRef) -> DefinitionKind<'db> { match self { DefinitionNodeRef::Import(ImportDefinitionNodeRef { node, alias_index, is_reexported, }) => DefinitionKind::Import(ImportDefinitionKind { node: AstNodeRef::new(parsed, node), alias_index, is_reexported, }), DefinitionNodeRef::ImportFrom(ImportFromDefinitionNodeRef { node, alias_index, is_reexported, }) => DefinitionKind::ImportFrom(ImportFromDefinitionKind { node: AstNodeRef::new(parsed, node), alias_index, is_reexported, }), DefinitionNodeRef::ImportFromSubmodule(ImportFromSubmoduleDefinitionNodeRef { node, }) => DefinitionKind::ImportFromSubmodule(ImportFromSubmoduleDefinitionKind { node: AstNodeRef::new(parsed, node), }), DefinitionNodeRef::ImportStar(star_import) => { let StarImportDefinitionNodeRef { node, symbol_id } = star_import; DefinitionKind::StarImport(StarImportDefinitionKind { node: AstNodeRef::new(parsed, node), symbol_id, }) } DefinitionNodeRef::Function(function) => { DefinitionKind::Function(AstNodeRef::new(parsed, function)) } DefinitionNodeRef::Class(class) => { DefinitionKind::Class(AstNodeRef::new(parsed, class)) } DefinitionNodeRef::TypeAlias(type_alias) => { DefinitionKind::TypeAlias(AstNodeRef::new(parsed, type_alias)) } DefinitionNodeRef::NamedExpression(named) => { DefinitionKind::NamedExpression(AstNodeRef::new(parsed, named)) } DefinitionNodeRef::Assignment(AssignmentDefinitionNodeRef { unpack, value, target, }) => DefinitionKind::Assignment(AssignmentDefinitionKind { target_kind: TargetKind::from(unpack), value: AstNodeRef::new(parsed, value), target: AstNodeRef::new(parsed, target), }), DefinitionNodeRef::AnnotatedAssignment(AnnotatedAssignmentDefinitionNodeRef { node: _, annotation, value, target, }) => DefinitionKind::AnnotatedAssignment(AnnotatedAssignmentDefinitionKind { target: AstNodeRef::new(parsed, target), annotation: AstNodeRef::new(parsed, annotation), value: value.map(|v| AstNodeRef::new(parsed, v)), }), DefinitionNodeRef::AugmentedAssignment(augmented_assignment) => { DefinitionKind::AugmentedAssignment(AstNodeRef::new(parsed, augmented_assignment)) } DefinitionNodeRef::For(ForStmtDefinitionNodeRef { unpack, iterable, target, is_async, }) => DefinitionKind::For(ForStmtDefinitionKind { target_kind: TargetKind::from(unpack), iterable: AstNodeRef::new(parsed, iterable), target: AstNodeRef::new(parsed, target), is_async, }), DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef { unpack, iterable, target, first, is_async, }) => DefinitionKind::Comprehension(ComprehensionDefinitionKind { target_kind: TargetKind::from(unpack), iterable: AstNodeRef::new(parsed, iterable), target: AstNodeRef::new(parsed, target), first, is_async, }), DefinitionNodeRef::VariadicPositionalParameter(parameter) => { DefinitionKind::VariadicPositionalParameter(AstNodeRef::new(parsed, parameter)) } DefinitionNodeRef::VariadicKeywordParameter(parameter) => { DefinitionKind::VariadicKeywordParameter(AstNodeRef::new(parsed, parameter)) } DefinitionNodeRef::Parameter(parameter) => { DefinitionKind::Parameter(AstNodeRef::new(parsed, parameter)) } DefinitionNodeRef::WithItem(WithItemDefinitionNodeRef { unpack, context_expr, target, is_async, }) => DefinitionKind::WithItem(WithItemDefinitionKind { target_kind: TargetKind::from(unpack), context_expr: AstNodeRef::new(parsed, context_expr), target: AstNodeRef::new(parsed, target), is_async, }), DefinitionNodeRef::MatchPattern(MatchPatternDefinitionNodeRef { pattern, identifier, index, }) => DefinitionKind::MatchPattern(MatchPatternDefinitionKind { pattern: AstNodeRef::new(parsed, pattern), identifier: AstNodeRef::new(parsed, identifier), index, }), DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef { handler, is_star, }) => DefinitionKind::ExceptHandler(ExceptHandlerDefinitionKind { handler: AstNodeRef::new(parsed, handler), is_star, }), DefinitionNodeRef::TypeVar(node) => { DefinitionKind::TypeVar(AstNodeRef::new(parsed, node)) } DefinitionNodeRef::ParamSpec(node) => { DefinitionKind::ParamSpec(AstNodeRef::new(parsed, node)) } DefinitionNodeRef::TypeVarTuple(node) => { DefinitionKind::TypeVarTuple(AstNodeRef::new(parsed, node)) } } } pub(super) fn key(self) -> DefinitionNodeKey { match self { Self::Import(ImportDefinitionNodeRef { node, alias_index, is_reexported: _, }) => (&node.names[alias_index]).into(), Self::ImportFrom(ImportFromDefinitionNodeRef { node, alias_index, is_reexported: _, }) => (&node.names[alias_index]).into(), Self::ImportFromSubmodule(ImportFromSubmoduleDefinitionNodeRef { node }) => node.into(), // INVARIANT: for an invalid-syntax statement such as `from foo import *, bar, *`, // we only create a `StarImportDefinitionKind` for the *first* `*` alias in the names list. Self::ImportStar(StarImportDefinitionNodeRef { node, symbol_id: _ }) => node .names .iter() .find(|alias| &alias.name == "*") .expect( "The `StmtImportFrom` node of a `StarImportDefinitionKind` instance \ should always have at least one `alias` with the name `*`.", ) .into(), Self::Function(node) => node.into(), Self::Class(node) => node.into(), Self::TypeAlias(node) => node.into(), Self::NamedExpression(node) => node.into(), Self::Assignment(AssignmentDefinitionNodeRef { value: _, unpack: _, target, }) => DefinitionNodeKey(NodeKey::from_node(target)), Self::AnnotatedAssignment(ann_assign) => ann_assign.node.into(), Self::AugmentedAssignment(node) => node.into(), Self::For(ForStmtDefinitionNodeRef { target, iterable: _, unpack: _, is_async: _, }) => DefinitionNodeKey(NodeKey::from_node(target)), Self::Comprehension(ComprehensionDefinitionNodeRef { target, .. }) => { DefinitionNodeKey(NodeKey::from_node(target)) } Self::VariadicPositionalParameter(node) => node.into(), Self::VariadicKeywordParameter(node) => node.into(), Self::Parameter(node) => node.into(), Self::WithItem(WithItemDefinitionNodeRef { context_expr: _, unpack: _, is_async: _, target, }) => DefinitionNodeKey(NodeKey::from_node(target)), Self::MatchPattern(MatchPatternDefinitionNodeRef { identifier, .. }) => { identifier.into() } Self::ExceptHandler(ExceptHandlerDefinitionNodeRef { handler, .. }) => handler.into(), Self::TypeVar(node) => node.into(), Self::ParamSpec(node) => node.into(), Self::TypeVarTuple(node) => node.into(), } } } #[derive(Clone, Copy, Debug)] pub(crate) enum DefinitionCategory { /// A Definition which binds a value to a name (e.g. `x = 1`). Binding, /// A Definition which declares the upper-bound of acceptable types for this name (`x: int`). Declaration, /// A Definition which both declares a type and binds a value (e.g. `x: int = 1`). DeclarationAndBinding, } impl DefinitionCategory { /// True if this definition establishes a "declared type" for the place. /// /// If so, any assignments reached by this definition are in error if they assign a value of a /// type not assignable to the declared type. /// /// Annotations establish a declared type. So do function and class definitions, and imports. pub(crate) fn is_declaration(self) -> bool { matches!( self, DefinitionCategory::Declaration | DefinitionCategory::DeclarationAndBinding ) } /// True if this definition assigns a value to the place. /// /// False only for annotated assignments without a RHS. pub(crate) fn is_binding(self) -> bool { matches!( self, DefinitionCategory::Binding | DefinitionCategory::DeclarationAndBinding ) } } /// The kind of a definition. /// /// ## Usage in salsa tracked structs /// /// [`DefinitionKind`] fields in salsa tracked structs should be tracked (attributed with `#[tracked]`) /// because the kind is a thin wrapper around [`AstNodeRef`]. See the [`AstNodeRef`] documentation /// for an in-depth explanation of why this is necessary. #[derive(Clone, Debug, get_size2::GetSize)] pub enum DefinitionKind<'db> { Import(ImportDefinitionKind), ImportFrom(ImportFromDefinitionKind), ImportFromSubmodule(ImportFromSubmoduleDefinitionKind), StarImport(StarImportDefinitionKind), Function(AstNodeRef), Class(AstNodeRef), TypeAlias(AstNodeRef), NamedExpression(AstNodeRef), Assignment(AssignmentDefinitionKind<'db>), AnnotatedAssignment(AnnotatedAssignmentDefinitionKind), AugmentedAssignment(AstNodeRef), For(ForStmtDefinitionKind<'db>), Comprehension(ComprehensionDefinitionKind<'db>), VariadicPositionalParameter(AstNodeRef), VariadicKeywordParameter(AstNodeRef), Parameter(AstNodeRef), WithItem(WithItemDefinitionKind<'db>), MatchPattern(MatchPatternDefinitionKind), ExceptHandler(ExceptHandlerDefinitionKind), TypeVar(AstNodeRef), ParamSpec(AstNodeRef), TypeVarTuple(AstNodeRef), } impl DefinitionKind<'_> { pub(crate) fn is_reexported(&self) -> bool { match self { DefinitionKind::Import(import) => import.is_reexported(), DefinitionKind::ImportFrom(import) => import.is_reexported(), DefinitionKind::ImportFromSubmodule(_) => true, _ => true, } } pub(crate) const fn as_star_import(&self) -> Option<&StarImportDefinitionKind> { match self { DefinitionKind::StarImport(import) => Some(import), _ => None, } } pub(crate) const fn as_class(&self) -> Option<&AstNodeRef> { match self { DefinitionKind::Class(class) => Some(class), _ => None, } } pub(crate) fn is_import(&self) -> bool { matches!( self, DefinitionKind::Import(_) | DefinitionKind::ImportFrom(_) | DefinitionKind::StarImport(_) | DefinitionKind::ImportFromSubmodule(_) ) } pub(crate) const fn is_unannotated_assignment(&self) -> bool { matches!(self, DefinitionKind::Assignment(_)) } pub(crate) const fn is_function_def(&self) -> bool { matches!(self, DefinitionKind::Function(_)) } /// Returns the [`TextRange`] of the definition target. /// /// A definition target would mainly be the node representing the place being defined i.e., /// [`ast::ExprName`], [`ast::Identifier`], [`ast::ExprAttribute`] or [`ast::ExprSubscript`] but could also be other nodes. pub(crate) fn target_range(&self, module: &ParsedModuleRef) -> TextRange { match self { DefinitionKind::Import(import) => import.alias(module).range(), DefinitionKind::ImportFrom(import) => import.alias(module).range(), DefinitionKind::ImportFromSubmodule(import) => import.import(module).range(), DefinitionKind::StarImport(import) => import.alias(module).range(), DefinitionKind::Function(function) => function.node(module).name.range(), DefinitionKind::Class(class) => class.node(module).name.range(), DefinitionKind::TypeAlias(type_alias) => type_alias.node(module).name.range(), DefinitionKind::NamedExpression(named) => named.node(module).target.range(), DefinitionKind::Assignment(assignment) => assignment.target.node(module).range(), DefinitionKind::AnnotatedAssignment(assign) => assign.target.node(module).range(), DefinitionKind::AugmentedAssignment(aug_assign) => { aug_assign.node(module).target.range() } DefinitionKind::For(for_stmt) => for_stmt.target.node(module).range(), DefinitionKind::Comprehension(comp) => comp.target(module).range(), DefinitionKind::VariadicPositionalParameter(parameter) => { parameter.node(module).name.range() } DefinitionKind::VariadicKeywordParameter(parameter) => { parameter.node(module).name.range() } DefinitionKind::Parameter(parameter) => parameter.node(module).parameter.name.range(), DefinitionKind::WithItem(with_item) => with_item.target.node(module).range(), DefinitionKind::MatchPattern(match_pattern) => { match_pattern.identifier.node(module).range() } DefinitionKind::ExceptHandler(handler) => handler.node(module).range(), DefinitionKind::TypeVar(type_var) => type_var.node(module).name.range(), DefinitionKind::ParamSpec(param_spec) => param_spec.node(module).name.range(), DefinitionKind::TypeVarTuple(type_var_tuple) => { type_var_tuple.node(module).name.range() } } } /// Returns the [`TextRange`] of the entire definition. pub(crate) fn full_range(&self, module: &ParsedModuleRef) -> TextRange { match self { DefinitionKind::Import(import) => import.alias(module).range(), DefinitionKind::ImportFrom(import) => import.alias(module).range(), DefinitionKind::ImportFromSubmodule(import) => import.import(module).range(), DefinitionKind::StarImport(import) => import.import(module).range(), DefinitionKind::Function(function) => function.node(module).range(), DefinitionKind::Class(class) => class.node(module).range(), DefinitionKind::TypeAlias(type_alias) => type_alias.node(module).range(), DefinitionKind::NamedExpression(named) => named.node(module).range(), DefinitionKind::Assignment(assign) => { let target_range = assign.target.node(module).range(); let value_range = assign.value.node(module).range(); target_range.cover(value_range) } DefinitionKind::AnnotatedAssignment(assign) => { let mut full_range = assign.target.node(module).range(); full_range = full_range.cover(assign.annotation.node(module).range()); if let Some(ref value) = assign.value { full_range = full_range.cover(value.node(module).range()); } full_range } DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.node(module).range(), DefinitionKind::For(for_stmt) => for_stmt.target.node(module).range(), DefinitionKind::Comprehension(comp) => comp.target(module).range(), DefinitionKind::VariadicPositionalParameter(parameter) => { parameter.node(module).range() } DefinitionKind::VariadicKeywordParameter(parameter) => parameter.node(module).range(), DefinitionKind::Parameter(parameter) => parameter.node(module).parameter.range(), DefinitionKind::WithItem(with_item) => with_item.target.node(module).range(), DefinitionKind::MatchPattern(match_pattern) => { match_pattern.identifier.node(module).range() } DefinitionKind::ExceptHandler(handler) => handler.node(module).range(), DefinitionKind::TypeVar(type_var) => type_var.node(module).range(), DefinitionKind::ParamSpec(param_spec) => param_spec.node(module).range(), DefinitionKind::TypeVarTuple(type_var_tuple) => type_var_tuple.node(module).range(), } } pub(crate) fn category(&self, in_stub: bool, module: &ParsedModuleRef) -> DefinitionCategory { match self { // functions, classes, and imports always bind, and we consider them declarations DefinitionKind::Function(_) | DefinitionKind::Class(_) | DefinitionKind::TypeAlias(_) | DefinitionKind::Import(_) | DefinitionKind::ImportFrom(_) | DefinitionKind::StarImport(_) | DefinitionKind::TypeVar(_) | DefinitionKind::ParamSpec(_) | DefinitionKind::TypeVarTuple(_) => DefinitionCategory::DeclarationAndBinding, // a parameter always binds a value, but is only a declaration if annotated DefinitionKind::VariadicPositionalParameter(parameter) | DefinitionKind::VariadicKeywordParameter(parameter) => { if parameter.node(module).annotation.is_some() { DefinitionCategory::DeclarationAndBinding } else { DefinitionCategory::Binding } } // presence of a default is irrelevant, same logic as for a no-default parameter DefinitionKind::Parameter(parameter_with_default) => { if parameter_with_default .node(module) .parameter .annotation .is_some() { DefinitionCategory::DeclarationAndBinding } else { DefinitionCategory::Binding } } // Annotated assignment is always a declaration. It is also a binding if there is a RHS // or if we are in a stub file. Unfortunately, it is common for stubs to omit even an `...` value placeholder. DefinitionKind::AnnotatedAssignment(ann_assign) => { if in_stub || ann_assign.value.is_some() { DefinitionCategory::DeclarationAndBinding } else { DefinitionCategory::Declaration } } // all of these bind values without declaring a type DefinitionKind::NamedExpression(_) | DefinitionKind::Assignment(_) | DefinitionKind::AugmentedAssignment(_) | DefinitionKind::For(_) | DefinitionKind::Comprehension(_) | DefinitionKind::WithItem(_) | DefinitionKind::MatchPattern(_) | DefinitionKind::ImportFromSubmodule(_) | DefinitionKind::ExceptHandler(_) => DefinitionCategory::Binding, } } } #[derive(Copy, Clone, Debug, PartialEq, Hash, get_size2::GetSize)] pub(crate) enum TargetKind<'db> { Sequence(UnpackPosition, Unpack<'db>), /// Name, attribute, or subscript. Single, } impl<'db> From)>> for TargetKind<'db> { fn from(value: Option<(UnpackPosition, Unpack<'db>)>) -> Self { match value { Some((unpack_position, unpack)) => TargetKind::Sequence(unpack_position, unpack), None => TargetKind::Single, } } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct StarImportDefinitionKind { node: AstNodeRef, symbol_id: ScopedSymbolId, } impl StarImportDefinitionKind { pub fn import<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtImportFrom { self.node.node(module) } pub(crate) fn alias<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Alias { // INVARIANT: for an invalid-syntax statement such as `from foo import *, bar, *`, // we only create a `StarImportDefinitionKind` for the *first* `*` alias in the names list. self.node .node(module) .names .iter() .find(|alias| &alias.name == "*") .expect( "The `StmtImportFrom` node of a `StarImportDefinitionKind` instance \ should always have at least one `alias` with the name `*`.", ) } pub(crate) fn symbol_id(&self) -> ScopedSymbolId { self.symbol_id } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct MatchPatternDefinitionKind { pattern: AstNodeRef, identifier: AstNodeRef, index: u32, } impl MatchPatternDefinitionKind { pub(crate) fn pattern<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Pattern { self.pattern.node(module) } pub(crate) fn index(&self) -> u32 { self.index } } /// Note that the elements of a comprehension can be in different scopes. /// If the definition target of a comprehension is a name, it is in the comprehension's scope. /// But if the target is an attribute or subscript, its definition is not in the comprehension's scope; /// it is in the scope in which the root variable is bound. /// TODO: currently we don't model this correctly and simply assume that it is in a scope outside the comprehension. #[derive(Clone, Debug, get_size2::GetSize)] pub struct ComprehensionDefinitionKind<'db> { target_kind: TargetKind<'db>, iterable: AstNodeRef, target: AstNodeRef, first: bool, is_async: bool, } impl<'db> ComprehensionDefinitionKind<'db> { pub(crate) fn iterable<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.iterable.node(module) } pub(crate) fn target_kind(&self) -> TargetKind<'db> { self.target_kind } pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.target.node(module) } pub(crate) fn is_first(&self) -> bool { self.first } pub(crate) fn is_async(&self) -> bool { self.is_async } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct ImportDefinitionKind { node: AstNodeRef, alias_index: usize, is_reexported: bool, } impl ImportDefinitionKind { pub fn import<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtImport { self.node.node(module) } pub(crate) fn alias<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Alias { &self.node.node(module).names[self.alias_index] } pub(crate) fn is_reexported(&self) -> bool { self.is_reexported } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct ImportFromDefinitionKind { node: AstNodeRef, alias_index: usize, is_reexported: bool, } impl ImportFromDefinitionKind { pub fn import<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtImportFrom { self.node.node(module) } pub(crate) fn alias<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Alias { &self.node.node(module).names[self.alias_index] } pub(crate) fn is_reexported(&self) -> bool { self.is_reexported } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct ImportFromSubmoduleDefinitionKind { node: AstNodeRef, } impl ImportFromSubmoduleDefinitionKind { pub fn import<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtImportFrom { self.node.node(module) } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct AssignmentDefinitionKind<'db> { target_kind: TargetKind<'db>, value: AstNodeRef, target: AstNodeRef, } impl<'db> AssignmentDefinitionKind<'db> { pub(crate) fn target_kind(&self) -> TargetKind<'db> { self.target_kind } pub fn value<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.value.node(module) } pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.target.node(module) } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct AnnotatedAssignmentDefinitionKind { annotation: AstNodeRef, value: Option>, target: AstNodeRef, } impl AnnotatedAssignmentDefinitionKind { pub(crate) fn value<'ast>(&self, module: &'ast ParsedModuleRef) -> Option<&'ast ast::Expr> { self.value.as_ref().map(|value| value.node(module)) } pub(crate) fn annotation<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.annotation.node(module) } pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.target.node(module) } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct WithItemDefinitionKind<'db> { target_kind: TargetKind<'db>, context_expr: AstNodeRef, target: AstNodeRef, is_async: bool, } impl<'db> WithItemDefinitionKind<'db> { pub(crate) fn context_expr<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.context_expr.node(module) } pub(crate) fn target_kind(&self) -> TargetKind<'db> { self.target_kind } pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.target.node(module) } pub(crate) const fn is_async(&self) -> bool { self.is_async } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct ForStmtDefinitionKind<'db> { target_kind: TargetKind<'db>, iterable: AstNodeRef, target: AstNodeRef, is_async: bool, } impl<'db> ForStmtDefinitionKind<'db> { pub(crate) fn iterable<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.iterable.node(module) } pub(crate) fn target_kind(&self) -> TargetKind<'db> { self.target_kind } pub(crate) fn target<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::Expr { self.target.node(module) } pub(crate) const fn is_async(&self) -> bool { self.is_async } } #[derive(Clone, Debug, get_size2::GetSize)] pub struct ExceptHandlerDefinitionKind { handler: AstNodeRef, is_star: bool, } impl ExceptHandlerDefinitionKind { pub(crate) fn node<'ast>( &self, module: &'ast ParsedModuleRef, ) -> &'ast ast::ExceptHandlerExceptHandler { self.handler.node(module) } pub(crate) fn handled_exceptions<'ast>( &self, module: &'ast ParsedModuleRef, ) -> Option<&'ast ast::Expr> { self.node(module).type_.as_deref() } pub(crate) fn is_star(&self) -> bool { self.is_star } } #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, salsa::Update, get_size2::GetSize)] pub(crate) struct DefinitionNodeKey(NodeKey); impl From<&ast::Alias> for DefinitionNodeKey { fn from(node: &ast::Alias) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::StmtImportFrom> for DefinitionNodeKey { fn from(node: &ast::StmtImportFrom) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::StmtFunctionDef> for DefinitionNodeKey { fn from(node: &ast::StmtFunctionDef) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::StmtClassDef> for DefinitionNodeKey { fn from(node: &ast::StmtClassDef) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::StmtTypeAlias> for DefinitionNodeKey { fn from(node: &ast::StmtTypeAlias) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::ExprName> for DefinitionNodeKey { fn from(node: &ast::ExprName) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::ExprAttribute> for DefinitionNodeKey { fn from(node: &ast::ExprAttribute) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::ExprSubscript> for DefinitionNodeKey { fn from(node: &ast::ExprSubscript) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::ExprNamed> for DefinitionNodeKey { fn from(node: &ast::ExprNamed) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::StmtAnnAssign> for DefinitionNodeKey { fn from(node: &ast::StmtAnnAssign) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::StmtAugAssign> for DefinitionNodeKey { fn from(node: &ast::StmtAugAssign) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::Parameter> for DefinitionNodeKey { fn from(node: &ast::Parameter) -> Self { Self(NodeKey::from_node(node)) } } impl From<&ast::ParameterWithDefault> for DefinitionNodeKey { fn from(node: &ast::ParameterWithDefault) -> Self { Self(NodeKey::from_node(node)) } } impl From> for DefinitionNodeKey { fn from(value: ast::AnyParameterRef) -> Self { Self(match value { ast::AnyParameterRef::Variadic(node) => NodeKey::from_node(node), ast::AnyParameterRef::NonVariadic(node) => NodeKey::from_node(node), }) } } impl From<&ast::Identifier> for DefinitionNodeKey { fn from(identifier: &ast::Identifier) -> Self { Self(NodeKey::from_node(identifier)) } } impl From<&ast::ExceptHandlerExceptHandler> for DefinitionNodeKey { fn from(handler: &ast::ExceptHandlerExceptHandler) -> Self { Self(NodeKey::from_node(handler)) } } impl From<&ast::TypeParamTypeVar> for DefinitionNodeKey { fn from(value: &ast::TypeParamTypeVar) -> Self { Self(NodeKey::from_node(value)) } } impl From<&ast::TypeParamParamSpec> for DefinitionNodeKey { fn from(value: &ast::TypeParamParamSpec) -> Self { Self(NodeKey::from_node(value)) } } impl From<&ast::TypeParamTypeVarTuple> for DefinitionNodeKey { fn from(value: &ast::TypeParamTypeVarTuple) -> Self { Self(NodeKey::from_node(value)) } } impl From<&AstNodeRef> for DefinitionNodeKey where for<'a> &'a T: Into, { fn from(value: &AstNodeRef) -> Self { Self(NodeKey::from_node_ref(value)) } }