mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:43 +00:00
[red-knot] consider imports to be declarations (#13398)
I noticed that this pattern sometimes occurs in typeshed: ``` if ...: from foo import bar else: def bar(): ... ``` If we have the rule that symbols with declarations only use declarations for the public type, then this ends up resolving as `Unknown | Literal[bar]`, because we didn't consider the import to be a declaration. I think the most straightforward thing here is to also consider imports as declarations. The same rationale applies as for function and class definitions: if you shadow an import, you should have to explicitly shadow with an annotation, rather than just doing it implicitly/accidentally. We may also ultimately need to re-evaluate the rule that public type considers only declarations, if there are declarations.
This commit is contained in:
parent
8b3da1867e
commit
4aca9b91ba
2 changed files with 55 additions and 14 deletions
|
@ -330,7 +330,7 @@ impl DefinitionCategory {
|
||||||
/// If so, any assignments reached by this definition are in error if they assign a value of a
|
/// 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.
|
/// type not assignable to the declared type.
|
||||||
///
|
///
|
||||||
/// Annotations establish a declared type. So do function and class definition.
|
/// Annotations establish a declared type. So do function and class definitions, and imports.
|
||||||
pub(crate) fn is_declaration(self) -> bool {
|
pub(crate) fn is_declaration(self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
|
@ -371,10 +371,11 @@ pub enum DefinitionKind {
|
||||||
impl DefinitionKind {
|
impl DefinitionKind {
|
||||||
pub(crate) fn category(&self) -> DefinitionCategory {
|
pub(crate) fn category(&self) -> DefinitionCategory {
|
||||||
match self {
|
match self {
|
||||||
// functions and classes always bind a value, and we always consider them declarations
|
// functions, classes, and imports always bind, and we consider them declarations
|
||||||
DefinitionKind::Function(_) | DefinitionKind::Class(_) => {
|
DefinitionKind::Function(_)
|
||||||
DefinitionCategory::DeclarationAndBinding
|
| DefinitionKind::Class(_)
|
||||||
}
|
| DefinitionKind::Import(_)
|
||||||
|
| DefinitionKind::ImportFrom(_) => DefinitionCategory::DeclarationAndBinding,
|
||||||
// a parameter always binds a value, but is only a declaration if annotated
|
// a parameter always binds a value, but is only a declaration if annotated
|
||||||
DefinitionKind::Parameter(parameter) => {
|
DefinitionKind::Parameter(parameter) => {
|
||||||
if parameter.annotation.is_some() {
|
if parameter.annotation.is_some() {
|
||||||
|
@ -400,9 +401,7 @@ impl DefinitionKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// all of these bind values without declaring a type
|
// all of these bind values without declaring a type
|
||||||
DefinitionKind::Import(_)
|
DefinitionKind::NamedExpression(_)
|
||||||
| DefinitionKind::ImportFrom(_)
|
|
||||||
| DefinitionKind::NamedExpression(_)
|
|
||||||
| DefinitionKind::Assignment(_)
|
| DefinitionKind::Assignment(_)
|
||||||
| DefinitionKind::AugmentedAssignment(_)
|
| DefinitionKind::AugmentedAssignment(_)
|
||||||
| DefinitionKind::For(_)
|
| DefinitionKind::For(_)
|
||||||
|
|
|
@ -1316,7 +1316,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add_binding(alias.into(), definition, module_ty);
|
self.add_declaration_with_binding(alias.into(), definition, module_ty, module_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_import_from_statement(&mut self, import: &ast::StmtImportFrom) {
|
fn infer_import_from_statement(&mut self, import: &ast::StmtImportFrom) {
|
||||||
|
@ -1500,11 +1500,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// the runtime error will occur immediately (rather than when the symbol is *used*,
|
// the runtime error will occur immediately (rather than when the symbol is *used*,
|
||||||
// as would be the case for a symbol with type `Unbound`), so it's appropriate to
|
// as would be the case for a symbol with type `Unbound`), so it's appropriate to
|
||||||
// think of the type of the imported symbol as `Unknown` rather than `Unbound`
|
// think of the type of the imported symbol as `Unknown` rather than `Unbound`
|
||||||
self.add_binding(
|
let ty = member_ty.replace_unbound_with(self.db, Type::Unknown);
|
||||||
alias.into(),
|
|
||||||
definition,
|
self.add_declaration_with_binding(alias.into(), definition, ty, ty);
|
||||||
member_ty.replace_unbound_with(self.db, Type::Unknown),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_return_statement(&mut self, ret: &ast::StmtReturn) {
|
fn infer_return_statement(&mut self, ret: &ast::StmtReturn) {
|
||||||
|
@ -5697,6 +5695,50 @@ mod tests {
|
||||||
assert_file_diagnostics(&db, "/src/a.py", &[]);
|
assert_file_diagnostics(&db, "/src/a.py", &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_implicit_shadow_import() {
|
||||||
|
let mut db = setup_db();
|
||||||
|
|
||||||
|
db.write_dedented(
|
||||||
|
"/src/a.py",
|
||||||
|
"
|
||||||
|
from b import x
|
||||||
|
|
||||||
|
x = 'foo'
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.write_file("/src/b.py", "x: int").unwrap();
|
||||||
|
|
||||||
|
assert_file_diagnostics(
|
||||||
|
&db,
|
||||||
|
"/src/a.py",
|
||||||
|
&[r#"Object of type 'Literal["foo"]' is not assignable to 'int'."#],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn import_from_conditional_reimport() {
|
||||||
|
let mut db = setup_db();
|
||||||
|
|
||||||
|
db.write_file("/src/a.py", "from b import f").unwrap();
|
||||||
|
db.write_dedented(
|
||||||
|
"/src/b.py",
|
||||||
|
"
|
||||||
|
if flag:
|
||||||
|
from c import f
|
||||||
|
else:
|
||||||
|
def f(): ...
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
db.write_file("/src/c.py", "def f(): ...").unwrap();
|
||||||
|
|
||||||
|
// TODO we should really disambiguate in such cases: Literal[b.f, c.f]
|
||||||
|
assert_public_ty(&db, "/src/a.py", "f", "Literal[f, f]");
|
||||||
|
}
|
||||||
|
|
||||||
// Incremental inference tests
|
// Incremental inference tests
|
||||||
|
|
||||||
fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> {
|
fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue