mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-07 13:15:19 +00:00
[ty] Bare ClassVar
annotations (#15768)
Some checks are pending
CI / cargo clippy (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / cargo clippy (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
## Summary It was recently clarified in the [typing spec](https://typing.python.org/en/latest/spec/class-compat.html#classvar) that bare `ClassVar` annotations are allowed. For annotated assignments with a right hand side value, the spec requires type checkers to infer the type as something "to which [the] value is assignable". For a value of `2`, the spec suggests `int`, `Literal[2]`, or `Any` as examples. Here, we choose `Unknown | Literal[2]` instead, conforming with out usual treatment of attribute types. closes https://github.com/astral-sh/ty/issues/211
This commit is contained in:
parent
4aaf32476a
commit
e7fb3684e8
3 changed files with 47 additions and 8 deletions
|
@ -689,16 +689,14 @@ class C:
|
|||
|
||||
reveal_type(C.pure_class_variable1) # revealed: str
|
||||
|
||||
# TODO: Should be `Unknown | Literal[1]`.
|
||||
reveal_type(C.pure_class_variable2) # revealed: Unknown
|
||||
reveal_type(C.pure_class_variable2) # revealed: Unknown | Literal[1]
|
||||
|
||||
c_instance = C()
|
||||
|
||||
# It is okay to access a pure class variable on an instance.
|
||||
reveal_type(c_instance.pure_class_variable1) # revealed: str
|
||||
|
||||
# TODO: Should be `Unknown | Literal[1]`.
|
||||
reveal_type(c_instance.pure_class_variable2) # revealed: Unknown
|
||||
reveal_type(c_instance.pure_class_variable2) # revealed: Unknown | Literal[1]
|
||||
|
||||
# error: [invalid-attribute-access] "Cannot assign to ClassVar `pure_class_variable1` from an instance of type `C`"
|
||||
c_instance.pure_class_variable1 = "value set on instance"
|
||||
|
@ -714,6 +712,24 @@ class Subclass(C):
|
|||
reveal_type(Subclass.pure_class_variable1) # revealed: str
|
||||
```
|
||||
|
||||
If a class variable is additionally qualified as `Final`, we do not union with `Unknown` for bare
|
||||
`ClassVar`s:
|
||||
|
||||
```py
|
||||
from typing import Final
|
||||
|
||||
class D:
|
||||
final1: Final[ClassVar] = 1
|
||||
final2: ClassVar[Final] = 1
|
||||
final3: ClassVar[Final[int]] = 1
|
||||
final4: Final[ClassVar[int]] = 1
|
||||
|
||||
reveal_type(D.final1) # revealed: Literal[1]
|
||||
reveal_type(D.final2) # revealed: Literal[1]
|
||||
reveal_type(D.final3) # revealed: int
|
||||
reveal_type(D.final4) # revealed: int
|
||||
```
|
||||
|
||||
#### Variable only mentioned in a class method
|
||||
|
||||
We also consider a class variable to be a pure class variable if it is only mentioned in a class
|
||||
|
|
|
@ -21,8 +21,7 @@ class C:
|
|||
reveal_type(C.a) # revealed: int
|
||||
reveal_type(C.b) # revealed: int
|
||||
reveal_type(C.c) # revealed: int
|
||||
# TODO: should be Unknown | Literal[1]
|
||||
reveal_type(C.d) # revealed: Unknown
|
||||
reveal_type(C.d) # revealed: Unknown | Literal[1]
|
||||
reveal_type(C.e) # revealed: int
|
||||
|
||||
c = C()
|
||||
|
|
|
@ -9,8 +9,8 @@ use crate::semantic_index::{
|
|||
};
|
||||
use crate::semantic_index::{DeclarationWithConstraint, global_scope, use_def_map};
|
||||
use crate::types::{
|
||||
KnownClass, Truthiness, Type, TypeAndQualifiers, TypeQualifiers, UnionBuilder, UnionType,
|
||||
binding_type, declaration_type, todo_type,
|
||||
DynamicType, KnownClass, Truthiness, Type, TypeAndQualifiers, TypeQualifiers, UnionBuilder,
|
||||
UnionType, binding_type, declaration_type, todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet, KnownModule, Program, resolve_module};
|
||||
|
||||
|
@ -672,6 +672,30 @@ fn place_by_id<'db>(
|
|||
.with_qualifiers(qualifiers);
|
||||
}
|
||||
|
||||
// Handle bare `ClassVar` annotations by falling back to the union of `Unknown` and the
|
||||
// inferred type.
|
||||
match declared {
|
||||
Ok(PlaceAndQualifiers {
|
||||
place: Place::Type(Type::Dynamic(DynamicType::Unknown), declaredness),
|
||||
qualifiers,
|
||||
}) if qualifiers.contains(TypeQualifiers::CLASS_VAR) => {
|
||||
let bindings = all_considered_bindings();
|
||||
match place_from_bindings_impl(db, bindings, requires_explicit_reexport) {
|
||||
Place::Type(inferred, boundness) => {
|
||||
return Place::Type(
|
||||
UnionType::from_elements(db, [Type::unknown(), inferred]),
|
||||
boundness,
|
||||
)
|
||||
.with_qualifiers(qualifiers);
|
||||
}
|
||||
Place::Unbound => {
|
||||
return Place::Type(Type::unknown(), declaredness).with_qualifiers(qualifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match declared {
|
||||
// Place is declared, trust the declared type
|
||||
Ok(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue