[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:
Alex Waygood 2024-11-07 22:07:27 +00:00 committed by GitHub
parent 5b6169b02d
commit 4b08d17088
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 162 additions and 150 deletions

View file

@ -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)));

View file

@ -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

View file

@ -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),

View file

@ -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]);

View file

@ -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,
},
}
}

View file

@ -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 => {