mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-19 03:48:29 +00:00
[ty] Use declared attribute types as type context (#21143)
## Summary
For example:
```py
class X:
x: list[int | str]
def _(x: X):
x.x = [1]
```
Resolves https://github.com/astral-sh/ty/issues/1375.
This commit is contained in:
parent
b93d8f2b9f
commit
bb40c34361
3 changed files with 304 additions and 107 deletions
|
|
@ -185,12 +185,12 @@ Declared attribute types:
|
|||
|
||||
```py
|
||||
class E:
|
||||
e: list[Literal[1]]
|
||||
a: list[Literal[1]]
|
||||
b: list[Literal[1]]
|
||||
|
||||
def _(e: E):
|
||||
# TODO: Implement attribute type context.
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to attribute `e` of type `list[Literal[1]]`"
|
||||
e.e = [1]
|
||||
e.a = [1]
|
||||
E.b = [1]
|
||||
```
|
||||
|
||||
Function return types:
|
||||
|
|
@ -200,6 +200,41 @@ def f() -> list[Literal[1]]:
|
|||
return [1]
|
||||
```
|
||||
|
||||
## Instance attribute
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Both meta and class/instance attribute annotations are used as type context:
|
||||
|
||||
```py
|
||||
from typing import Literal, Any
|
||||
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> list[Literal[1]]:
|
||||
return []
|
||||
|
||||
def __set__(self, instance: object, value: list[Literal[1]]) -> None:
|
||||
pass
|
||||
|
||||
def lst[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
def _(flag: bool):
|
||||
class Meta(type):
|
||||
if flag:
|
||||
x: DataDescriptor = DataDescriptor()
|
||||
|
||||
class C(metaclass=Meta):
|
||||
x: list[int | None]
|
||||
|
||||
def _(c: C):
|
||||
c.x = lst(1)
|
||||
C.x = lst(1)
|
||||
```
|
||||
|
||||
## Class constructor parameters
|
||||
|
||||
```toml
|
||||
|
|
@ -226,3 +261,72 @@ A(f(1))
|
|||
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `list[int | None]`, found `list[list[Unknown]]`"
|
||||
A(f([]))
|
||||
```
|
||||
|
||||
## Multi-inference diagnostics
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Diagnostics unrelated to the type-context are only reported once:
|
||||
|
||||
`call.py`:
|
||||
|
||||
```py
|
||||
def f[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
def a(x: list[bool], y: list[bool]): ...
|
||||
def b(x: list[int], y: list[int]): ...
|
||||
def c(x: list[int], y: list[int]): ...
|
||||
def _(x: int):
|
||||
if x == 0:
|
||||
y = a
|
||||
elif x == 1:
|
||||
y = b
|
||||
else:
|
||||
y = c
|
||||
|
||||
if x == 0:
|
||||
z = True
|
||||
|
||||
y(f(True), [True])
|
||||
|
||||
# error: [possibly-unresolved-reference] "Name `z` used when possibly not defined"
|
||||
y(f(True), [z])
|
||||
```
|
||||
|
||||
`call_standalone_expression.py`:
|
||||
|
||||
```py
|
||||
def f(_: str): ...
|
||||
def g(_: str): ...
|
||||
def _(a: object, b: object, flag: bool):
|
||||
if flag:
|
||||
x = f
|
||||
else:
|
||||
x = g
|
||||
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `object` and `object`"
|
||||
x(f"{'a' if a > b else 'b'}")
|
||||
```
|
||||
|
||||
`attribute_assignment.py`:
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class TD(TypedDict):
|
||||
y: int
|
||||
|
||||
class X:
|
||||
td: TD
|
||||
|
||||
def _(x: X, flag: bool):
|
||||
if flag:
|
||||
y = 1
|
||||
|
||||
# error: [possibly-unresolved-reference] "Name `y` used when possibly not defined"
|
||||
x.td = {"y": y}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -281,46 +281,3 @@ def _(flag: bool):
|
|||
# we currently consider `TypedDict` instances to be subtypes of `dict`
|
||||
f({"y": 1})
|
||||
```
|
||||
|
||||
Diagnostics unrelated to the type-context are only reported once:
|
||||
|
||||
`expression.py`:
|
||||
|
||||
```py
|
||||
def f[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
def a(x: list[bool], y: list[bool]): ...
|
||||
def b(x: list[int], y: list[int]): ...
|
||||
def c(x: list[int], y: list[int]): ...
|
||||
def _(x: int):
|
||||
if x == 0:
|
||||
y = a
|
||||
elif x == 1:
|
||||
y = b
|
||||
else:
|
||||
y = c
|
||||
|
||||
if x == 0:
|
||||
z = True
|
||||
|
||||
y(f(True), [True])
|
||||
|
||||
# error: [possibly-unresolved-reference] "Name `z` used when possibly not defined"
|
||||
y(f(True), [z])
|
||||
```
|
||||
|
||||
`standalone_expression.py`:
|
||||
|
||||
```py
|
||||
def f(_: str): ...
|
||||
def g(_: str): ...
|
||||
def _(a: object, b: object, flag: bool):
|
||||
if flag:
|
||||
x = f
|
||||
else:
|
||||
x = g
|
||||
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `object` and `object`"
|
||||
x(f"{'a' if a > b else 'b'}")
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue