Skip to content

Commit a8d9104

Browse files
authored
Fix/#13070 defer annotations when future is active (#13395)
1 parent d3530ab commit a8d9104

File tree

3 files changed

+90
-10
lines changed

3 files changed

+90
-10
lines changed

crates/red_knot_python_semantic/src/semantic_index.rs

+9
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ pub(crate) struct SemanticIndex<'db> {
115115
/// Note: We should not depend on this map when analysing other files or
116116
/// changing a file invalidates all dependents.
117117
ast_ids: IndexVec<FileScopeId, AstIds>,
118+
119+
/// Flags about the global scope (code usage impacting inference)
120+
has_future_annotations: bool,
118121
}
119122

120123
impl<'db> SemanticIndex<'db> {
@@ -215,6 +218,12 @@ impl<'db> SemanticIndex<'db> {
215218
pub(crate) fn node_scope(&self, node: NodeWithScopeRef) -> FileScopeId {
216219
self.scopes_by_node[&node.node_key()]
217220
}
221+
222+
/// Checks if there is an import of `__future__.annotations` in the global scope, which affects
223+
/// the logic for type inference.
224+
pub(super) fn has_future_annotations(&self) -> bool {
225+
self.has_future_annotations
226+
}
218227
}
219228

220229
pub struct AncestorsIter<'a> {

crates/red_knot_python_semantic/src/semantic_index/builder.rs

+15
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ pub(super) struct SemanticIndexBuilder<'db> {
4545
/// Flow states at each `break` in the current loop.
4646
loop_break_states: Vec<FlowSnapshot>,
4747

48+
/// Flags about the file's global scope
49+
has_future_annotations: bool,
50+
4851
// Semantic Index fields
4952
scopes: IndexVec<FileScopeId, Scope>,
5053
scope_ids_by_scope: IndexVec<FileScopeId, ScopeId<'db>>,
@@ -68,6 +71,8 @@ impl<'db> SemanticIndexBuilder<'db> {
6871
current_match_case: None,
6972
loop_break_states: vec![],
7073

74+
has_future_annotations: false,
75+
7176
scopes: IndexVec::new(),
7277
symbol_tables: IndexVec::new(),
7378
ast_ids: IndexVec::new(),
@@ -450,6 +455,7 @@ impl<'db> SemanticIndexBuilder<'db> {
450455
scopes_by_expression: self.scopes_by_expression,
451456
scopes_by_node: self.scopes_by_node,
452457
use_def_maps,
458+
has_future_annotations: self.has_future_annotations,
453459
}
454460
}
455461
}
@@ -543,7 +549,16 @@ where
543549
&alias.name.id
544550
};
545551

552+
// Look for imports `from __future__ import annotations`, ignore `as ...`
553+
// We intentionally don't enforce the rules about location of `__future__`
554+
// imports here, we assume the user's intent was to apply the `__future__`
555+
// import, so we still check using it (and will also emit a diagnostic about a
556+
// miss-placed `__future__` import.)
557+
self.has_future_annotations |= alias.name.id == "annotations"
558+
&& node.module.as_deref() == Some("__future__");
559+
546560
let symbol = self.add_symbol(symbol_name.clone());
561+
547562
self.add_definition(symbol, ImportFromDefinitionNodeRef { node, alias_index });
548563
}
549564
}

crates/red_knot_python_semantic/src/types/infer.rs

+66-10
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,10 @@ impl<'db> TypeInferenceBuilder<'db> {
318318
self.types.has_deferred |= inference.has_deferred;
319319
}
320320

321-
/// Are we currently inferring types in a stub file?
322-
fn is_stub(&self) -> bool {
323-
self.file.is_stub(self.db.upcast())
321+
/// Are we currently inferring types in file with deferred types?
322+
/// This is true for stub files and files with `__future__.annotations`
323+
fn are_all_types_deferred(&self) -> bool {
324+
self.index.has_future_annotations() || self.file.is_stub(self.db.upcast())
324325
}
325326

326327
/// Are we currently inferring deferred types?
@@ -703,7 +704,7 @@ impl<'db> TypeInferenceBuilder<'db> {
703704
self.infer_parameters(parameters);
704705

705706
// TODO: this should also be applied to parameter annotations.
706-
if self.is_stub() {
707+
if self.are_all_types_deferred() {
707708
self.types.has_deferred = true;
708709
} else {
709710
self.infer_optional_annotation_expression(returns.as_deref());
@@ -831,9 +832,9 @@ impl<'db> TypeInferenceBuilder<'db> {
831832
self.infer_expression(&keyword.value);
832833
}
833834

834-
// inference of bases deferred in stubs
835+
// Inference of bases deferred in stubs
835836
// TODO also defer stringified generic type parameters
836-
if self.is_stub() {
837+
if self.are_all_types_deferred() {
837838
self.types.has_deferred = true;
838839
} else {
839840
for base in class.bases() {
@@ -843,13 +844,11 @@ impl<'db> TypeInferenceBuilder<'db> {
843844
}
844845

845846
fn infer_function_deferred(&mut self, function: &ast::StmtFunctionDef) {
846-
if self.is_stub() {
847-
self.infer_optional_annotation_expression(function.returns.as_deref());
848-
}
847+
self.infer_optional_annotation_expression(function.returns.as_deref());
849848
}
850849

851850
fn infer_class_deferred(&mut self, class: &ast::StmtClassDef) {
852-
if self.is_stub() {
851+
if self.are_all_types_deferred() {
853852
for base in class.bases() {
854853
self.infer_expression(base);
855854
}
@@ -4166,6 +4165,63 @@ mod tests {
41664165
Ok(())
41674166
}
41684167

4168+
#[test]
4169+
fn deferred_annotation_in_stubs_always_resolve() -> anyhow::Result<()> {
4170+
let mut db = setup_db();
4171+
4172+
// Stub files should always resolve deferred annotations
4173+
db.write_dedented(
4174+
"/src/stub.pyi",
4175+
"
4176+
def get_foo() -> Foo: ...
4177+
class Foo: ...
4178+
foo = get_foo()
4179+
",
4180+
)?;
4181+
assert_public_ty(&db, "/src/stub.pyi", "foo", "Foo");
4182+
4183+
Ok(())
4184+
}
4185+
4186+
#[test]
4187+
fn deferred_annotations_regular_source_fails() -> anyhow::Result<()> {
4188+
let mut db = setup_db();
4189+
4190+
// In (regular) source files, deferred annotations are *not* resolved
4191+
// Also tests imports from `__future__` that are not annotations
4192+
db.write_dedented(
4193+
"/src/source.py",
4194+
"
4195+
from __future__ import with_statement as annotations
4196+
def get_foo() -> Foo: ...
4197+
class Foo: ...
4198+
foo = get_foo()
4199+
",
4200+
)?;
4201+
assert_public_ty(&db, "/src/source.py", "foo", "Unknown");
4202+
4203+
Ok(())
4204+
}
4205+
4206+
#[test]
4207+
fn deferred_annotation_in_sources_with_future_resolves() -> anyhow::Result<()> {
4208+
let mut db = setup_db();
4209+
4210+
// In source files with `__future__.annotations`, deferred annotations are resolved
4211+
db.write_dedented(
4212+
"/src/source_with_future.py",
4213+
"
4214+
from __future__ import annotations
4215+
def get_foo() -> Foo: ...
4216+
class Foo: ...
4217+
foo = get_foo()
4218+
",
4219+
)?;
4220+
assert_public_ty(&db, "/src/source_with_future.py", "foo", "Foo");
4221+
4222+
Ok(())
4223+
}
4224+
41694225
#[test]
41704226
fn narrow_not_none() -> anyhow::Result<()> {
41714227
let mut db = setup_db();

0 commit comments

Comments
 (0)