mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:45:24 +00:00
[red-knot] Treat classes as instances of their respective metaclasses in boolean tests (#15105)
## Summary Follow-up to #15089. ## Test Plan Markdown tests. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
3b27d5dbad
commit
f764f59971
2 changed files with 68 additions and 24 deletions
|
@ -220,6 +220,57 @@ else:
|
||||||
reveal_type(y) # revealed: A & ~AlwaysTruthy | A & ~AlwaysFalsy
|
reveal_type(y) # revealed: A & ~AlwaysTruthy | A & ~AlwaysFalsy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Truthiness of classes
|
||||||
|
|
||||||
|
```py
|
||||||
|
class MetaAmbiguous(type):
|
||||||
|
def __bool__(self) -> bool: ...
|
||||||
|
|
||||||
|
class MetaFalsy(type):
|
||||||
|
def __bool__(self) -> Literal[False]: ...
|
||||||
|
|
||||||
|
class MetaTruthy(type):
|
||||||
|
def __bool__(self) -> Literal[True]: ...
|
||||||
|
|
||||||
|
class MetaDeferred(type):
|
||||||
|
def __bool__(self) -> MetaAmbiguous: ...
|
||||||
|
|
||||||
|
class AmbiguousClass(metaclass=MetaAmbiguous): ...
|
||||||
|
class FalsyClass(metaclass=MetaFalsy): ...
|
||||||
|
class TruthyClass(metaclass=MetaTruthy): ...
|
||||||
|
class DeferredClass(metaclass=MetaDeferred): ...
|
||||||
|
|
||||||
|
def _(
|
||||||
|
a: type[AmbiguousClass],
|
||||||
|
t: type[TruthyClass],
|
||||||
|
f: type[FalsyClass],
|
||||||
|
d: type[DeferredClass],
|
||||||
|
ta: type[TruthyClass | AmbiguousClass],
|
||||||
|
af: type[AmbiguousClass] | type[FalsyClass],
|
||||||
|
flag: bool,
|
||||||
|
):
|
||||||
|
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass]
|
||||||
|
if ta:
|
||||||
|
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass] & ~AlwaysFalsy
|
||||||
|
|
||||||
|
reveal_type(af) # revealed: type[AmbiguousClass] | type[FalsyClass]
|
||||||
|
if af:
|
||||||
|
reveal_type(af) # revealed: type[AmbiguousClass] & ~AlwaysFalsy
|
||||||
|
|
||||||
|
# TODO: Emit a diagnostic (`d` is not valid in boolean context)
|
||||||
|
if d:
|
||||||
|
# TODO: Should be `Unknown`
|
||||||
|
reveal_type(d) # revealed: type[DeferredClass] & ~AlwaysFalsy
|
||||||
|
|
||||||
|
tf = TruthyClass if flag else FalsyClass
|
||||||
|
reveal_type(tf) # revealed: Literal[TruthyClass, FalsyClass]
|
||||||
|
|
||||||
|
if tf:
|
||||||
|
reveal_type(tf) # revealed: Literal[TruthyClass]
|
||||||
|
else:
|
||||||
|
reveal_type(tf) # revealed: Literal[FalsyClass]
|
||||||
|
```
|
||||||
|
|
||||||
## Narrowing in chained boolean expressions
|
## Narrowing in chained boolean expressions
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
@ -253,17 +304,12 @@ class MetaFalsy(type):
|
||||||
def __bool__(self) -> Literal[False]: ...
|
def __bool__(self) -> Literal[False]: ...
|
||||||
|
|
||||||
class MetaTruthy(type):
|
class MetaTruthy(type):
|
||||||
def __bool__(self) -> Literal[False]: ...
|
def __bool__(self) -> Literal[True]: ...
|
||||||
|
|
||||||
class FalsyClass(metaclass=MetaFalsy): ...
|
class FalsyClass(metaclass=MetaFalsy): ...
|
||||||
class TruthyClass(metaclass=MetaTruthy): ...
|
class TruthyClass(metaclass=MetaTruthy): ...
|
||||||
|
|
||||||
def _(x: type[FalsyClass] | type[TruthyClass]):
|
def _(x: type[FalsyClass] | type[TruthyClass]):
|
||||||
# TODO: Should be `type[TruthyClass] | A`
|
reveal_type(x or A()) # revealed: type[TruthyClass] | A
|
||||||
# revealed: type[FalsyClass] & ~AlwaysFalsy | type[TruthyClass] & ~AlwaysFalsy | A
|
reveal_type(x and A()) # revealed: type[FalsyClass] | A
|
||||||
reveal_type(x or A())
|
|
||||||
|
|
||||||
# TODO: Should be `type[FalsyClass] | A`
|
|
||||||
# revealed: type[FalsyClass] & ~AlwaysTruthy | type[TruthyClass] & ~AlwaysTruthy | A
|
|
||||||
reveal_type(x and A())
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -1206,14 +1206,6 @@ impl<'db> Type<'db> {
|
||||||
Type::SubclassOf(_),
|
Type::SubclassOf(_),
|
||||||
) => true,
|
) => true,
|
||||||
|
|
||||||
(Type::SubclassOf(_), _) | (_, Type::SubclassOf(_)) => {
|
|
||||||
// TODO: Once we have support for final classes, we can determine disjointness in some cases
|
|
||||||
// here. However, note that it might be better to turn `Type::SubclassOf('FinalClass')` into
|
|
||||||
// `Type::ClassLiteral('FinalClass')` during construction, instead of adding special cases for
|
|
||||||
// final classes inside `Type::SubclassOf` everywhere.
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
(Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => {
|
(Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => {
|
||||||
// `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint.
|
// `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint.
|
||||||
// Thus, they are only disjoint if `ty.bool() == AlwaysFalse`.
|
// Thus, they are only disjoint if `ty.bool() == AlwaysFalse`.
|
||||||
|
@ -1224,6 +1216,14 @@ impl<'db> Type<'db> {
|
||||||
matches!(ty.bool(db), Truthiness::AlwaysTrue)
|
matches!(ty.bool(db), Truthiness::AlwaysTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(Type::SubclassOf(_), _) | (_, Type::SubclassOf(_)) => {
|
||||||
|
// TODO: Once we have support for final classes, we can determine disjointness in some cases
|
||||||
|
// here. However, note that it might be better to turn `Type::SubclassOf('FinalClass')` into
|
||||||
|
// `Type::ClassLiteral('FinalClass')` during construction, instead of adding special cases for
|
||||||
|
// final classes inside `Type::SubclassOf` everywhere.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
(Type::KnownInstance(left), right) => {
|
(Type::KnownInstance(left), right) => {
|
||||||
left.instance_fallback(db).is_disjoint_from(db, right)
|
left.instance_fallback(db).is_disjoint_from(db, right)
|
||||||
}
|
}
|
||||||
|
@ -1678,15 +1678,13 @@ impl<'db> Type<'db> {
|
||||||
Type::Any | Type::Todo(_) | Type::Never | Type::Unknown => Truthiness::Ambiguous,
|
Type::Any | Type::Todo(_) | Type::Never | Type::Unknown => Truthiness::Ambiguous,
|
||||||
Type::FunctionLiteral(_) => Truthiness::AlwaysTrue,
|
Type::FunctionLiteral(_) => Truthiness::AlwaysTrue,
|
||||||
Type::ModuleLiteral(_) => Truthiness::AlwaysTrue,
|
Type::ModuleLiteral(_) => Truthiness::AlwaysTrue,
|
||||||
Type::ClassLiteral(_) => {
|
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||||
// TODO: lookup `__bool__` and `__len__` methods on the class's metaclass
|
class.metaclass(db).to_instance(db).bool(db)
|
||||||
// More info in https://docs.python.org/3/library/stdtypes.html#truth-value-testing
|
|
||||||
Truthiness::Ambiguous
|
|
||||||
}
|
|
||||||
Type::SubclassOf(_) => {
|
|
||||||
// TODO: see above
|
|
||||||
Truthiness::Ambiguous
|
|
||||||
}
|
}
|
||||||
|
Type::SubclassOf(SubclassOfType { base }) => base
|
||||||
|
.into_class()
|
||||||
|
.map(|class| Type::class_literal(class).bool(db))
|
||||||
|
.unwrap_or(Truthiness::Ambiguous),
|
||||||
Type::AlwaysTruthy => Truthiness::AlwaysTrue,
|
Type::AlwaysTruthy => Truthiness::AlwaysTrue,
|
||||||
Type::AlwaysFalsy => Truthiness::AlwaysFalse,
|
Type::AlwaysFalsy => Truthiness::AlwaysFalse,
|
||||||
instance_ty @ Type::Instance(InstanceType { class }) => {
|
instance_ty @ Type::Instance(InstanceType { class }) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue