mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] print diagnostics with fully qualified name to disambiguate some cases (#19850)
Some checks are pending
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 (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / mkdocs (push) Waiting to run
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 / 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 (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 / 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 (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / mkdocs (push) Waiting to run
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 / 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 (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
There are some situations that we have a confusing diagnostics due to identical class names. ## Class with same name from different modules ```python import pandas import polars df: pandas.DataFrame = polars.DataFrame() ``` This yields the following error: **Actual:** error: [invalid-assignment] "Object of type `DataFrame` is not assignable to `DataFrame`" **Expected**: error: [invalid-assignment] "Object of type `polars.DataFrame` is not assignable to `pandas.DataFrame`" ## Nested classes ```python from enum import Enum class A: class B(Enum): ACTIVE = "active" INACTIVE = "inactive" class C: class B(Enum): ACTIVE = "active" INACTIVE = "inactive" ``` **Actual**: error: [invalid-assignment] "Object of type `Literal[B.ACTIVE]` is not assignable to `B`" **Expected**: error: [invalid-assignment] "Object of type `Literal[my_module.C.B.ACTIVE]` is not assignable to `my_module.A.B`" ## Solution In this MR we added an heuristics to detect when to use a fully qualified name: - There is an invalid assignment and; - They are two different classes and; - They have the same name The fully qualified name always includes: - module name - nested classes name - actual class name There was no `QualifiedDisplay` so I had to implement it from scratch. I'm very new to the codebase, so I might have done things inefficiently, so I appreciate feedback. Should we pre-compute the fully qualified name or do it on demand? ## Not implemented ### Function-local classes Should we approach this in a different PR? **Example**: ```python # t.py from __future__ import annotations def function() -> A: class A: pass return A() class A: pass a: A = function() ``` #### mypy ```console t.py:8: error: Incompatible return value type (got "t.A@5", expected "t.A") [return-value] ``` From my testing the 5 in `A@5` comes from the like number. #### ty ```console error[invalid-return-type]: Return type does not match returned value --> t.py:4:19 | 4 | def function() -> A: | - Expected `A` because of return type 5 | class A: 6 | pass 7 | 8 | return A() | ^^^ expected `A`, found `A` | info: rule `invalid-return-type` is enabled by default ``` Fixes https://github.com/astral-sh/ty/issues/848 --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
89ca493fd9
commit
d75ef3823c
4 changed files with 440 additions and 81 deletions
|
@ -0,0 +1,230 @@
|
|||
# Identical type display names in diagnostics
|
||||
|
||||
ty prints the fully qualified name to disambiguate objects with the same name.
|
||||
|
||||
## Nested class
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
class A:
|
||||
class B:
|
||||
pass
|
||||
|
||||
class C:
|
||||
class B:
|
||||
pass
|
||||
|
||||
a: A.B = C.B() # error: [invalid-assignment] "Object of type `test.C.B` is not assignable to `test.A.B`"
|
||||
```
|
||||
|
||||
## Nested class in function
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
class B:
|
||||
pass
|
||||
|
||||
def f(b: B):
|
||||
class B:
|
||||
pass
|
||||
|
||||
# error: [invalid-assignment] "Object of type `test.<locals of function 'f'>.B` is not assignable to `test.B`"
|
||||
b = B()
|
||||
```
|
||||
|
||||
## Class from different modules
|
||||
|
||||
```py
|
||||
import a
|
||||
import b
|
||||
|
||||
df: a.DataFrame = b.DataFrame() # error: [invalid-assignment] "Object of type `b.DataFrame` is not assignable to `a.DataFrame`"
|
||||
|
||||
def _(dfs: list[b.DataFrame]):
|
||||
# TODO should be"Object of type `list[b.DataFrame]` is not assignable to `list[a.DataFrame]`
|
||||
# error: [invalid-assignment] "Object of type `list[DataFrame]` is not assignable to `list[DataFrame]`"
|
||||
dataframes: list[a.DataFrame] = dfs
|
||||
```
|
||||
|
||||
`a.py`:
|
||||
|
||||
```py
|
||||
class DataFrame:
|
||||
pass
|
||||
```
|
||||
|
||||
`b.py`:
|
||||
|
||||
```py
|
||||
class DataFrame:
|
||||
pass
|
||||
```
|
||||
|
||||
## Enum from different modules
|
||||
|
||||
```py
|
||||
import status_a
|
||||
import status_b
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[status_b.Status.ACTIVE]` is not assignable to `status_a.Status`"
|
||||
s: status_a.Status = status_b.Status.ACTIVE
|
||||
```
|
||||
|
||||
`status_a.py`:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = 1
|
||||
INACTIVE = 2
|
||||
```
|
||||
|
||||
`status_b.py`:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
```
|
||||
|
||||
## Nested enum
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
class A:
|
||||
class B(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
|
||||
class C:
|
||||
class B(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[test.C.B.ACTIVE]` is not assignable to `test.A.B`"
|
||||
a: A.B = C.B.ACTIVE
|
||||
```
|
||||
|
||||
## Class literals
|
||||
|
||||
```py
|
||||
import cls_a
|
||||
import cls_b
|
||||
|
||||
# error: [invalid-assignment] "Object of type `<class 'cls_b.Config'>` is not assignable to `type[cls_a.Config]`"
|
||||
config_class: type[cls_a.Config] = cls_b.Config
|
||||
```
|
||||
|
||||
`cls_a.py`:
|
||||
|
||||
```py
|
||||
class Config:
|
||||
pass
|
||||
```
|
||||
|
||||
`cls_b.py`:
|
||||
|
||||
```py
|
||||
class Config:
|
||||
pass
|
||||
```
|
||||
|
||||
## Generic aliases
|
||||
|
||||
```py
|
||||
import generic_a
|
||||
import generic_b
|
||||
|
||||
# TODO should be error: [invalid-assignment] "Object of type `<class 'generic_b.Container[int]'>` is not assignable to `type[generic_a.Container[int]]`"
|
||||
container: type[generic_a.Container[int]] = generic_b.Container[int]
|
||||
```
|
||||
|
||||
`generic_a.py`:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Container(Generic[T]):
|
||||
pass
|
||||
```
|
||||
|
||||
`generic_b.py`:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Container(Generic[T]):
|
||||
pass
|
||||
```
|
||||
|
||||
## Protocols
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
import proto_a
|
||||
import proto_b
|
||||
|
||||
# TODO should be error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
|
||||
def _(drawable_b: proto_b.Drawable):
|
||||
drawable: proto_a.Drawable = drawable_b
|
||||
```
|
||||
|
||||
`proto_a.py`:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Drawable(Protocol):
|
||||
def draw(self) -> None: ...
|
||||
```
|
||||
|
||||
`proto_b.py`:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Drawable(Protocol):
|
||||
def draw(self) -> int: ...
|
||||
```
|
||||
|
||||
## TypedDict
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
import dict_a
|
||||
import dict_b
|
||||
|
||||
def _(b_person: dict_b.Person):
|
||||
# TODO should be error: [invalid-assignment] "Object of type `dict_b.Person` is not assignable to `dict_a.Person`"
|
||||
person_var: dict_a.Person = b_person
|
||||
```
|
||||
|
||||
`dict_a.py`:
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
```
|
||||
|
||||
`dict_b.py`:
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Person(TypedDict):
|
||||
name: bytes
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue