mirror of https://github.com/astral-sh/ruff
[red-knot] class bases are not affected by __future__.annotations (#17456)
## Summary We were over-conflating the conditions for deferred name resolution. `from __future__ import annotations` defers annotations, but not class bases. In stub files, class bases are also deferred. Modeling this correctly also reduces likelihood of cycles in Python files using `from __future__ import annotations` (since deferred resolution is inherently cycle-prone). The same cycles are still possible in `.pyi` files, but much less likely, since typically there isn't anything in a `pyi` file that would cause an early return from a scope, or otherwise cause visibility constraints to persist to end of scope. Usually there is only code at module global scope and class scope, which can't have `return` statements, and `raise` or `assert` statements in a stub file would be very strange. (Technically according to the spec we'd be within our rights to just forbid a whole bunch of syntax outright in a stub file, but I kinda like minimizing unnecessary differences between the handling of Python files and stub files.) ## Test Plan Added mdtests.
This commit is contained in:
parent
44ad201262
commit
1918c61623
|
|
@ -156,3 +156,24 @@ def _():
|
||||||
def f(self) -> C:
|
def f(self) -> C:
|
||||||
return self
|
return self
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Base class references
|
||||||
|
|
||||||
|
### Not deferred by __future__.annotations
|
||||||
|
|
||||||
|
```py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
class A(B): # error: [unresolved-reference]
|
||||||
|
pass
|
||||||
|
|
||||||
|
class B:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deferred in stub files
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
class A(B): ...
|
||||||
|
class B: ...
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -585,8 +585,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
/// Are we currently inferring types in file with deferred types?
|
/// Are we currently inferring types in file with deferred types?
|
||||||
/// This is true for stub files and files with `__future__.annotations`
|
/// This is true for stub files and files with `__future__.annotations`
|
||||||
fn are_all_types_deferred(&self) -> bool {
|
fn defer_annotations(&self) -> bool {
|
||||||
self.index.has_future_annotations() || self.file().is_stub(self.db().upcast())
|
self.index.has_future_annotations() || self.in_stub()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Are we currently inferring deferred types?
|
/// Are we currently inferring deferred types?
|
||||||
|
|
@ -1467,7 +1467,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// If there are type params, parameters and returns are evaluated in that scope, that is, in
|
// If there are type params, parameters and returns are evaluated in that scope, that is, in
|
||||||
// `infer_function_type_params`, rather than here.
|
// `infer_function_type_params`, rather than here.
|
||||||
if type_params.is_none() {
|
if type_params.is_none() {
|
||||||
if self.are_all_types_deferred() {
|
if self.defer_annotations() {
|
||||||
self.types.deferred.insert(definition);
|
self.types.deferred.insert(definition);
|
||||||
} else {
|
} else {
|
||||||
self.infer_optional_annotation_expression(
|
self.infer_optional_annotation_expression(
|
||||||
|
|
@ -1791,9 +1791,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// TODO: Only defer the references that are actually string literals, instead of
|
// TODO: Only defer the references that are actually string literals, instead of
|
||||||
// deferring the entire class definition if a string literal occurs anywhere in the
|
// deferring the entire class definition if a string literal occurs anywhere in the
|
||||||
// base class list.
|
// base class list.
|
||||||
if self.are_all_types_deferred()
|
if self.in_stub() || class_node.bases().iter().any(contains_string_literal) {
|
||||||
|| class_node.bases().iter().any(contains_string_literal)
|
|
||||||
{
|
|
||||||
self.types.deferred.insert(definition);
|
self.types.deferred.insert(definition);
|
||||||
} else {
|
} else {
|
||||||
for base in class_node.bases() {
|
for base in class_node.bases() {
|
||||||
|
|
@ -2919,7 +2917,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
let mut declared_ty = self.infer_annotation_expression(
|
let mut declared_ty = self.infer_annotation_expression(
|
||||||
annotation,
|
annotation,
|
||||||
DeferredExpressionState::from(self.are_all_types_deferred()),
|
DeferredExpressionState::from(self.defer_annotations()),
|
||||||
);
|
);
|
||||||
|
|
||||||
if target
|
if target
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue