mirror of https://github.com/astral-sh/ruff
[red-knot] Generalise special-casing for `KnownClass`es in `Type::bool` (#16300)
This commit is contained in:
parent
5fab97f1ef
commit
5347abc766
|
|
@ -91,3 +91,32 @@ class PossiblyUnboundTrue:
|
||||||
|
|
||||||
reveal_type(bool(PossiblyUnboundTrue())) # revealed: bool
|
reveal_type(bool(PossiblyUnboundTrue())) # revealed: bool
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Special-cased classes
|
||||||
|
|
||||||
|
Some special-cased `@final` classes are known by red-knot to have instances that are either always
|
||||||
|
truthy or always falsy.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
import types
|
||||||
|
import typing
|
||||||
|
import sys
|
||||||
|
from knot_extensions import AlwaysTruthy, static_assert, is_subtype_of
|
||||||
|
from typing_extensions import _NoDefaultType
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(sys.version_info.__class__, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(types.EllipsisType, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(_NoDefaultType, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(slice, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(types.FunctionType, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(types.MethodType, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(typing.TypeVar, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(typing.TypeAliasType, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(types.MethodWrapperType, AlwaysTruthy))
|
||||||
|
static_assert(is_subtype_of(types.WrapperDescriptorType, AlwaysTruthy))
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1351,51 +1351,9 @@ impl<'db> Type<'db> {
|
||||||
.iter()
|
.iter()
|
||||||
.all(|elem| elem.is_single_valued(db)),
|
.all(|elem| elem.is_single_valued(db)),
|
||||||
|
|
||||||
Type::Instance(InstanceType { class }) => match class.known(db) {
|
Type::Instance(InstanceType { class }) => {
|
||||||
Some(
|
class.known(db).is_some_and(KnownClass::is_single_valued)
|
||||||
KnownClass::NoneType
|
}
|
||||||
| KnownClass::NoDefaultType
|
|
||||||
| KnownClass::VersionInfo
|
|
||||||
| KnownClass::EllipsisType
|
|
||||||
| KnownClass::TypeAliasType,
|
|
||||||
) => true,
|
|
||||||
Some(
|
|
||||||
KnownClass::Bool
|
|
||||||
| KnownClass::Object
|
|
||||||
| KnownClass::Bytes
|
|
||||||
| KnownClass::Type
|
|
||||||
| KnownClass::Int
|
|
||||||
| KnownClass::Float
|
|
||||||
| KnownClass::Complex
|
|
||||||
| KnownClass::Str
|
|
||||||
| KnownClass::List
|
|
||||||
| KnownClass::Tuple
|
|
||||||
| KnownClass::Set
|
|
||||||
| KnownClass::FrozenSet
|
|
||||||
| KnownClass::Dict
|
|
||||||
| KnownClass::Slice
|
|
||||||
| KnownClass::Range
|
|
||||||
| KnownClass::Property
|
|
||||||
| KnownClass::BaseException
|
|
||||||
| KnownClass::BaseExceptionGroup
|
|
||||||
| KnownClass::GenericAlias
|
|
||||||
| KnownClass::ModuleType
|
|
||||||
| KnownClass::FunctionType
|
|
||||||
| KnownClass::MethodType
|
|
||||||
| KnownClass::MethodWrapperType
|
|
||||||
| KnownClass::WrapperDescriptorType
|
|
||||||
| KnownClass::SpecialForm
|
|
||||||
| KnownClass::ChainMap
|
|
||||||
| KnownClass::Counter
|
|
||||||
| KnownClass::DefaultDict
|
|
||||||
| KnownClass::Deque
|
|
||||||
| KnownClass::OrderedDict
|
|
||||||
| KnownClass::SupportsIndex
|
|
||||||
| KnownClass::StdlibAlias
|
|
||||||
| KnownClass::TypeVar,
|
|
||||||
) => false,
|
|
||||||
None => false,
|
|
||||||
},
|
|
||||||
|
|
||||||
Type::Dynamic(_)
|
Type::Dynamic(_)
|
||||||
| Type::Never
|
| Type::Never
|
||||||
|
|
@ -1741,12 +1699,9 @@ impl<'db> Type<'db> {
|
||||||
},
|
},
|
||||||
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 }) => match class.known(db) {
|
||||||
if class.is_known(db, KnownClass::Bool) {
|
Some(known_class) => known_class.bool(),
|
||||||
Truthiness::Ambiguous
|
None => {
|
||||||
} else if class.is_known(db, KnownClass::NoneType) {
|
|
||||||
Truthiness::AlwaysFalse
|
|
||||||
} else {
|
|
||||||
// We only check the `__bool__` method for truth testing, even though at
|
// We only check the `__bool__` method for truth testing, even though at
|
||||||
// runtime there is a fallback to `__len__`, since `__bool__` takes precedence
|
// runtime there is a fallback to `__len__`, since `__bool__` takes precedence
|
||||||
// and a subclass could add a `__bool__` method.
|
// and a subclass could add a `__bool__` method.
|
||||||
|
|
@ -1824,7 +1779,7 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
Type::KnownInstance(known_instance) => known_instance.bool(),
|
Type::KnownInstance(known_instance) => known_instance.bool(),
|
||||||
Type::Union(union) => {
|
Type::Union(union) => {
|
||||||
let mut truthiness = None;
|
let mut truthiness = None;
|
||||||
|
|
@ -2928,6 +2883,59 @@ impl<'db> KnownClass {
|
||||||
matches!(self, Self::Bool)
|
matches!(self, Self::Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine whether instances of this class are always truthy, always falsy,
|
||||||
|
/// or have an ambiguous truthiness.
|
||||||
|
const fn bool(self) -> Truthiness {
|
||||||
|
match self {
|
||||||
|
// N.B. It's only generally safe to infer `Truthiness::AlwaysTrue` for a `KnownClass`
|
||||||
|
// variant if the class's `__bool__` method always returns the same thing *and* the
|
||||||
|
// class is `@final`.
|
||||||
|
//
|
||||||
|
// E.g. `ModuleType.__bool__` always returns `True`, but `ModuleType` is not `@final`.
|
||||||
|
// Equally, `range` is `@final`, but its `__bool__` method can return `False`.
|
||||||
|
Self::EllipsisType
|
||||||
|
| Self::NoDefaultType
|
||||||
|
| Self::MethodType
|
||||||
|
| Self::Slice
|
||||||
|
| Self::FunctionType
|
||||||
|
| Self::VersionInfo
|
||||||
|
| Self::TypeAliasType
|
||||||
|
| Self::TypeVar
|
||||||
|
| Self::WrapperDescriptorType
|
||||||
|
| Self::MethodWrapperType => Truthiness::AlwaysTrue,
|
||||||
|
|
||||||
|
Self::NoneType => Truthiness::AlwaysFalse,
|
||||||
|
|
||||||
|
Self::BaseException
|
||||||
|
| Self::Object
|
||||||
|
| Self::OrderedDict
|
||||||
|
| Self::BaseExceptionGroup
|
||||||
|
| Self::Bool
|
||||||
|
| Self::Str
|
||||||
|
| Self::List
|
||||||
|
| Self::GenericAlias
|
||||||
|
| Self::StdlibAlias
|
||||||
|
| Self::SupportsIndex
|
||||||
|
| Self::Set
|
||||||
|
| Self::Tuple
|
||||||
|
| Self::Int
|
||||||
|
| Self::Type
|
||||||
|
| Self::Bytes
|
||||||
|
| Self::FrozenSet
|
||||||
|
| Self::Range
|
||||||
|
| Self::Property
|
||||||
|
| Self::SpecialForm
|
||||||
|
| Self::Dict
|
||||||
|
| Self::ModuleType
|
||||||
|
| Self::ChainMap
|
||||||
|
| Self::Complex
|
||||||
|
| Self::Counter
|
||||||
|
| Self::DefaultDict
|
||||||
|
| Self::Deque
|
||||||
|
| Self::Float => Truthiness::Ambiguous,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_str(&self, db: &'db dyn Db) -> &'static str {
|
pub fn as_str(&self, db: &'db dyn Db) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Bool => "bool",
|
Self::Bool => "bool",
|
||||||
|
|
@ -3074,6 +3082,51 @@ impl<'db> KnownClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return true if all instances of this `KnownClass` compare equal.
|
||||||
|
const fn is_single_valued(self) -> bool {
|
||||||
|
match self {
|
||||||
|
KnownClass::NoneType
|
||||||
|
| KnownClass::NoDefaultType
|
||||||
|
| KnownClass::VersionInfo
|
||||||
|
| KnownClass::EllipsisType
|
||||||
|
| KnownClass::TypeAliasType => true,
|
||||||
|
|
||||||
|
KnownClass::Bool
|
||||||
|
| KnownClass::Object
|
||||||
|
| KnownClass::Bytes
|
||||||
|
| KnownClass::Type
|
||||||
|
| KnownClass::Int
|
||||||
|
| KnownClass::Float
|
||||||
|
| KnownClass::Complex
|
||||||
|
| KnownClass::Str
|
||||||
|
| KnownClass::List
|
||||||
|
| KnownClass::Tuple
|
||||||
|
| KnownClass::Set
|
||||||
|
| KnownClass::FrozenSet
|
||||||
|
| KnownClass::Dict
|
||||||
|
| KnownClass::Slice
|
||||||
|
| KnownClass::Range
|
||||||
|
| KnownClass::Property
|
||||||
|
| KnownClass::BaseException
|
||||||
|
| KnownClass::BaseExceptionGroup
|
||||||
|
| KnownClass::GenericAlias
|
||||||
|
| KnownClass::ModuleType
|
||||||
|
| KnownClass::FunctionType
|
||||||
|
| KnownClass::MethodType
|
||||||
|
| KnownClass::MethodWrapperType
|
||||||
|
| KnownClass::WrapperDescriptorType
|
||||||
|
| KnownClass::SpecialForm
|
||||||
|
| KnownClass::ChainMap
|
||||||
|
| KnownClass::Counter
|
||||||
|
| KnownClass::DefaultDict
|
||||||
|
| KnownClass::Deque
|
||||||
|
| KnownClass::OrderedDict
|
||||||
|
| KnownClass::SupportsIndex
|
||||||
|
| KnownClass::StdlibAlias
|
||||||
|
| KnownClass::TypeVar => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Is this class a singleton class?
|
/// Is this class a singleton class?
|
||||||
///
|
///
|
||||||
/// A singleton class is a class where it is known that only one instance can ever exist at runtime.
|
/// A singleton class is a class where it is known that only one instance can ever exist at runtime.
|
||||||
|
|
@ -3152,6 +3205,7 @@ impl<'db> KnownClass {
|
||||||
"MethodWrapperType" => Self::MethodWrapperType,
|
"MethodWrapperType" => Self::MethodWrapperType,
|
||||||
"WrapperDescriptorType" => Self::WrapperDescriptorType,
|
"WrapperDescriptorType" => Self::WrapperDescriptorType,
|
||||||
"TypeAliasType" => Self::TypeAliasType,
|
"TypeAliasType" => Self::TypeAliasType,
|
||||||
|
"TypeVar" => Self::TypeVar,
|
||||||
"ChainMap" => Self::ChainMap,
|
"ChainMap" => Self::ChainMap,
|
||||||
"Counter" => Self::Counter,
|
"Counter" => Self::Counter,
|
||||||
"defaultdict" => Self::DefaultDict,
|
"defaultdict" => Self::DefaultDict,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue