mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-20 12:35:40 +00:00
[ty] Fixup a few details around version-specific dataclass features (#21453)
Some checks are pending
CI / mkdocs (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}) (push) Blocked by required conditions
CI / cargo test (macos-latest) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
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 / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / ty completion evaluation (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (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 (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks walltime (medium|multithreaded) (push) Blocked by required conditions
CI / benchmarks walltime (small|large) (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / mkdocs (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}) (push) Blocked by required conditions
CI / cargo test (macos-latest) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
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 / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / ty completion evaluation (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (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 (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks walltime (medium|multithreaded) (push) Blocked by required conditions
CI / benchmarks walltime (small|large) (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
This commit is contained in:
parent
5f501374c4
commit
8599c7e5b3
5 changed files with 179 additions and 8 deletions
|
|
@ -2411,6 +2411,73 @@ Answer.<CURSOR>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn namedtuple_methods() {
|
||||||
|
let builder = completion_test_builder(
|
||||||
|
"\
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
class Quux(NamedTuple):
|
||||||
|
x: int
|
||||||
|
y: str
|
||||||
|
|
||||||
|
quux = Quux()
|
||||||
|
quux.<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(
|
||||||
|
builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @r"
|
||||||
|
count :: bound method Quux.count(value: Any, /) -> int
|
||||||
|
index :: bound method Quux.index(value: Any, start: SupportsIndex = Literal[0], stop: SupportsIndex = int, /) -> int
|
||||||
|
x :: int
|
||||||
|
y :: str
|
||||||
|
__add__ :: Overload[(value: tuple[int | str, ...], /) -> tuple[int | str, ...], (value: tuple[_T@__add__, ...], /) -> tuple[int | str | _T@__add__, ...]]
|
||||||
|
__annotations__ :: dict[str, Any]
|
||||||
|
__class__ :: type[Quux]
|
||||||
|
__class_getitem__ :: bound method type[Quux].__class_getitem__(item: Any, /) -> GenericAlias
|
||||||
|
__contains__ :: bound method Quux.__contains__(key: object, /) -> bool
|
||||||
|
__delattr__ :: bound method Quux.__delattr__(name: str, /) -> None
|
||||||
|
__dict__ :: dict[str, Any]
|
||||||
|
__dir__ :: bound method Quux.__dir__() -> Iterable[str]
|
||||||
|
__doc__ :: str | None
|
||||||
|
__eq__ :: bound method Quux.__eq__(value: object, /) -> bool
|
||||||
|
__format__ :: bound method Quux.__format__(format_spec: str, /) -> str
|
||||||
|
__ge__ :: bound method Quux.__ge__(value: tuple[int | str, ...], /) -> bool
|
||||||
|
__getattribute__ :: bound method Quux.__getattribute__(name: str, /) -> Any
|
||||||
|
__getitem__ :: Overload[(index: Literal[-2, 0], /) -> int, (index: Literal[-1, 1], /) -> str, (index: SupportsIndex, /) -> int | str, (index: slice[Any, Any, Any], /) -> tuple[int | str, ...]]
|
||||||
|
__getstate__ :: bound method Quux.__getstate__() -> object
|
||||||
|
__gt__ :: bound method Quux.__gt__(value: tuple[int | str, ...], /) -> bool
|
||||||
|
__hash__ :: bound method Quux.__hash__() -> int
|
||||||
|
__init__ :: bound method Quux.__init__() -> None
|
||||||
|
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
|
||||||
|
__iter__ :: bound method Quux.__iter__() -> Iterator[int | str]
|
||||||
|
__le__ :: bound method Quux.__le__(value: tuple[int | str, ...], /) -> bool
|
||||||
|
__len__ :: () -> Literal[2]
|
||||||
|
__lt__ :: bound method Quux.__lt__(value: tuple[int | str, ...], /) -> bool
|
||||||
|
__module__ :: str
|
||||||
|
__mul__ :: bound method Quux.__mul__(value: SupportsIndex, /) -> tuple[int | str, ...]
|
||||||
|
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
||||||
|
__new__ :: (x: int, y: str) -> None
|
||||||
|
__orig_bases__ :: tuple[Any, ...]
|
||||||
|
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
|
||||||
|
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
||||||
|
__replace__ :: bound method NamedTupleFallback.__replace__(**kwargs: Any) -> NamedTupleFallback
|
||||||
|
__repr__ :: bound method Quux.__repr__() -> str
|
||||||
|
__reversed__ :: bound method Quux.__reversed__() -> Iterator[int | str]
|
||||||
|
__rmul__ :: bound method Quux.__rmul__(value: SupportsIndex, /) -> tuple[int | str, ...]
|
||||||
|
__setattr__ :: bound method Quux.__setattr__(name: str, value: Any, /) -> None
|
||||||
|
__sizeof__ :: bound method Quux.__sizeof__() -> int
|
||||||
|
__str__ :: bound method Quux.__str__() -> str
|
||||||
|
__subclasshook__ :: bound method type[Quux].__subclasshook__(subclass: type, /) -> bool
|
||||||
|
_asdict :: bound method NamedTupleFallback._asdict() -> dict[str, Any]
|
||||||
|
_field_defaults :: dict[str, Any]
|
||||||
|
_fields :: tuple[str, ...]
|
||||||
|
_make :: bound method type[NamedTupleFallback]._make(iterable: Iterable[Any]) -> NamedTupleFallback
|
||||||
|
_replace :: bound method NamedTupleFallback._replace(**kwargs: Any) -> NamedTupleFallback
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
// We don't yet take function parameters into account.
|
// We don't yet take function parameters into account.
|
||||||
#[test]
|
#[test]
|
||||||
fn call_prefix1() {
|
fn call_prefix1() {
|
||||||
|
|
|
||||||
|
|
@ -667,8 +667,13 @@ reveal_type(B.__slots__) # revealed: tuple[Literal["x"], Literal["y"]]
|
||||||
|
|
||||||
### `weakref_slot`
|
### `weakref_slot`
|
||||||
|
|
||||||
When a dataclass is defined with `weakref_slot=True`, the `__weakref__` attribute is generated. For
|
When a dataclass is defined with `weakref_slot=True` on Python >=3.11, the `__weakref__` attribute
|
||||||
now, we do not attempt to infer a more precise type for it.
|
is generated. For now, we do not attempt to infer a more precise type for it.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.11"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
@ -680,6 +685,58 @@ class C:
|
||||||
reveal_type(C.__weakref__) # revealed: Any | None
|
reveal_type(C.__weakref__) # revealed: Any | None
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `__weakref__` attribute is correctly not modeled as existing on instances of slotted dataclasses
|
||||||
|
where the class definition was not marked with `weakref=True`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class C: ...
|
||||||
|
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
reveal_type(C().__weakref__) # revealed: Unknown
|
||||||
|
```
|
||||||
|
|
||||||
|
### New features are not available on old Python versions
|
||||||
|
|
||||||
|
Certain parameters to `@dataclass` were added on newer Python versions; we do not infer them as
|
||||||
|
having any effect on older Python versions:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.9"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
|
||||||
|
# TODO: these nonexistent keyword arguments should cause us to emit diagnostics on Python 3.9
|
||||||
|
@dataclass(
|
||||||
|
slots=True,
|
||||||
|
weakref_slot=True,
|
||||||
|
match_args=True
|
||||||
|
)
|
||||||
|
class Foo: ...
|
||||||
|
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
reveal_type(Foo.__slots__) # revealed: Unknown
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
reveal_type(Foo.__match_args__) # revealed: Unknown
|
||||||
|
|
||||||
|
# TODO: this actually *does* exist at runtime
|
||||||
|
# (all classes and non-slotted instances have it available by default).
|
||||||
|
# We could try to model that more fully...?
|
||||||
|
# It's not added by the dataclasses machinery, though.
|
||||||
|
#
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
reveal_type(Foo.__weakref__) # revealed: Unknown
|
||||||
|
```
|
||||||
|
|
||||||
## `Final` fields
|
## `Final` fields
|
||||||
|
|
||||||
Dataclass fields can be annotated with `Final`, which means that the field cannot be reassigned
|
Dataclass fields can be annotated with `Final`, which means that the field cannot be reassigned
|
||||||
|
|
|
||||||
|
|
@ -682,7 +682,12 @@ static_assert(has_member(C(1), "__slots__"))
|
||||||
|
|
||||||
#### `weakref_slot=True`
|
#### `weakref_slot=True`
|
||||||
|
|
||||||
When `weakref_slot=True`, the corresponding dunder attribute becomes available:
|
When `weakref_slot=True` on Python >=3.11, the corresponding dunder attribute becomes available:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.11"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from ty_extensions import has_member, static_assert
|
from ty_extensions import has_member, static_assert
|
||||||
|
|
@ -742,6 +747,32 @@ def _(c: C):
|
||||||
static_assert(has_member(c, "__match_args__"))
|
static_assert(has_member(c, "__match_args__"))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Attributes added on new Python versions are not synthesized on older Python versions
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.9"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from ty_extensions import static_assert, has_member
|
||||||
|
|
||||||
|
# TODO: these parameters don't exist on Python 3.9;
|
||||||
|
# we should emit a diagnostic (or two)
|
||||||
|
@dataclass(slots=True, weakref_slot=True)
|
||||||
|
class F: ...
|
||||||
|
|
||||||
|
static_assert(not has_member(F, "__slots__"))
|
||||||
|
static_assert(not has_member(F, "__match_args__"))
|
||||||
|
|
||||||
|
# In actual fact, all non-slotted instances have this attribute
|
||||||
|
# (and even slotted instances can, if `__weakref__` is included in `__slots__`);
|
||||||
|
# we could possibly model that more fully?
|
||||||
|
# It's not added by the dataclasses machinery, though
|
||||||
|
static_assert(not has_member(F(), "__weakref__"))
|
||||||
|
```
|
||||||
|
|
||||||
### Attributes not available at runtime
|
### Attributes not available at runtime
|
||||||
|
|
||||||
Typeshed includes some attributes in `object` that are not available for some (builtin) types. For
|
Typeshed includes some attributes in `object` that are not available for some (builtin) types. For
|
||||||
|
|
|
||||||
|
|
@ -993,7 +993,11 @@ impl<'db> Bindings<'db> {
|
||||||
flags |= DataclassFlags::FROZEN;
|
flags |= DataclassFlags::FROZEN;
|
||||||
}
|
}
|
||||||
if to_bool(match_args, true) {
|
if to_bool(match_args, true) {
|
||||||
flags |= DataclassFlags::MATCH_ARGS;
|
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||||
|
flags |= DataclassFlags::MATCH_ARGS;
|
||||||
|
} else {
|
||||||
|
// TODO: emit diagnostic
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if to_bool(kw_only, false) {
|
if to_bool(kw_only, false) {
|
||||||
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||||
|
|
@ -1003,10 +1007,18 @@ impl<'db> Bindings<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if to_bool(slots, false) {
|
if to_bool(slots, false) {
|
||||||
flags |= DataclassFlags::SLOTS;
|
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||||
|
flags |= DataclassFlags::SLOTS;
|
||||||
|
} else {
|
||||||
|
// TODO: emit diagnostic
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if to_bool(weakref_slot, false) {
|
if to_bool(weakref_slot, false) {
|
||||||
flags |= DataclassFlags::WEAKREF_SLOT;
|
if Program::get(db).python_version(db) >= PythonVersion::PY311 {
|
||||||
|
flags |= DataclassFlags::WEAKREF_SLOT;
|
||||||
|
} else {
|
||||||
|
// TODO: emit diagnostic
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = DataclassParams::from_flags(db, flags);
|
let params = DataclassParams::from_flags(db, flags);
|
||||||
|
|
|
||||||
|
|
@ -2397,7 +2397,9 @@ impl<'db> ClassLiteral<'db> {
|
||||||
.map(|(name, _)| Type::string_literal(db, name));
|
.map(|(name, _)| Type::string_literal(db, name));
|
||||||
Some(Type::heterogeneous_tuple(db, match_args))
|
Some(Type::heterogeneous_tuple(db, match_args))
|
||||||
}
|
}
|
||||||
(CodeGeneratorKind::DataclassLike(_), "__weakref__") => {
|
(CodeGeneratorKind::DataclassLike(_), "__weakref__")
|
||||||
|
if Program::get(db).python_version(db) >= PythonVersion::PY311 =>
|
||||||
|
{
|
||||||
if !has_dataclass_param(DataclassFlags::WEAKREF_SLOT)
|
if !has_dataclass_param(DataclassFlags::WEAKREF_SLOT)
|
||||||
|| !has_dataclass_param(DataclassFlags::SLOTS)
|
|| !has_dataclass_param(DataclassFlags::SLOTS)
|
||||||
{
|
{
|
||||||
|
|
@ -2456,7 +2458,9 @@ impl<'db> ClassLiteral<'db> {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
(CodeGeneratorKind::DataclassLike(_), "__slots__") => {
|
(CodeGeneratorKind::DataclassLike(_), "__slots__")
|
||||||
|
if Program::get(db).python_version(db) >= PythonVersion::PY310 =>
|
||||||
|
{
|
||||||
has_dataclass_param(DataclassFlags::SLOTS).then(|| {
|
has_dataclass_param(DataclassFlags::SLOTS).then(|| {
|
||||||
let fields = self.fields(db, specialization, field_policy);
|
let fields = self.fields(db, specialization, field_policy);
|
||||||
let slots = fields.keys().map(|name| Type::string_literal(db, name));
|
let slots = fields.keys().map(|name| Type::string_literal(db, name));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue