Skip to content

Commit

Permalink
[red-knot] Generalise special-casing for KnownClasses in `Type::boo…
Browse files Browse the repository at this point in the history
…l` (#16300)
  • Loading branch information
AlexWaygood authored Feb 21, 2025
1 parent 5fab97f commit 5347abc
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,32 @@ class PossiblyUnboundTrue:

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))
```
158 changes: 106 additions & 52 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1351,51 +1351,9 @@ impl<'db> Type<'db> {
.iter()
.all(|elem| elem.is_single_valued(db)),

Type::Instance(InstanceType { class }) => match class.known(db) {
Some(
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::Instance(InstanceType { class }) => {
class.known(db).is_some_and(KnownClass::is_single_valued)
}

Type::Dynamic(_)
| Type::Never
Expand Down Expand Up @@ -1741,12 +1699,9 @@ impl<'db> Type<'db> {
},
Type::AlwaysTruthy => Truthiness::AlwaysTrue,
Type::AlwaysFalsy => Truthiness::AlwaysFalse,
instance_ty @ Type::Instance(InstanceType { class }) => {
if class.is_known(db, KnownClass::Bool) {
Truthiness::Ambiguous
} else if class.is_known(db, KnownClass::NoneType) {
Truthiness::AlwaysFalse
} else {
instance_ty @ Type::Instance(InstanceType { class }) => match class.known(db) {
Some(known_class) => known_class.bool(),
None => {
// We only check the `__bool__` method for truth testing, even though at
// runtime there is a fallback to `__len__`, since `__bool__` takes precedence
// and a subclass could add a `__bool__` method.
Expand Down Expand Up @@ -1824,7 +1779,7 @@ impl<'db> Type<'db> {
}
}
}
}
},
Type::KnownInstance(known_instance) => known_instance.bool(),
Type::Union(union) => {
let mut truthiness = None;
Expand Down Expand Up @@ -2928,6 +2883,59 @@ impl<'db> KnownClass {
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 {
match self {
Self::Bool => "bool",
Expand Down Expand Up @@ -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?
///
/// A singleton class is a class where it is known that only one instance can ever exist at runtime.
Expand Down Expand Up @@ -3152,6 +3205,7 @@ impl<'db> KnownClass {
"MethodWrapperType" => Self::MethodWrapperType,
"WrapperDescriptorType" => Self::WrapperDescriptorType,
"TypeAliasType" => Self::TypeAliasType,
"TypeVar" => Self::TypeVar,
"ChainMap" => Self::ChainMap,
"Counter" => Self::Counter,
"defaultdict" => Self::DefaultDict,
Expand Down

0 comments on commit 5347abc

Please sign in to comment.