mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00
[ty] Allow unions including Any
/Unknown
as bases (#18094)
## Summary Alternative fix for https://github.com/astral-sh/ty/issues/312 ## Test Plan New Markdown test
This commit is contained in:
parent
7dc4fefb47
commit
6e39250015
3 changed files with 82 additions and 38 deletions
|
@ -256,6 +256,25 @@ class Foo(x): ...
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `__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.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
def _(flag: bool, any: Any):
|
||||||
|
if flag:
|
||||||
|
Base = any
|
||||||
|
else:
|
||||||
|
class Base: ...
|
||||||
|
|
||||||
|
class Foo(Base): ...
|
||||||
|
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Any, <class 'object'>]
|
||||||
|
```
|
||||||
|
|
||||||
## `__bases__` includes multiple `Union`s
|
## `__bases__` includes multiple `Union`s
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
@ -140,43 +140,6 @@ info[revealed-type]: Revealed type
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
|
||||||
error[duplicate-base]: Duplicate base class `Eggs`
|
|
||||||
--> src/mdtest_snippet.py:16:7
|
|
||||||
|
|
|
||||||
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
|
||||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
|
||||||
16 | class Ham(
|
|
||||||
| _______^
|
|
||||||
17 | | Spam,
|
|
||||||
18 | | Eggs,
|
|
||||||
19 | | Bar,
|
|
||||||
20 | | Baz,
|
|
||||||
21 | | Spam,
|
|
||||||
22 | | Eggs,
|
|
||||||
23 | | ): ...
|
|
||||||
| |_^
|
|
||||||
24 |
|
|
||||||
25 | # fmt: on
|
|
||||||
|
|
|
||||||
info: The definition of class `Ham` will raise `TypeError` at runtime
|
|
||||||
--> src/mdtest_snippet.py:18:5
|
|
||||||
|
|
|
||||||
16 | class Ham(
|
|
||||||
17 | Spam,
|
|
||||||
18 | Eggs,
|
|
||||||
| ---- Class `Eggs` first included in bases list here
|
|
||||||
19 | Bar,
|
|
||||||
20 | Baz,
|
|
||||||
21 | Spam,
|
|
||||||
22 | Eggs,
|
|
||||||
| ^^^^ Class `Eggs` later repeated here
|
|
||||||
23 | ): ...
|
|
||||||
|
|
|
||||||
info: rule `duplicate-base` is enabled by default
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
```
|
||||||
error[duplicate-base]: Duplicate base class `Spam`
|
error[duplicate-base]: Duplicate base class `Spam`
|
||||||
--> src/mdtest_snippet.py:16:7
|
--> src/mdtest_snippet.py:16:7
|
||||||
|
@ -215,6 +178,43 @@ info: rule `duplicate-base` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[duplicate-base]: Duplicate base class `Eggs`
|
||||||
|
--> src/mdtest_snippet.py:16:7
|
||||||
|
|
|
||||||
|
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||||
|
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||||
|
16 | class Ham(
|
||||||
|
| _______^
|
||||||
|
17 | | Spam,
|
||||||
|
18 | | Eggs,
|
||||||
|
19 | | Bar,
|
||||||
|
20 | | Baz,
|
||||||
|
21 | | Spam,
|
||||||
|
22 | | Eggs,
|
||||||
|
23 | | ): ...
|
||||||
|
| |_^
|
||||||
|
24 |
|
||||||
|
25 | # fmt: on
|
||||||
|
|
|
||||||
|
info: The definition of class `Ham` will raise `TypeError` at runtime
|
||||||
|
--> src/mdtest_snippet.py:18:5
|
||||||
|
|
|
||||||
|
16 | class Ham(
|
||||||
|
17 | Spam,
|
||||||
|
18 | Eggs,
|
||||||
|
| ---- Class `Eggs` first included in bases list here
|
||||||
|
19 | Bar,
|
||||||
|
20 | Baz,
|
||||||
|
21 | Spam,
|
||||||
|
22 | Eggs,
|
||||||
|
| ^^^^ Class `Eggs` later repeated here
|
||||||
|
23 | ): ...
|
||||||
|
|
|
||||||
|
info: rule `duplicate-base` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
info[revealed-type]: Revealed type
|
info[revealed-type]: Revealed type
|
||||||
--> src/mdtest_snippet.py:27:13
|
--> src/mdtest_snippet.py:27:13
|
||||||
|
|
|
@ -123,7 +123,32 @@ impl<'db> ClassBase<'db> {
|
||||||
Some(valid_element)
|
Some(valid_element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
|
Type::Union(union) => {
|
||||||
|
// We do not support full unions of MROs (yet). Until we do,
|
||||||
|
// support the cases where one of the types in the union is
|
||||||
|
// a dynamic type such as `Any` or `Unknown`, and all other
|
||||||
|
// types *would be* valid class bases. In this case, we can
|
||||||
|
// "fold" the other potential bases into the dynamic type,
|
||||||
|
// and return `Any`/`Unknown` as the class base to prevent
|
||||||
|
// invalid-base diagnostics and further downstream errors.
|
||||||
|
let Some(Type::Dynamic(dynamic)) = union
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.find(|elem| matches!(elem, Type::Dynamic(_)))
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if union
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.all(|elem| ClassBase::try_from_type(db, *elem).is_some())
|
||||||
|
{
|
||||||
|
Some(ClassBase::Dynamic(*dynamic))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
Type::NominalInstance(_) => None, // TODO -- handle `__mro_entries__`?
|
Type::NominalInstance(_) => None, // TODO -- handle `__mro_entries__`?
|
||||||
Type::PropertyInstance(_) => None,
|
Type::PropertyInstance(_) => None,
|
||||||
Type::Never
|
Type::Never
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue