mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-01 17:32:25 +00:00
[red-knot] Add a new Type::KnownInstanceType
variant (#14155)
## Summary Fixes #14114. I don't think I can really describe the problems with our current architecture (and therefore the motivations for this PR) any better than @carljm did in that issue, so I'll just copy it out here! --- We currently represent "known instances" (e.g. special forms like `typing.Literal`, which are an instance of `typing._SpecialForm`, but need to be handled differently from other instances of `typing._SpecialForm`) as an `InstanceType` with a `known` field that is `Some(...)`. This makes it easy to handle a known instance as if it were a regular instance type (by ignoring the `known` field), and in some cases (e.g. `Type::member`) that is correct and convenient. But in other cases (e.g. `Type::is_equivalent_to`) it is not correct, and we currently have a bug that we would consider the known-instance type of `typing.Literal` as equivalent to the general instance type for `typing._SpecialForm`, and we would fail to consider it a singleton type or a single-valued type (even though it is both.) An instance type with `known.is_some()` is semantically quite different from an instance type with `known.is_none()`. The former is a singleton type that represents exactly one runtime object; the latter is an open type that represents many runtime objects, including instances of unknown subclasses. It is too error-prone to represent these very-different types as a single `Type` variant. We should instead introduce a dedicated `Type::KnownInstance` variant and force ourselves to handle these explicitly in all `Type` variant matches. ## Possible followups There is still a little bit of awkwardness in our current design in some places, in that we first infer the symbol `typing.Literal` as a `_SpecialForm` instance, and then later convert that instance-type into a known-instance-type. We could also use this `KnownInstanceType` enum to account for other special runtime symbols such as `builtins.Ellipsis` or `builtins.NotImplemented`. I think these might be worth pursuing, but I didn't do them here as they didn't seem essential right now, and I wanted to keep the diff relatively minimal. ## Test Plan `cargo test -p red_knot_python_semantic`. New unit tests added for `Type::is_subtype_of`. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
5b6169b02d
commit
4b08d17088
6 changed files with 162 additions and 150 deletions
|
@ -335,6 +335,8 @@ pub enum Type<'db> {
|
|||
SubclassOf(SubclassOfType<'db>),
|
||||
/// The set of Python objects with the given class in their __class__'s method resolution order
|
||||
Instance(InstanceType<'db>),
|
||||
/// A single Python object that requires special treatment in the type system
|
||||
KnownInstance(KnownInstanceType),
|
||||
/// The set of objects in any of the types in the union
|
||||
Union(UnionType<'db>),
|
||||
/// The set of objects in all of the types in the intersection
|
||||
|
@ -490,22 +492,22 @@ impl<'db> Type<'db> {
|
|||
(_, Type::Unknown | Type::Any | Type::Todo) => false,
|
||||
(Type::Never, _) => true,
|
||||
(_, Type::Never) => false,
|
||||
(_, Type::Instance(InstanceType { class, .. }))
|
||||
(_, Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Object) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::Instance(InstanceType { class, .. }), _)
|
||||
(Type::Instance(InstanceType { class }), _)
|
||||
if class.is_known(db, KnownClass::Object) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
(Type::BooleanLiteral(_), Type::Instance(InstanceType { class, .. }))
|
||||
(Type::BooleanLiteral(_), Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Bool) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::IntLiteral(_), Type::Instance(InstanceType { class, .. }))
|
||||
(Type::IntLiteral(_), Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Int) =>
|
||||
{
|
||||
true
|
||||
|
@ -513,9 +515,9 @@ impl<'db> Type<'db> {
|
|||
(Type::StringLiteral(_), Type::LiteralString) => true,
|
||||
(
|
||||
Type::StringLiteral(_) | Type::LiteralString,
|
||||
Type::Instance(InstanceType { class, .. }),
|
||||
Type::Instance(InstanceType { class }),
|
||||
) if class.is_known(db, KnownClass::Str) => true,
|
||||
(Type::BytesLiteral(_), Type::Instance(InstanceType { class, .. }))
|
||||
(Type::BytesLiteral(_), Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Bytes) =>
|
||||
{
|
||||
true
|
||||
|
@ -530,7 +532,7 @@ impl<'db> Type<'db> {
|
|||
},
|
||||
)
|
||||
}
|
||||
(Type::ClassLiteral(..), Type::Instance(InstanceType { class, .. }))
|
||||
(Type::ClassLiteral(..), Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Type) =>
|
||||
{
|
||||
true
|
||||
|
@ -545,7 +547,6 @@ impl<'db> Type<'db> {
|
|||
Type::SubclassOf(SubclassOfType { class: self_class }),
|
||||
Type::Instance(InstanceType {
|
||||
class: target_class,
|
||||
..
|
||||
}),
|
||||
) if self_class
|
||||
.metaclass(db)
|
||||
|
@ -603,15 +604,13 @@ impl<'db> Type<'db> {
|
|||
.iter()
|
||||
.all(|&neg_ty| neg_ty.is_disjoint_from(db, ty))
|
||||
}
|
||||
(
|
||||
Type::Instance(InstanceType {
|
||||
class: self_class, ..
|
||||
}),
|
||||
Type::Instance(InstanceType {
|
||||
class: target_class,
|
||||
..
|
||||
}),
|
||||
) => self_class.is_subclass_of(db, target_class),
|
||||
(Type::KnownInstance(left), right) => {
|
||||
left.instance_fallback(db).is_subtype_of(db, right)
|
||||
}
|
||||
(left, Type::KnownInstance(right)) => {
|
||||
left.is_subtype_of(db, right.instance_fallback(db))
|
||||
}
|
||||
(Type::Instance(left), Type::Instance(right)) => left.is_instance_of(db, right.class),
|
||||
// TODO
|
||||
_ => false,
|
||||
}
|
||||
|
@ -660,8 +659,8 @@ impl<'db> Type<'db> {
|
|||
self == other
|
||||
|| matches!((self, other),
|
||||
(
|
||||
Type::Instance(InstanceType { class: self_class, .. }),
|
||||
Type::Instance(InstanceType { class: target_class, .. })
|
||||
Type::Instance(InstanceType { class: self_class }),
|
||||
Type::Instance(InstanceType { class: target_class })
|
||||
)
|
||||
if self_class.is_known(db, KnownClass::NoneType) &&
|
||||
target_class.is_known(db, KnownClass::NoneType))
|
||||
|
@ -753,75 +752,68 @@ impl<'db> Type<'db> {
|
|||
// final classes inside `Type::SubclassOf` everywhere.
|
||||
false
|
||||
}
|
||||
(Type::KnownInstance(left), Type::KnownInstance(right)) => left != right,
|
||||
(Type::KnownInstance(left), right) => {
|
||||
left.instance_fallback(db).is_disjoint_from(db, right)
|
||||
}
|
||||
(left, Type::KnownInstance(right)) => {
|
||||
left.is_disjoint_from(db, right.instance_fallback(db))
|
||||
}
|
||||
(
|
||||
Type::Instance(InstanceType {
|
||||
class: class_none, ..
|
||||
}),
|
||||
Type::Instance(InstanceType {
|
||||
class: class_other, ..
|
||||
}),
|
||||
Type::Instance(InstanceType { class: class_none }),
|
||||
Type::Instance(InstanceType { class: class_other }),
|
||||
)
|
||||
| (
|
||||
Type::Instance(InstanceType {
|
||||
class: class_other, ..
|
||||
}),
|
||||
Type::Instance(InstanceType {
|
||||
class: class_none, ..
|
||||
}),
|
||||
Type::Instance(InstanceType { class: class_other }),
|
||||
Type::Instance(InstanceType { class: class_none }),
|
||||
) if class_none.is_known(db, KnownClass::NoneType) => !matches!(
|
||||
class_other.known(db),
|
||||
Some(KnownClass::NoneType | KnownClass::Object)
|
||||
),
|
||||
(
|
||||
Type::Instance(InstanceType {
|
||||
class: class_none, ..
|
||||
}),
|
||||
_,
|
||||
)
|
||||
| (
|
||||
_,
|
||||
Type::Instance(InstanceType {
|
||||
class: class_none, ..
|
||||
}),
|
||||
) if class_none.is_known(db, KnownClass::NoneType) => true,
|
||||
(Type::Instance(InstanceType { class: class_none }), _)
|
||||
| (_, Type::Instance(InstanceType { class: class_none }))
|
||||
if class_none.is_known(db, KnownClass::NoneType) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
|
||||
(Type::BooleanLiteral(..), Type::Instance(InstanceType { class, .. }))
|
||||
| (Type::Instance(InstanceType { class, .. }), Type::BooleanLiteral(..)) => !matches!(
|
||||
(Type::BooleanLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::BooleanLiteral(..)) => !matches!(
|
||||
class.known(db),
|
||||
Some(KnownClass::Bool | KnownClass::Int | KnownClass::Object)
|
||||
),
|
||||
(Type::BooleanLiteral(..), _) | (_, Type::BooleanLiteral(..)) => true,
|
||||
|
||||
(Type::IntLiteral(..), Type::Instance(InstanceType { class, .. }))
|
||||
| (Type::Instance(InstanceType { class, .. }), Type::IntLiteral(..)) => {
|
||||
(Type::IntLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::IntLiteral(..)) => {
|
||||
!matches!(class.known(db), Some(KnownClass::Int | KnownClass::Object))
|
||||
}
|
||||
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => true,
|
||||
|
||||
(Type::StringLiteral(..), Type::LiteralString)
|
||||
| (Type::LiteralString, Type::StringLiteral(..)) => false,
|
||||
(Type::StringLiteral(..), Type::Instance(InstanceType { class, .. }))
|
||||
| (Type::Instance(InstanceType { class, .. }), Type::StringLiteral(..)) => {
|
||||
(Type::StringLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::StringLiteral(..)) => {
|
||||
!matches!(class.known(db), Some(KnownClass::Str | KnownClass::Object))
|
||||
}
|
||||
(Type::StringLiteral(..), _) | (_, Type::StringLiteral(..)) => true,
|
||||
|
||||
(Type::LiteralString, Type::LiteralString) => false,
|
||||
(Type::LiteralString, Type::Instance(InstanceType { class, .. }))
|
||||
| (Type::Instance(InstanceType { class, .. }), Type::LiteralString) => {
|
||||
(Type::LiteralString, Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::LiteralString) => {
|
||||
!matches!(class.known(db), Some(KnownClass::Str | KnownClass::Object))
|
||||
}
|
||||
(Type::LiteralString, _) | (_, Type::LiteralString) => true,
|
||||
|
||||
(Type::BytesLiteral(..), Type::Instance(InstanceType { class, .. }))
|
||||
| (Type::Instance(InstanceType { class, .. }), Type::BytesLiteral(..)) => !matches!(
|
||||
(Type::BytesLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::BytesLiteral(..)) => !matches!(
|
||||
class.known(db),
|
||||
Some(KnownClass::Bytes | KnownClass::Object)
|
||||
),
|
||||
(Type::BytesLiteral(..), _) | (_, Type::BytesLiteral(..)) => true,
|
||||
|
||||
(Type::SliceLiteral(..), Type::Instance(InstanceType { class, .. }))
|
||||
| (Type::Instance(InstanceType { class, .. }), Type::SliceLiteral(..)) => !matches!(
|
||||
(Type::SliceLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::SliceLiteral(..)) => !matches!(
|
||||
class.known(db),
|
||||
Some(KnownClass::Slice | KnownClass::Object)
|
||||
),
|
||||
|
@ -829,10 +821,10 @@ impl<'db> Type<'db> {
|
|||
|
||||
(
|
||||
Type::FunctionLiteral(..) | Type::ModuleLiteral(..) | Type::ClassLiteral(..),
|
||||
Type::Instance(InstanceType { class, .. }),
|
||||
Type::Instance(InstanceType { class }),
|
||||
)
|
||||
| (
|
||||
Type::Instance(InstanceType { class, .. }),
|
||||
Type::Instance(InstanceType { class }),
|
||||
Type::FunctionLiteral(..) | Type::ModuleLiteral(..) | Type::ClassLiteral(..),
|
||||
) => !class.is_known(db, KnownClass::Object),
|
||||
|
||||
|
@ -902,8 +894,9 @@ impl<'db> Type<'db> {
|
|||
Type::BooleanLiteral(_)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::ModuleLiteral(..) => true,
|
||||
Type::Instance(InstanceType { class, .. }) => {
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::KnownInstance(..) => true,
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
// TODO some more instance types can be singleton types (EllipsisType, NotImplementedType)
|
||||
matches!(class.known(db), Some(KnownClass::NoneType))
|
||||
}
|
||||
|
@ -944,7 +937,8 @@ impl<'db> Type<'db> {
|
|||
| Type::BooleanLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
| Type::BytesLiteral(..)
|
||||
| Type::SliceLiteral(..) => true,
|
||||
| Type::SliceLiteral(..)
|
||||
| Type::KnownInstance(..) => true,
|
||||
|
||||
Type::SubclassOf(..) => {
|
||||
// TODO: Same comment as above for `is_singleton`
|
||||
|
@ -956,7 +950,7 @@ impl<'db> Type<'db> {
|
|||
.iter()
|
||||
.all(|elem| elem.is_single_valued(db)),
|
||||
|
||||
Type::Instance(InstanceType { class, .. }) => match class.known(db) {
|
||||
Type::Instance(InstanceType { class }) => match class.known(db) {
|
||||
Some(KnownClass::NoneType) => true,
|
||||
Some(
|
||||
KnownClass::Bool
|
||||
|
@ -1045,6 +1039,9 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
Type::ClassLiteral(class_ty) => class_ty.member(db, name),
|
||||
Type::SubclassOf(subclass_of_ty) => subclass_of_ty.member(db, name),
|
||||
Type::KnownInstance(known_instance) => {
|
||||
known_instance.instance_fallback(db).member(db, name)
|
||||
}
|
||||
Type::Instance(_) => {
|
||||
// TODO MRO? get_own_instance_member, get_instance_member
|
||||
Type::Todo.into()
|
||||
|
@ -1139,7 +1136,7 @@ impl<'db> Type<'db> {
|
|||
// TODO: see above
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
Type::Instance(InstanceType { class, .. }) => {
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
// TODO: lookup `__bool__` and `__len__` methods on the instance's class
|
||||
// More info in https://docs.python.org/3/library/stdtypes.html#truth-value-testing
|
||||
// For now, we only special-case some builtin classes
|
||||
|
@ -1149,6 +1146,7 @@ impl<'db> Type<'db> {
|
|||
Truthiness::Ambiguous
|
||||
}
|
||||
}
|
||||
Type::KnownInstance(known_instance) => known_instance.bool(),
|
||||
Type::Union(union) => {
|
||||
let union_elements = union.elements(db);
|
||||
let first_element_truthiness = union_elements[0].bool(db);
|
||||
|
@ -1204,11 +1202,11 @@ impl<'db> Type<'db> {
|
|||
.first()
|
||||
.map(|arg| arg.bool(db).into_type(db))
|
||||
.unwrap_or(Type::BooleanLiteral(false)),
|
||||
_ => Type::anonymous_instance(class),
|
||||
_ => Type::Instance(InstanceType { class }),
|
||||
})
|
||||
}
|
||||
|
||||
instance_ty @ Type::Instance(InstanceType { .. }) => {
|
||||
instance_ty @ Type::Instance(_) => {
|
||||
let args = std::iter::once(self)
|
||||
.chain(arg_types.iter().copied())
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -1355,8 +1353,12 @@ impl<'db> Type<'db> {
|
|||
Type::Todo => Type::Todo,
|
||||
Type::Unknown => Type::Unknown,
|
||||
Type::Never => Type::Never,
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::anonymous_instance(*class),
|
||||
Type::SubclassOf(SubclassOfType { class }) => Type::anonymous_instance(*class),
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
Type::Instance(InstanceType { class: *class })
|
||||
}
|
||||
Type::SubclassOf(SubclassOfType { class }) => {
|
||||
Type::Instance(InstanceType { class: *class })
|
||||
}
|
||||
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
|
||||
// TODO: we can probably do better here: --Alex
|
||||
Type::Intersection(_) => Type::Todo,
|
||||
|
@ -1366,6 +1368,7 @@ impl<'db> Type<'db> {
|
|||
| Type::BytesLiteral(_)
|
||||
| Type::FunctionLiteral(_)
|
||||
| Type::Instance(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::IntLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
|
@ -1375,10 +1378,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn anonymous_instance(class: Class<'db>) -> Self {
|
||||
Self::Instance(InstanceType::anonymous(class))
|
||||
}
|
||||
|
||||
/// The type `NoneType` / `None`
|
||||
pub fn none(db: &'db dyn Db) -> Type<'db> {
|
||||
KnownClass::NoneType.to_instance(db)
|
||||
|
@ -1390,9 +1389,10 @@ impl<'db> Type<'db> {
|
|||
pub fn to_meta_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Type::Never => Type::Never,
|
||||
Type::Instance(InstanceType { class, .. }) => {
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
Type::SubclassOf(SubclassOfType { class: *class })
|
||||
}
|
||||
Type::KnownInstance(known_instance) => known_instance.class().to_class(db),
|
||||
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
||||
Type::BooleanLiteral(_) => KnownClass::Bool.to_class(db),
|
||||
Type::BytesLiteral(_) => KnownClass::Bytes.to_class(db),
|
||||
|
@ -1431,6 +1431,9 @@ impl<'db> Type<'db> {
|
|||
match self {
|
||||
Type::IntLiteral(_) | Type::BooleanLiteral(_) => self.repr(db),
|
||||
Type::StringLiteral(_) | Type::LiteralString => *self,
|
||||
Type::KnownInstance(known_instance) => {
|
||||
Type::StringLiteral(StringLiteralType::new(db, known_instance.repr()))
|
||||
}
|
||||
// TODO: handle more complex types
|
||||
_ => KnownClass::Str.to_instance(db),
|
||||
}
|
||||
|
@ -1450,6 +1453,9 @@ impl<'db> Type<'db> {
|
|||
format!("'{}'", literal.value(db).escape_default()).into_boxed_str()
|
||||
})),
|
||||
Type::LiteralString => Type::LiteralString,
|
||||
Type::KnownInstance(known_instance) => {
|
||||
Type::StringLiteral(StringLiteralType::new(db, known_instance.repr()))
|
||||
}
|
||||
// TODO: handle more complex types
|
||||
_ => KnownClass::Str.to_instance(db),
|
||||
}
|
||||
|
@ -1612,39 +1618,58 @@ impl<'db> KnownClass {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enumeration of specific runtime that are special enough to be considered their own type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum KnownInstance {
|
||||
pub enum KnownInstanceType {
|
||||
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
|
||||
Literal,
|
||||
// TODO: fill this enum out with more special forms, etc.
|
||||
}
|
||||
|
||||
impl KnownInstance {
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
impl KnownInstanceType {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
KnownInstance::Literal => "Literal",
|
||||
KnownInstanceType::Literal => "Literal",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_from_module(module: &Module, instance_name: &str) -> Option<Self> {
|
||||
let candidate = Self::from_name(instance_name)?;
|
||||
candidate.check_module(module).then_some(candidate)
|
||||
}
|
||||
|
||||
fn from_name(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
"Literal" => Some(Self::Literal),
|
||||
_ => None,
|
||||
/// Evaluate the known instance in boolean context
|
||||
pub const fn bool(self) -> Truthiness {
|
||||
match self {
|
||||
Self::Literal => Truthiness::AlwaysTrue,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_module(self, module: &Module) -> bool {
|
||||
/// Return the repr of the symbol at runtime
|
||||
pub const fn repr(self) -> &'static str {
|
||||
match self {
|
||||
Self::Literal => "typing.Literal",
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`KnownClass`] which this symbol is an instance of
|
||||
pub const fn class(self) -> KnownClass {
|
||||
match self {
|
||||
Self::Literal => KnownClass::SpecialForm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the instance type which this type is a subtype of.
|
||||
///
|
||||
/// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`,
|
||||
/// so `KnownInstanceType::Literal.instance_fallback(db)`
|
||||
/// returns `Type::Instance(InstanceType { class: <typing._SpecialForm> })`.
|
||||
pub fn instance_fallback(self, db: &dyn Db) -> Type {
|
||||
self.class().to_instance(db)
|
||||
}
|
||||
|
||||
pub fn try_from_module_and_symbol(module: &Module, instance_name: &str) -> Option<Self> {
|
||||
if !module.search_path().is_standard_library() {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
match self {
|
||||
Self::Literal => {
|
||||
matches!(module.name().as_str(), "typing" | "typing_extensions")
|
||||
}
|
||||
match (module.name().as_str(), instance_name) {
|
||||
("typing" | "typing_extensions", "Literal") => Some(Self::Literal),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1993,7 +2018,7 @@ impl<'db> IterationOutcome<'db> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum Truthiness {
|
||||
pub enum Truthiness {
|
||||
/// For an object `x`, `bool(x)` will always return `True`
|
||||
AlwaysTrue,
|
||||
/// For an object `x`, `bool(x)` will always return `False`
|
||||
|
@ -2475,37 +2500,12 @@ impl<'db> SubclassOfType<'db> {
|
|||
}
|
||||
|
||||
/// A type representing the set of runtime objects which are instances of a certain class.
|
||||
///
|
||||
/// Some specific instances of some types need to be treated specially by the type system:
|
||||
/// for example, various special forms are instances of `typing._SpecialForm`,
|
||||
/// but need to be handled differently in annotations. These special instances are marked as such
|
||||
/// using the `known` field on this struct.
|
||||
///
|
||||
/// Note that, for example, `InstanceType { class: typing._SpecialForm, known: None }`
|
||||
/// is a supertype of `InstanceType { class: typing._SpecialForm, known: KnownInstance::Literal }`.
|
||||
/// The two types are not disjoint.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct InstanceType<'db> {
|
||||
class: Class<'db>,
|
||||
known: Option<KnownInstance>,
|
||||
}
|
||||
|
||||
impl<'db> InstanceType<'db> {
|
||||
pub fn anonymous(class: Class<'db>) -> Self {
|
||||
Self { class, known: None }
|
||||
}
|
||||
|
||||
pub fn known(class: Class<'db>, known: KnownInstance) -> Self {
|
||||
Self {
|
||||
class,
|
||||
known: Some(known),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_known(&self, known_instance: KnownInstance) -> bool {
|
||||
self.known == Some(known_instance)
|
||||
}
|
||||
|
||||
/// Return `true` if members of this type are instances of the class `class` at runtime.
|
||||
pub fn is_instance_of(self, db: &'db dyn Db, class: Class<'db>) -> bool {
|
||||
self.class.is_subclass_of(db, class)
|
||||
|
@ -2703,6 +2703,8 @@ mod tests {
|
|||
BytesLiteral(&'static str),
|
||||
// BuiltinInstance("str") corresponds to an instance of the builtin `str` class
|
||||
BuiltinInstance(&'static str),
|
||||
TypingInstance(&'static str),
|
||||
KnownInstance(KnownInstanceType),
|
||||
// BuiltinClassLiteral("str") corresponds to the builtin `str` class object itself
|
||||
BuiltinClassLiteral(&'static str),
|
||||
Union(Vec<Ty>),
|
||||
|
@ -2724,6 +2726,8 @@ mod tests {
|
|||
Ty::LiteralString => Type::LiteralString,
|
||||
Ty::BytesLiteral(s) => Type::BytesLiteral(BytesLiteralType::new(db, s.as_bytes())),
|
||||
Ty::BuiltinInstance(s) => builtins_symbol(db, s).expect_type().to_instance(db),
|
||||
Ty::TypingInstance(s) => typing_symbol(db, s).expect_type().to_instance(db),
|
||||
Ty::KnownInstance(known_instance) => Type::KnownInstance(known_instance),
|
||||
Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).expect_type(),
|
||||
Ty::Union(tys) => {
|
||||
UnionType::from_elements(db, tys.into_iter().map(|ty| ty.into_type(db)))
|
||||
|
@ -2813,6 +2817,14 @@ mod tests {
|
|||
#[test_case(Ty::Intersection{pos: vec![Ty::BuiltinInstance("str")], neg: vec![Ty::StringLiteral("foo")]}, Ty::Intersection{pos: vec![], neg: vec![Ty::IntLiteral(2)]})]
|
||||
#[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinClassLiteral("int"))]
|
||||
#[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinInstance("object"))]
|
||||
#[test_case(
|
||||
Ty::KnownInstance(KnownInstanceType::Literal),
|
||||
Ty::TypingInstance("_SpecialForm")
|
||||
)]
|
||||
#[test_case(
|
||||
Ty::KnownInstance(KnownInstanceType::Literal),
|
||||
Ty::BuiltinInstance("object")
|
||||
)]
|
||||
fn is_subtype_of(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||
|
|
|
@ -246,7 +246,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
}
|
||||
} else {
|
||||
// ~Literal[True] & bool = Literal[False]
|
||||
if let Type::Instance(InstanceType { class, .. }) = new_positive {
|
||||
if let Type::Instance(InstanceType { class }) = new_positive {
|
||||
if class.is_known(db, KnownClass::Bool) {
|
||||
if let Some(&Type::BooleanLiteral(value)) = self
|
||||
.negative
|
||||
|
|
|
@ -66,7 +66,7 @@ impl Display for DisplayRepresentation<'_> {
|
|||
Type::Any => f.write_str("Any"),
|
||||
Type::Never => f.write_str("Never"),
|
||||
Type::Unknown => f.write_str("Unknown"),
|
||||
Type::Instance(InstanceType { class, .. })
|
||||
Type::Instance(InstanceType { class })
|
||||
if class.is_known(self.db, KnownClass::NoneType) =>
|
||||
{
|
||||
f.write_str("None")
|
||||
|
@ -82,10 +82,8 @@ impl Display for DisplayRepresentation<'_> {
|
|||
Type::SubclassOf(SubclassOfType { class }) => {
|
||||
write!(f, "type[{}]", class.name(self.db))
|
||||
}
|
||||
Type::Instance(InstanceType { class, known }) => f.write_str(match known {
|
||||
Some(super::KnownInstance::Literal) => "Literal",
|
||||
_ => class.name(self.db),
|
||||
}),
|
||||
Type::Instance(InstanceType { class }) => f.write_str(class.name(self.db)),
|
||||
Type::KnownInstance(known_instance) => f.write_str(known_instance.as_str()),
|
||||
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
|
||||
Type::Union(union) => union.display(self.db).fmt(f),
|
||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||
|
|
|
@ -58,7 +58,7 @@ use crate::types::{
|
|||
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, typing_extensions_symbol,
|
||||
Boundness, BytesLiteralType, Class, ClassLiteralType, FunctionType, InstanceType,
|
||||
IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction,
|
||||
KnownInstance, MetaclassErrorKind, SliceLiteralType, StringLiteralType, Symbol, Truthiness,
|
||||
KnownInstanceType, MetaclassErrorKind, SliceLiteralType, StringLiteralType, Symbol, Truthiness,
|
||||
TupleType, Type, TypeArrayDisplay, UnionBuilder, UnionType,
|
||||
};
|
||||
use crate::unpack::Unpack;
|
||||
|
@ -634,7 +634,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
fn check_division_by_zero(&mut self, expr: &ast::ExprBinOp, left: Type<'db>) {
|
||||
match left {
|
||||
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
|
||||
Type::Instance(InstanceType { class, .. })
|
||||
Type::Instance(InstanceType { class })
|
||||
if matches!(
|
||||
class.known(self.db),
|
||||
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
|
||||
|
@ -1297,13 +1297,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
// anything else is invalid and should lead to a diagnostic being reported --Alex
|
||||
match node_ty {
|
||||
Type::Any | Type::Unknown => node_ty,
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::anonymous_instance(class),
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
Type::Instance(InstanceType { class })
|
||||
}
|
||||
Type::Tuple(tuple) => UnionType::from_elements(
|
||||
self.db,
|
||||
tuple.elements(self.db).iter().map(|ty| {
|
||||
ty.into_class_literal()
|
||||
.map_or(Type::Todo, |ClassLiteralType { class }| {
|
||||
Type::anonymous_instance(class)
|
||||
Type::Instance(InstanceType { class })
|
||||
})
|
||||
}),
|
||||
),
|
||||
|
@ -1503,14 +1505,16 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
// If the declared variable is annotated with _SpecialForm class then we treat it differently
|
||||
// by assigning the known field to the instance.
|
||||
if let Type::Instance(InstanceType { class, .. }) = annotation_ty {
|
||||
if let Type::Instance(InstanceType { class }) = annotation_ty {
|
||||
if class.is_known(self.db, KnownClass::SpecialForm) {
|
||||
if let Some(name_expr) = target.as_name_expr() {
|
||||
let maybe_known_instance = file_to_module(self.db, self.file)
|
||||
if let Some(known_instance) = file_to_module(self.db, self.file)
|
||||
.as_ref()
|
||||
.and_then(|module| KnownInstance::maybe_from_module(module, &name_expr.id));
|
||||
if let Some(known_instance) = maybe_known_instance {
|
||||
annotation_ty = Type::Instance(InstanceType::known(class, known_instance));
|
||||
.and_then(|module| {
|
||||
KnownInstanceType::try_from_module_and_symbol(module, &name_expr.id)
|
||||
})
|
||||
{
|
||||
annotation_ty = Type::KnownInstance(known_instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1555,7 +1559,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
self.infer_augmented_op(assignment, target_type, value_type)
|
||||
})
|
||||
}
|
||||
Type::Instance(InstanceType { class, .. }) => {
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
if let Symbol::Type(class_member, boundness) =
|
||||
class.class_member(self.db, op.in_place_dunder())
|
||||
{
|
||||
|
@ -2717,7 +2721,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
(_, Type::Unknown) => Type::Unknown,
|
||||
(
|
||||
op @ (UnaryOp::UAdd | UnaryOp::USub | UnaryOp::Invert),
|
||||
Type::Instance(InstanceType { class, .. }),
|
||||
Type::Instance(InstanceType { class }),
|
||||
) => {
|
||||
let unary_dunder_method = match op {
|
||||
UnaryOp::Invert => "__invert__",
|
||||
|
@ -3848,7 +3852,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Err(_) => SliceArg::Unsupported,
|
||||
},
|
||||
Some(Type::BooleanLiteral(b)) => SliceArg::Arg(Some(i32::from(b))),
|
||||
Some(Type::Instance(InstanceType { class, .. }))
|
||||
Some(Type::Instance(InstanceType { class }))
|
||||
if class.is_known(self.db, KnownClass::NoneType) =>
|
||||
{
|
||||
SliceArg::Arg(None)
|
||||
|
@ -4194,10 +4198,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
} = subscript;
|
||||
|
||||
match value_ty {
|
||||
Type::Instance(InstanceType {
|
||||
class: _,
|
||||
known: Some(known_instance),
|
||||
}) => self.infer_parameterized_known_instance_type_expression(known_instance, slice),
|
||||
Type::KnownInstance(known_instance) => {
|
||||
self.infer_parameterized_known_instance_type_expression(known_instance, slice)
|
||||
}
|
||||
_ => {
|
||||
self.infer_type_expression(slice);
|
||||
Type::Todo // TODO: generics
|
||||
|
@ -4207,11 +4210,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
fn infer_parameterized_known_instance_type_expression(
|
||||
&mut self,
|
||||
known_instance: KnownInstance,
|
||||
known_instance: KnownInstanceType,
|
||||
parameters: &ast::Expr,
|
||||
) -> Type<'db> {
|
||||
match known_instance {
|
||||
KnownInstance::Literal => match self.infer_literal_parameter_type(parameters) {
|
||||
KnownInstanceType::Literal => match self.infer_literal_parameter_type(parameters) {
|
||||
Ok(ty) => ty,
|
||||
Err(nodes) => {
|
||||
for node in nodes {
|
||||
|
@ -4238,13 +4241,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
// TODO handle type aliases
|
||||
ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
let value_ty = self.infer_expression(value);
|
||||
if matches!(
|
||||
value_ty,
|
||||
Type::Instance(InstanceType {
|
||||
known: Some(KnownInstance::Literal),
|
||||
..
|
||||
})
|
||||
) {
|
||||
if matches!(value_ty, Type::KnownInstance(KnownInstanceType::Literal)) {
|
||||
self.infer_literal_parameter_type(slice)?
|
||||
} else {
|
||||
return Err(vec![parameters]);
|
||||
|
|
|
@ -5,7 +5,7 @@ use indexmap::IndexSet;
|
|||
use itertools::Either;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use super::{Class, ClassLiteralType, KnownClass, Type};
|
||||
use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, Type};
|
||||
use crate::Db;
|
||||
|
||||
/// The inferred method resolution order of a given class.
|
||||
|
@ -379,6 +379,9 @@ impl<'db> ClassBase<'db> {
|
|||
| Type::SliceLiteral(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::SubclassOf(_) => None,
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
KnownInstanceType::Literal => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::semantic_index::expression::Expression;
|
|||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||
use crate::semantic_index::symbol_table;
|
||||
use crate::types::{
|
||||
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass,
|
||||
infer_expression_types, ClassLiteralType, InstanceType, IntersectionBuilder, KnownClass,
|
||||
KnownConstraintFunction, KnownFunction, Truthiness, Type, UnionBuilder,
|
||||
};
|
||||
use crate::Db;
|
||||
|
@ -353,7 +353,9 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
|||
let to_constraint = match function {
|
||||
KnownConstraintFunction::IsInstance => {
|
||||
|class_literal: ClassLiteralType<'db>| {
|
||||
Type::anonymous_instance(class_literal.class)
|
||||
Type::Instance(InstanceType {
|
||||
class: class_literal.class,
|
||||
})
|
||||
}
|
||||
}
|
||||
KnownConstraintFunction::IsSubclass => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue