mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-02 12:58:27 +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 / 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 / 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 / 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 / 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 / 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
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