mirror of https://github.com/astral-sh/ruff
[ty] Fix `is_disjoint_from` with `@final` classes (#21167)
## Summary We currently perform a subtyping check instead of the intended subclass check (and the subtyping check is confusingly named `is_subclass_of`). This showed up in https://github.com/astral-sh/ruff/pull/21070.
This commit is contained in:
parent
3179b05221
commit
1baf98aab3
|
|
@ -87,6 +87,31 @@ static_assert(is_disjoint_from(memoryview, Foo))
|
||||||
static_assert(is_disjoint_from(type[memoryview], type[Foo]))
|
static_assert(is_disjoint_from(type[memoryview], type[Foo]))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Specialized `@final` types
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import final
|
||||||
|
from ty_extensions import static_assert, is_disjoint_from
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Foo[T]:
|
||||||
|
def get(self) -> T:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class A: ...
|
||||||
|
class B: ...
|
||||||
|
|
||||||
|
static_assert(not is_disjoint_from(Foo[A], Foo[B]))
|
||||||
|
|
||||||
|
# TODO: `int` and `str` are disjoint bases, so these should be disjoint.
|
||||||
|
static_assert(not is_disjoint_from(Foo[int], Foo[str]))
|
||||||
|
```
|
||||||
|
|
||||||
## "Disjoint base" builtin types
|
## "Disjoint base" builtin types
|
||||||
|
|
||||||
Most other builtins can be subclassed and can even be used in multiple inheritance. However, builtin
|
Most other builtins can be subclassed and can even be used in multiple inheritance. However, builtin
|
||||||
|
|
|
||||||
|
|
@ -637,12 +637,17 @@ impl<'db> ClassType<'db> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimisation: if either class is `@final`, we only need to do one `is_subclass_of` call.
|
|
||||||
if self.is_final(db) {
|
if self.is_final(db) {
|
||||||
return self.is_subclass_of(db, other);
|
return self
|
||||||
|
.iter_mro(db)
|
||||||
|
.filter_map(ClassBase::into_class)
|
||||||
|
.any(|class| class.class_literal(db).0 == other.class_literal(db).0);
|
||||||
}
|
}
|
||||||
if other.is_final(db) {
|
if other.is_final(db) {
|
||||||
return other.is_subclass_of(db, self);
|
return other
|
||||||
|
.iter_mro(db)
|
||||||
|
.filter_map(ClassBase::into_class)
|
||||||
|
.any(|class| class.class_literal(db).0 == self.class_literal(db).0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two disjoint bases can only coexist in an MRO if one is a subclass of the other.
|
// Two disjoint bases can only coexist in an MRO if one is a subclass of the other.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue