Skip to content

Commit 4aca9b9

Browse files
authored
[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.
1 parent 8b3da18 commit 4aca9b9

File tree

2 files changed

+55
-14
lines changed

2 files changed

+55
-14
lines changed

crates/red_knot_python_semantic/src/semantic_index/definition.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ impl DefinitionCategory {
330330
/// If so, any assignments reached by this definition are in error if they assign a value of a
331331
/// type not assignable to the declared type.
332332
///
333-
/// Annotations establish a declared type. So do function and class definition.
333+
/// Annotations establish a declared type. So do function and class definitions, and imports.
334334
pub(crate) fn is_declaration(self) -> bool {
335335
matches!(
336336
self,
@@ -371,10 +371,11 @@ pub enum DefinitionKind {
371371
impl DefinitionKind {
372372
pub(crate) fn category(&self) -> DefinitionCategory {
373373
match self {
374-
// functions and classes always bind a value, and we always consider them declarations
375-
DefinitionKind::Function(_) | DefinitionKind::Class(_) => {
376-
DefinitionCategory::DeclarationAndBinding
377-
}
374+
// functions, classes, and imports always bind, and we consider them declarations
375+
DefinitionKind::Function(_)
376+
| DefinitionKind::Class(_)
377+
| DefinitionKind::Import(_)
378+
| DefinitionKind::ImportFrom(_) => DefinitionCategory::DeclarationAndBinding,
378379
// a parameter always binds a value, but is only a declaration if annotated
379380
DefinitionKind::Parameter(parameter) => {
380381
if parameter.annotation.is_some() {
@@ -400,9 +401,7 @@ impl DefinitionKind {
400401
}
401402
}
402403
// all of these bind values without declaring a type
403-
DefinitionKind::Import(_)
404-
| DefinitionKind::ImportFrom(_)
405-
| DefinitionKind::NamedExpression(_)
404+
DefinitionKind::NamedExpression(_)
406405
| DefinitionKind::Assignment(_)
407406
| DefinitionKind::AugmentedAssignment(_)
408407
| DefinitionKind::For(_)

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,7 +1316,7 @@ impl<'db> TypeInferenceBuilder<'db> {
13161316
Type::Unknown
13171317
};
13181318

1319-
self.add_binding(alias.into(), definition, module_ty);
1319+
self.add_declaration_with_binding(alias.into(), definition, module_ty, module_ty);
13201320
}
13211321

13221322
fn infer_import_from_statement(&mut self, import: &ast::StmtImportFrom) {
@@ -1500,11 +1500,9 @@ impl<'db> TypeInferenceBuilder<'db> {
15001500
// the runtime error will occur immediately (rather than when the symbol is *used*,
15011501
// as would be the case for a symbol with type `Unbound`), so it's appropriate to
15021502
// think of the type of the imported symbol as `Unknown` rather than `Unbound`
1503-
self.add_binding(
1504-
alias.into(),
1505-
definition,
1506-
member_ty.replace_unbound_with(self.db, Type::Unknown),
1507-
);
1503+
let ty = member_ty.replace_unbound_with(self.db, Type::Unknown);
1504+
1505+
self.add_declaration_with_binding(alias.into(), definition, ty, ty);
15081506
}
15091507

15101508
fn infer_return_statement(&mut self, ret: &ast::StmtReturn) {
@@ -5697,6 +5695,50 @@ mod tests {
56975695
assert_file_diagnostics(&db, "/src/a.py", &[]);
56985696
}
56995697

5698+
#[test]
5699+
fn no_implicit_shadow_import() {
5700+
let mut db = setup_db();
5701+
5702+
db.write_dedented(
5703+
"/src/a.py",
5704+
"
5705+
from b import x
5706+
5707+
x = 'foo'
5708+
",
5709+
)
5710+
.unwrap();
5711+
5712+
db.write_file("/src/b.py", "x: int").unwrap();
5713+
5714+
assert_file_diagnostics(
5715+
&db,
5716+
"/src/a.py",
5717+
&[r#"Object of type 'Literal["foo"]' is not assignable to 'int'."#],
5718+
);
5719+
}
5720+
5721+
#[test]
5722+
fn import_from_conditional_reimport() {
5723+
let mut db = setup_db();
5724+
5725+
db.write_file("/src/a.py", "from b import f").unwrap();
5726+
db.write_dedented(
5727+
"/src/b.py",
5728+
"
5729+
if flag:
5730+
from c import f
5731+
else:
5732+
def f(): ...
5733+
",
5734+
)
5735+
.unwrap();
5736+
db.write_file("/src/c.py", "def f(): ...").unwrap();
5737+
5738+
// TODO we should really disambiguate in such cases: Literal[b.f, c.f]
5739+
assert_public_ty(&db, "/src/a.py", "f", "Literal[f, f]");
5740+
}
5741+
57005742
// Incremental inference tests
57015743

57025744
fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> {

0 commit comments

Comments
 (0)