mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[ty] Split invalid-base
error code into two error codes (#18245)
Some checks are pending
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 / cargo test (linux) (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / benchmarks (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (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 / 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
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
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 / cargo test (linux) (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / benchmarks (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (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 / 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
[ty Playground] Release / publish (push) Waiting to run
This commit is contained in:
parent
02394b8049
commit
cb04343b3b
9 changed files with 568 additions and 130 deletions
|
@ -173,7 +173,7 @@ if hasattr(DoesNotExist, "__mro__"):
|
|||
if not isinstance(DoesNotExist, type):
|
||||
reveal_type(DoesNotExist) # revealed: Unknown & ~type
|
||||
|
||||
class Foo(DoesNotExist): ... # error: [invalid-base]
|
||||
class Foo(DoesNotExist): ... # error: [unsupported-base]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
|
@ -232,11 +232,15 @@ reveal_type(AA.__mro__) # revealed: tuple[<class 'AA'>, <class 'Z'>, Unknown, <
|
|||
|
||||
## `__bases__` includes a `Union`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
We don't support union types in a class's bases; a base must resolve to a single `ClassType`. If we
|
||||
find a union type in a class's bases, we infer the class's `__mro__` as being
|
||||
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
|
@ -250,7 +254,7 @@ else:
|
|||
|
||||
reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
|
||||
# error: 11 [invalid-base] "Invalid class base with type `<class 'A'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
# error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
class Foo(x): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
@ -259,8 +263,8 @@ reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'obje
|
|||
## `__bases__` is a union of a dynamic type and valid bases
|
||||
|
||||
If a dynamic type such as `Any` or `Unknown` is one of the elements in the union, and all other
|
||||
types *would be* valid class bases, we do not emit an `invalid-base` diagnostic and use the dynamic
|
||||
type as a base to prevent further downstream errors.
|
||||
types *would be* valid class bases, we do not emit an `invalid-base` or `unsupported-base`
|
||||
diagnostic, and we use the dynamic type as a base to prevent further downstream errors.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
@ -299,8 +303,8 @@ else:
|
|||
reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
reveal_type(y) # revealed: <class 'C'> | <class 'D'>
|
||||
|
||||
# error: 11 [invalid-base] "Invalid class base with type `<class 'A'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
# error: 14 [invalid-base] "Invalid class base with type `<class 'C'> | <class 'D'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
# error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
# error: 14 [unsupported-base] "Unsupported class base with type `<class 'C'> | <class 'D'>`"
|
||||
class Foo(x, y): ...
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
@ -321,7 +325,7 @@ if returns_bool():
|
|||
else:
|
||||
foo = object
|
||||
|
||||
# error: 21 [invalid-base] "Invalid class base with type `<class 'Y'> | <class 'object'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
# error: 21 [unsupported-base] "Unsupported class base with type `<class 'Y'> | <class 'object'>`"
|
||||
class PossibleError(foo, X): ...
|
||||
|
||||
reveal_type(PossibleError.__mro__) # revealed: tuple[<class 'PossibleError'>, Unknown, <class 'object'>]
|
||||
|
@ -339,12 +343,47 @@ else:
|
|||
# revealed: tuple[<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>] | tuple[<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>]
|
||||
reveal_type(B.__mro__)
|
||||
|
||||
# error: 12 [invalid-base] "Invalid class base with type `<class 'B'> | <class 'B'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
# error: 12 [unsupported-base] "Unsupported class base with type `<class 'B'> | <class 'B'>`"
|
||||
class Z(A, B): ...
|
||||
|
||||
reveal_type(Z.__mro__) # revealed: tuple[<class 'Z'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
## `__bases__` lists that include objects that are not instances of `type`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
class Foo(2): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
A base that is not an instance of `type` but does have an `__mro_entries__` method will not raise an
|
||||
exception at runtime, so we issue `unsupported-base` rather than `invalid-base`:
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
def __mro_entries__(self, bases: tuple[type, ...]) -> tuple[type, ...]:
|
||||
return ()
|
||||
|
||||
class Bar(Foo()): ... # error: [unsupported-base]
|
||||
```
|
||||
|
||||
But for objects that have badly defined `__mro_entries__`, `invalid-base` is emitted rather than
|
||||
`unsupported-base`:
|
||||
|
||||
```py
|
||||
class Bad1:
|
||||
def __mro_entries__(self, bases, extra_arg):
|
||||
return ()
|
||||
|
||||
class Bad2:
|
||||
def __mro_entries__(self, bases) -> int:
|
||||
return 42
|
||||
|
||||
class BadSub1(Bad1()): ... # error: [invalid-base]
|
||||
class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
## `__bases__` lists with duplicate bases
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: mro.md - Method Resolution Order tests - `__bases__` includes a `Union`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def returns_bool() -> bool:
|
||||
4 | return True
|
||||
5 |
|
||||
6 | class A: ...
|
||||
7 | class B: ...
|
||||
8 |
|
||||
9 | if returns_bool():
|
||||
10 | x = A
|
||||
11 | else:
|
||||
12 | x = B
|
||||
13 |
|
||||
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
15 |
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
17 | class Foo(x): ...
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:13
|
||||
|
|
||||
12 | x = B
|
||||
13 |
|
||||
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
| ^ `<class 'A'> | <class 'B'>`
|
||||
15 |
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
|
||||
--> src/mdtest_snippet.py:17:11
|
||||
|
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
17 | class Foo(x): ...
|
||||
| ^
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
|
||||
info: ty cannot resolve a consistent MRO for class `Foo` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
info: rule `unsupported-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:19:13
|
||||
|
|
||||
17 | class Foo(x): ...
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
| ^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
|
||||
|
|
||||
|
||||
```
|
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: mro.md - Method Resolution Order tests - `__bases__` lists that include objects that are not instances of `type`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Foo(2): ... # error: [invalid-base]
|
||||
2 | class Foo:
|
||||
3 | def __mro_entries__(self, bases: tuple[type, ...]) -> tuple[type, ...]:
|
||||
4 | return ()
|
||||
5 |
|
||||
6 | class Bar(Foo()): ... # error: [unsupported-base]
|
||||
7 | class Bad1:
|
||||
8 | def __mro_entries__(self, bases, extra_arg):
|
||||
9 | return ()
|
||||
10 |
|
||||
11 | class Bad2:
|
||||
12 | def __mro_entries__(self, bases) -> int:
|
||||
13 | return 42
|
||||
14 |
|
||||
15 | class BadSub1(Bad1()): ... # error: [invalid-base]
|
||||
16 | class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid class base with type `Literal[2]`
|
||||
--> src/mdtest_snippet.py:1:11
|
||||
|
|
||||
1 | class Foo(2): ... # error: [invalid-base]
|
||||
| ^
|
||||
2 | class Foo:
|
||||
3 | def __mro_entries__(self, bases: tuple[type, ...]) -> tuple[type, ...]:
|
||||
|
|
||||
info: Definition of class `Foo` will raise `TypeError` at runtime
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning[unsupported-base]: Unsupported class base with type `Foo`
|
||||
--> src/mdtest_snippet.py:6:11
|
||||
|
|
||||
4 | return ()
|
||||
5 |
|
||||
6 | class Bar(Foo()): ... # error: [unsupported-base]
|
||||
| ^^^^^
|
||||
7 | class Bad1:
|
||||
8 | def __mro_entries__(self, bases, extra_arg):
|
||||
|
|
||||
info: ty cannot resolve a consistent MRO for class `Bar` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
info: rule `unsupported-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid class base with type `Bad1`
|
||||
--> src/mdtest_snippet.py:15:15
|
||||
|
|
||||
13 | return 42
|
||||
14 |
|
||||
15 | class BadSub1(Bad1()): ... # error: [invalid-base]
|
||||
| ^^^^^^
|
||||
16 | class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
|
|
||||
info: Definition of class `BadSub1` will raise `TypeError` at runtime
|
||||
info: An instance type is only a valid class base if it has a valid `__mro_entries__` method
|
||||
info: Type `Bad1` has an `__mro_entries__` method, but it cannot be called with the expected arguments
|
||||
info: Expected a signature at least as permissive as `def __mro_entries__(self, bases: tuple[type, ...], /) -> tuple[type, ...]`
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-base]: Invalid class base with type `Bad2`
|
||||
--> src/mdtest_snippet.py:16:15
|
||||
|
|
||||
15 | class BadSub1(Bad1()): ... # error: [invalid-base]
|
||||
16 | class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Definition of class `BadSub2` will raise `TypeError` at runtime
|
||||
info: An instance type is only a valid class base if it has a valid `__mro_entries__` method
|
||||
info: Type `Bad2` has an `__mro_entries__` method, but it does not return a tuple of types
|
||||
info: rule `invalid-base` is enabled by default
|
||||
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue