mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00
[red-knot] Add initial support for *
imports (#16923)
## Summary This PR adds initial support for `*` imports to red-knot. The approach is to implement a standalone query, called from semantic indexing, that visits the module referenced by the `*` import and collects all global-scope public names that will be imported by the `*` import. The `SemanticIndexBuilder` then adds separate definitions for each of these names, all keyed to the same `ast::Alias` node that represents the `*` import. There are many pieces of `*`-import semantics that are still yet to be done, even with this PR: - This PR does not attempt to implement any of the semantics to do with `__all__`. (If a module defines `__all__`, then only the symbols included in `__all__` are imported, _not_ all public global-scope symbols. - With the logic implemented in this PR as it currently stands, we sometimes incorrectly consider a symbol bound even though it is defined in a branch that is statically known to be dead code, e.g. (assuming the target Python version is set to 3.11): ```py # a.py import sys if sys.version_info < (3, 10): class Foo: ... ``` ```py # b.py from a import * print(Foo) # this is unbound at runtime on 3.11, # but we currently consider it bound with the logic in this PR ``` Implementing these features is important, but is for now deferred to followup PRs. Many thanks to @ntBre, who contributed to this PR in a pairing session on Friday! ## Test Plan Assertions in existing mdtests are adjusted, and several new ones are added.
This commit is contained in:
parent
cba197e3c5
commit
e87fee4b3b
17 changed files with 927 additions and 357 deletions
|
@ -14,7 +14,7 @@ use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
|||
use crate::semantic_index::ast_ids::AstIds;
|
||||
use crate::semantic_index::attribute_assignment::AttributeAssignments;
|
||||
use crate::semantic_index::builder::SemanticIndexBuilder;
|
||||
use crate::semantic_index::definition::{Definition, DefinitionNodeKey};
|
||||
use crate::semantic_index::definition::{Definition, DefinitionNodeKey, Definitions};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId, SymbolTable,
|
||||
|
@ -29,6 +29,7 @@ pub mod definition;
|
|||
pub mod expression;
|
||||
mod narrowing_constraints;
|
||||
pub(crate) mod predicate;
|
||||
mod re_exports;
|
||||
pub mod symbol;
|
||||
mod use_def;
|
||||
mod visibility_constraints;
|
||||
|
@ -136,7 +137,7 @@ pub(crate) struct SemanticIndex<'db> {
|
|||
scopes_by_expression: FxHashMap<ExpressionNodeKey, FileScopeId>,
|
||||
|
||||
/// Map from a node creating a definition to its definition.
|
||||
definitions_by_node: FxHashMap<DefinitionNodeKey, Definition<'db>>,
|
||||
definitions_by_node: FxHashMap<DefinitionNodeKey, Definitions<'db>>,
|
||||
|
||||
/// Map from a standalone expression to its [`Expression`] ingredient.
|
||||
expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
|
||||
|
@ -250,13 +251,37 @@ impl<'db> SemanticIndex<'db> {
|
|||
AncestorsIter::new(self, scope)
|
||||
}
|
||||
|
||||
/// Returns the [`Definition`] salsa ingredient for `definition_key`.
|
||||
/// Returns the [`definition::Definition`] salsa ingredient(s) for `definition_key`.
|
||||
///
|
||||
/// There will only ever be >1 `Definition` associated with a `definition_key`
|
||||
/// if the definition is created by a wildcard (`*`) import.
|
||||
#[track_caller]
|
||||
pub(crate) fn definition(
|
||||
pub(crate) fn definitions(
|
||||
&self,
|
||||
definition_key: impl Into<DefinitionNodeKey>,
|
||||
) -> &Definitions<'db> {
|
||||
&self.definitions_by_node[&definition_key.into()]
|
||||
}
|
||||
|
||||
/// Returns the [`definition::Definition`] salsa ingredient for `definition_key`.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// If the number of definitions associated with the key is not exactly 1 and
|
||||
/// the `debug_assertions` feature is enabled, this method will panic.
|
||||
#[track_caller]
|
||||
pub(crate) fn expect_single_definition(
|
||||
&self,
|
||||
definition_key: impl Into<DefinitionNodeKey> + std::fmt::Debug + Copy,
|
||||
) -> Definition<'db> {
|
||||
self.definitions_by_node[&definition_key.into()]
|
||||
let definitions = self.definitions(definition_key);
|
||||
debug_assert_eq!(
|
||||
definitions.len(),
|
||||
1,
|
||||
"Expected exactly one definition to be associated with AST node {definition_key:?} but found {}",
|
||||
definitions.len()
|
||||
);
|
||||
definitions[0]
|
||||
}
|
||||
|
||||
/// Returns the [`Expression`] ingredient for an expression node.
|
||||
|
@ -280,7 +305,8 @@ impl<'db> SemanticIndex<'db> {
|
|||
.copied()
|
||||
}
|
||||
|
||||
/// Returns the id of the scope that `node` creates. This is different from [`Definition::scope`] which
|
||||
/// Returns the id of the scope that `node` creates.
|
||||
/// This is different from [`definition::Definition::scope`] which
|
||||
/// returns the scope in which that definition is defined in.
|
||||
#[track_caller]
|
||||
pub(crate) fn node_scope(&self, node: NodeWithScopeRef) -> FileScopeId {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue