A new Type variant
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use instance::{Protocol, ProtocolInstanceType};
|
||||
use itertools::Either;
|
||||
|
||||
use std::slice::Iter;
|
||||
@@ -476,6 +477,8 @@ pub enum Type<'db> {
|
||||
/// The set of Python objects with the given class in their __class__'s method resolution order.
|
||||
/// Construct this variant using the `Type::instance` constructor function.
|
||||
NominalInstance(NominalInstanceType<'db>),
|
||||
/// The set of Python objects that conform to the interface described by a given protocol.
|
||||
ProtocolInstance(ProtocolInstanceType<'db>),
|
||||
/// A single Python object that requires special treatment in the type system
|
||||
KnownInstance(KnownInstanceType<'db>),
|
||||
/// An instance of `builtins.property`
|
||||
@@ -543,17 +546,17 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
fn is_none(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_instance()
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class().is_known(db, KnownClass::NoneType))
|
||||
}
|
||||
|
||||
fn is_bool(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_instance()
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class().is_known(db, KnownClass::Bool))
|
||||
}
|
||||
|
||||
pub fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_instance().is_some_and(|instance| {
|
||||
self.into_nominal_instance().is_some_and(|instance| {
|
||||
instance
|
||||
.class()
|
||||
.is_known(db, KnownClass::NotImplementedType)
|
||||
@@ -561,7 +564,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
pub fn is_object(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_instance()
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class().is_object(db))
|
||||
}
|
||||
|
||||
@@ -667,6 +670,8 @@ impl<'db> Type<'db> {
|
||||
.iter()
|
||||
.any(|ty| ty.contains_todo(db))
|
||||
}
|
||||
|
||||
Self::ProtocolInstance(protocol) => protocol.contains_todo(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,6 +892,7 @@ impl<'db> Type<'db> {
|
||||
Type::Intersection(intersection) => Type::Intersection(intersection.normalized(db)),
|
||||
Type::Tuple(tuple) => Type::Tuple(tuple.normalized(db)),
|
||||
Type::Callable(callable) => Type::Callable(callable.normalized(db)),
|
||||
Type::ProtocolInstance(protocol) => protocol.normalized(db),
|
||||
Type::LiteralString
|
||||
| Type::NominalInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
@@ -1150,6 +1156,14 @@ impl<'db> Type<'db> {
|
||||
false
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
|
||||
left.is_subtype_of(db, right)
|
||||
}
|
||||
// A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`.
|
||||
// TODO: `Callable` types are also structural types.
|
||||
(Type::ProtocolInstance(_), _) => false,
|
||||
(_, Type::ProtocolInstance(protocol)) => self.satisfies_protocol(db, protocol),
|
||||
|
||||
// A fully static heterogeneous tuple type `A` is a subtype of a fully static heterogeneous tuple type `B`
|
||||
// iff the two tuple types have the same number of elements and each element-type in `A` is a subtype
|
||||
// of the element-type at the same index in `B`. (Now say that 5 times fast.)
|
||||
@@ -1499,6 +1513,16 @@ impl<'db> Type<'db> {
|
||||
.into_callable_type(db)
|
||||
.is_assignable_to(db, target),
|
||||
|
||||
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
|
||||
left.is_assignable_to(db, right)
|
||||
}
|
||||
// Other than the dynamic types such as `Any`/`Unknown`/`Todo` handled above,
|
||||
// a protocol instance can never be assignable to a nominal type,
|
||||
// with the *sole* exception of `object`.
|
||||
// TODO: `Callable` types are also structural types.
|
||||
(Type::ProtocolInstance(_), _) => false,
|
||||
(_, Type::ProtocolInstance(protocol)) => self.satisfies_protocol(db, protocol),
|
||||
|
||||
// TODO other types containing gradual forms
|
||||
_ => self.is_subtype_of(db, target),
|
||||
}
|
||||
@@ -1522,6 +1546,13 @@ impl<'db> Type<'db> {
|
||||
(Type::NominalInstance(left), Type::NominalInstance(right)) => {
|
||||
left.is_equivalent_to(db, right)
|
||||
}
|
||||
(Type::ProtocolInstance(first), Type::ProtocolInstance(right)) => {
|
||||
first.is_equivalent_to(db, right)
|
||||
}
|
||||
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
|
||||
n.class().is_object(db) && protocol.normalized(db) == nominal
|
||||
}
|
||||
_ => self == other && self.is_fully_static(db) && other.is_fully_static(db),
|
||||
}
|
||||
}
|
||||
@@ -1572,10 +1603,24 @@ impl<'db> Type<'db> {
|
||||
first.is_gradual_equivalent_to(db, second)
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(first), Type::ProtocolInstance(right)) => {
|
||||
first.is_gradual_equivalent_to(db, right)
|
||||
}
|
||||
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
|
||||
n.class().is_object(db) && protocol.normalized(db) == nominal
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn satisfies_protocol(self, db: &'db dyn Db, protocol: ProtocolInstanceType<'db>) -> bool {
|
||||
protocol
|
||||
.protocol_members(db)
|
||||
.iter()
|
||||
.all(|member| !self.member(db, member).symbol.is_unbound())
|
||||
}
|
||||
|
||||
/// Return true if this type and `other` have no common elements.
|
||||
///
|
||||
/// Note: This function aims to have no false positives, but might return
|
||||
@@ -1772,6 +1817,68 @@ impl<'db> Type<'db> {
|
||||
ty.bool(db).is_always_true()
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
|
||||
left.is_disjoint_from(db, right)
|
||||
}
|
||||
|
||||
// TODO: we could also consider `protocol` to be disjoint from `nominal` if `nominal`
|
||||
// has the right member but the type of its member is disjoint from the type of the
|
||||
// member on `protocol`.
|
||||
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
|
||||
n.class().is_final(db) && !nominal.satisfies_protocol(db, protocol)
|
||||
}
|
||||
|
||||
(
|
||||
ty @ (Type::LiteralString
|
||||
| Type::StringLiteral(..)
|
||||
| Type::BytesLiteral(..)
|
||||
| Type::BooleanLiteral(..)
|
||||
| Type::SliceLiteral(..)
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::GenericAlias(..)
|
||||
| Type::IntLiteral(..)),
|
||||
Type::ProtocolInstance(protocol),
|
||||
)
|
||||
| (
|
||||
Type::ProtocolInstance(protocol),
|
||||
ty @ (Type::LiteralString
|
||||
| Type::StringLiteral(..)
|
||||
| Type::BytesLiteral(..)
|
||||
| Type::BooleanLiteral(..)
|
||||
| Type::SliceLiteral(..)
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::GenericAlias(..)
|
||||
| Type::IntLiteral(..)),
|
||||
) => !ty.satisfies_protocol(db, protocol),
|
||||
|
||||
(Type::ProtocolInstance(protocol), Type::KnownInstance(known_instance))
|
||||
| (Type::KnownInstance(known_instance), Type::ProtocolInstance(protocol)) => {
|
||||
!known_instance
|
||||
.instance_fallback(db)
|
||||
.satisfies_protocol(db, protocol)
|
||||
}
|
||||
|
||||
(Type::Callable(_), Type::ProtocolInstance(_))
|
||||
| (Type::ProtocolInstance(_), Type::Callable(_)) => {
|
||||
// TODO disjointness between `Callable` and `ProtocolInstance`
|
||||
false
|
||||
}
|
||||
|
||||
(Type::Tuple(..), Type::ProtocolInstance(..))
|
||||
| (Type::ProtocolInstance(..), Type::Tuple(..)) => {
|
||||
// Currently we do not make any general assumptions about the disjointness of a `Tuple` type
|
||||
// and a `ProtocolInstance` type because a `Tuple` type can be an instance of a tuple
|
||||
// subclass.
|
||||
//
|
||||
// TODO when we capture the types of the protocol members, we can improve on this.
|
||||
false
|
||||
}
|
||||
|
||||
// for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`,
|
||||
// so although the type is dynamic we can still determine disjointedness in some situations
|
||||
(Type::SubclassOf(subclass_of_ty), other)
|
||||
@@ -1977,6 +2084,8 @@ impl<'db> Type<'db> {
|
||||
| Type::AlwaysTruthy
|
||||
| Type::PropertyInstance(_) => true,
|
||||
|
||||
Type::ProtocolInstance(protocol) => protocol.is_fully_static(),
|
||||
|
||||
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
||||
None => true,
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.is_fully_static(db),
|
||||
@@ -2042,6 +2151,26 @@ impl<'db> Type<'db> {
|
||||
false
|
||||
}
|
||||
|
||||
Type::ProtocolInstance(..) => {
|
||||
// It *might* be possible to have a singleton protocol-instance type...?
|
||||
//
|
||||
// E.g.:
|
||||
//
|
||||
// ```py
|
||||
// from typing import Protocol, Callable
|
||||
//
|
||||
// class WeirdAndWacky(Protocol):
|
||||
// @property
|
||||
// def __class__(self) -> Callable[[], None]: ...
|
||||
// ```
|
||||
//
|
||||
// `WeirdAndWacky` only has a single possible inhabitant: `None`!
|
||||
// It is thus a singleton type.
|
||||
// However, going out of our way to recognise it as such is probably not worth it.
|
||||
// Such cases should anyway be exceedingly rare and/or contrived.
|
||||
false
|
||||
}
|
||||
|
||||
// An unbounded, unconstrained typevar is not a singleton, because it can be
|
||||
// specialized to a non-singleton type. A bounded typevar is not a singleton, even if
|
||||
// the bound is a final singleton class, since it can still be specialized to `Never`.
|
||||
@@ -2144,6 +2273,11 @@ impl<'db> Type<'db> {
|
||||
| Type::SliceLiteral(..)
|
||||
| Type::KnownInstance(..) => true,
|
||||
|
||||
Type::ProtocolInstance(..) => {
|
||||
// See comment in the `Type::ProtocolInstance` branch for `Type::is_singleton`.
|
||||
false
|
||||
}
|
||||
|
||||
// An unbounded, unconstrained typevar is not single-valued, because it can be
|
||||
// specialized to a multiple-valued type. A bounded typevar is not single-valued, even
|
||||
// if the bound is a final single-valued class, since it can still be specialized to
|
||||
@@ -2337,6 +2471,7 @@ impl<'db> Type<'db> {
|
||||
| Type::Tuple(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::PropertyInstance(_) => None,
|
||||
}
|
||||
}
|
||||
@@ -2403,6 +2538,17 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::NominalInstance(instance) => instance.class().instance_member(db, name),
|
||||
|
||||
Type::ProtocolInstance(protocol) => match protocol.inner() {
|
||||
Protocol::FromClass(class) => class.instance_member(db, name),
|
||||
Protocol::Synthesized(synthesized) => {
|
||||
if synthesized.members(db).contains(name) {
|
||||
SymbolAndQualifiers::todo("Capture type of synthesized protocol members")
|
||||
} else {
|
||||
Symbol::Unbound.into()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Type::FunctionLiteral(_) => KnownClass::FunctionType
|
||||
.to_instance(db)
|
||||
.instance_member(db, name),
|
||||
@@ -2885,6 +3031,7 @@ impl<'db> Type<'db> {
|
||||
),
|
||||
|
||||
Type::NominalInstance(..)
|
||||
| Type::ProtocolInstance(..)
|
||||
| Type::BooleanLiteral(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
@@ -2915,7 +3062,7 @@ impl<'db> Type<'db> {
|
||||
// It will need a special handling, so it remember the origin type to properly
|
||||
// resolve the attribute.
|
||||
if matches!(
|
||||
self.into_instance()
|
||||
self.into_nominal_instance()
|
||||
.and_then(|instance| instance.class().known(db)),
|
||||
Some(KnownClass::ModuleType | KnownClass::GenericAlias)
|
||||
) {
|
||||
@@ -3181,6 +3328,8 @@ impl<'db> Type<'db> {
|
||||
None => try_dunder_bool()?,
|
||||
},
|
||||
|
||||
Type::ProtocolInstance(_) => try_dunder_bool()?,
|
||||
|
||||
Type::KnownInstance(known_instance) => known_instance.bool(),
|
||||
|
||||
Type::PropertyInstance(_) => Truthiness::AlwaysTrue,
|
||||
@@ -4315,11 +4464,14 @@ impl<'db> Type<'db> {
|
||||
};
|
||||
let specialized = specialization
|
||||
.map(|specialization| {
|
||||
Type::instance(ClassType::Generic(GenericAlias::new(
|
||||
Type::instance(
|
||||
db,
|
||||
generic_origin,
|
||||
specialization,
|
||||
)))
|
||||
ClassType::Generic(GenericAlias::new(
|
||||
db,
|
||||
generic_origin,
|
||||
specialization,
|
||||
)),
|
||||
)
|
||||
})
|
||||
.unwrap_or(instance_ty);
|
||||
Ok(specialized)
|
||||
@@ -4350,9 +4502,9 @@ impl<'db> Type<'db> {
|
||||
pub fn to_instance(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Type::Dynamic(_) | Type::Never => Some(*self),
|
||||
Type::ClassLiteral(class) => Some(Type::instance(class.default_specialization(db))),
|
||||
Type::GenericAlias(alias) => Some(Type::instance(ClassType::from(*alias))),
|
||||
Type::SubclassOf(subclass_of_ty) => Some(subclass_of_ty.to_instance()),
|
||||
Type::ClassLiteral(class) => Some(Type::instance(db, class.default_specialization(db))),
|
||||
Type::GenericAlias(alias) => Some(Type::instance(db, ClassType::from(*alias))),
|
||||
Type::SubclassOf(subclass_of_ty) => Some(subclass_of_ty.to_instance(db)),
|
||||
Type::Union(union) => {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
for element in union.elements(db) {
|
||||
@@ -4371,6 +4523,7 @@ impl<'db> Type<'db> {
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
@@ -4417,11 +4570,11 @@ impl<'db> Type<'db> {
|
||||
KnownClass::Float.to_instance(db),
|
||||
],
|
||||
),
|
||||
_ => Type::instance(class.default_specialization(db)),
|
||||
_ => Type::instance(db, class.default_specialization(db)),
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
Type::GenericAlias(alias) => Ok(Type::instance(ClassType::from(*alias))),
|
||||
Type::GenericAlias(alias) => Ok(Type::instance(db, ClassType::from(*alias))),
|
||||
|
||||
Type::SubclassOf(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
@@ -4444,6 +4597,7 @@ impl<'db> Type<'db> {
|
||||
| Type::Never
|
||||
| Type::FunctionLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::PropertyInstance(_) => Err(InvalidTypeExpressionError {
|
||||
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(*self)],
|
||||
fallback_type: Type::unknown(),
|
||||
@@ -4691,6 +4845,7 @@ impl<'db> Type<'db> {
|
||||
),
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
||||
Type::BoundSuper(_) => KnownClass::Super.to_class_literal(db),
|
||||
Type::ProtocolInstance(protocol) => protocol.to_meta_type(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4812,9 +4967,11 @@ impl<'db> Type<'db> {
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::SliceLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
// Instance contains a ClassType, which has already been specialized if needed, like
|
||||
// above with BoundMethod's self_instance.
|
||||
// `NominalInstance` contains a ClassType, which has already been specialized if needed,
|
||||
// like above with BoundMethod's self_instance.
|
||||
| Type::NominalInstance(_)
|
||||
// Same for `ProtocolInstance`
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::KnownInstance(_) => self,
|
||||
}
|
||||
}
|
||||
@@ -4912,6 +5069,11 @@ impl<'db> Type<'db> {
|
||||
|
||||
Self::TypeVar(var) => Some(TypeDefinition::TypeVar(var.definition(db))),
|
||||
|
||||
Self::ProtocolInstance(protocol) => match protocol.inner() {
|
||||
Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))),
|
||||
Protocol::Synthesized(_) => None,
|
||||
},
|
||||
|
||||
Self::Union(_) | Self::Intersection(_) => None,
|
||||
|
||||
// These types have no definition
|
||||
|
||||
@@ -591,7 +591,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
}
|
||||
_ => {
|
||||
let known_instance = new_positive
|
||||
.into_instance()
|
||||
.into_nominal_instance()
|
||||
.and_then(|instance| instance.class().known(db));
|
||||
|
||||
if known_instance == Some(KnownClass::Object) {
|
||||
@@ -705,7 +705,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
let contains_bool = || {
|
||||
self.positive
|
||||
.iter()
|
||||
.filter_map(|ty| ty.into_instance())
|
||||
.filter_map(|ty| ty.into_nominal_instance())
|
||||
.filter_map(|instance| instance.class().known(db))
|
||||
.any(KnownClass::is_bool)
|
||||
};
|
||||
|
||||
@@ -144,6 +144,10 @@ impl<'db> ClassType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_protocol(self, db: &'db dyn Db) -> bool {
|
||||
self.class_literal(db).0.is_protocol(db)
|
||||
}
|
||||
|
||||
pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
|
||||
let (class_literal, _) = self.class_literal(db);
|
||||
class_literal.name(db)
|
||||
@@ -1076,6 +1080,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
Parameters::new([Parameter::positional_or_keyword(Name::new_static("other"))
|
||||
// TODO: could be `Self`.
|
||||
.with_annotated_type(Type::instance(
|
||||
db,
|
||||
self.apply_optional_specialization(db, specialization),
|
||||
))]),
|
||||
Some(KnownClass::Bool.to_instance(db)),
|
||||
@@ -2084,7 +2089,7 @@ impl<'db> KnownClass {
|
||||
pub(crate) fn to_instance(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.to_class_literal(db)
|
||||
.to_class_type(db)
|
||||
.map(Type::instance)
|
||||
.map(|class| Type::instance(db, class))
|
||||
.unwrap_or_else(Type::unknown)
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ impl<'db> ClassBase<'db> {
|
||||
| Type::SubclassOf(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy => None,
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::types::{
|
||||
use crate::Db;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::instance::Protocol;
|
||||
use super::CallableType;
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
@@ -81,6 +82,25 @@ impl Display for DisplayRepresentation<'_> {
|
||||
(ClassType::Generic(alias), _) => write!(f, "{}", alias.display(self.db)),
|
||||
}
|
||||
}
|
||||
Type::ProtocolInstance(protocol) => match protocol.inner() {
|
||||
Protocol::FromClass(ClassType::NonGeneric(class)) => {
|
||||
f.write_str(class.name(self.db))
|
||||
}
|
||||
Protocol::FromClass(ClassType::Generic(alias)) => alias.display(self.db).fmt(f),
|
||||
Protocol::Synthesized(synthetic) => {
|
||||
f.write_str("<Protocol with members ")?;
|
||||
let member_list = synthetic.members(self.db);
|
||||
let num_members = member_list.len();
|
||||
for (i, member) in member_list.iter().enumerate() {
|
||||
let is_last = i == num_members - 1;
|
||||
write!(f, "'{member}'")?;
|
||||
if !is_last {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
}
|
||||
f.write_char('>')
|
||||
}
|
||||
},
|
||||
Type::PropertyInstance(_) => f.write_str("property"),
|
||||
Type::ModuleLiteral(module) => {
|
||||
write!(f, "<module '{}'>", module.module(self.db).name())
|
||||
|
||||
@@ -2541,6 +2541,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Type::Dynamic(..) | Type::Never => true,
|
||||
|
||||
Type::NominalInstance(..)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::BooleanLiteral(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
@@ -5008,6 +5009,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
| Type::GenericAlias(_)
|
||||
| Type::SubclassOf(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::Union(_)
|
||||
@@ -5288,6 +5290,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
| Type::GenericAlias(_)
|
||||
| Type::SubclassOf(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::Intersection(_)
|
||||
@@ -5313,6 +5316,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
| Type::GenericAlias(_)
|
||||
| Type::SubclassOf(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::Intersection(_)
|
||||
|
||||
@@ -6,11 +6,15 @@ use super::{ClassType, KnownClass, SubclassOfType, Type};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub(crate) const fn instance(class: ClassType<'db>) -> Self {
|
||||
Self::NominalInstance(NominalInstanceType { class })
|
||||
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
if class.is_protocol(db) {
|
||||
Self::ProtocolInstance(ProtocolInstanceType(Protocol::FromClass(class)))
|
||||
} else {
|
||||
Self::NominalInstance(NominalInstanceType { class })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn into_instance(self) -> Option<NominalInstanceType<'db>> {
|
||||
pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
|
||||
match self {
|
||||
Type::NominalInstance(instance_type) => Some(instance_type),
|
||||
_ => None,
|
||||
@@ -95,30 +99,26 @@ impl<'db> From<NominalInstanceType<'db>> for Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, salsa::Supertype)]
|
||||
pub enum ProtocolInstanceType<'db> {
|
||||
FromClass(ClassType<'db>),
|
||||
Synthesized(SynthesizedProtocolType<'db>),
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, salsa::Update)]
|
||||
pub struct ProtocolInstanceType<'db>(
|
||||
// Keep the inner field here private,
|
||||
// so that the only way of constructing `ProtocolInstanceType` instances
|
||||
// is through the `Type::instance` constructor function.
|
||||
Protocol<'db>,
|
||||
);
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> ProtocolInstanceType<'db> {
|
||||
#[salsa::tracked(return_ref)]
|
||||
fn protocol_members(self, db: &'db dyn Db) -> FxOrderSet<Name> {
|
||||
match self {
|
||||
Self::FromClass(class) => class
|
||||
.class_literal(db)
|
||||
.0
|
||||
.into_protocol_class(db)
|
||||
.expect("Protocol class literal should be a protocol class")
|
||||
.protocol_members(db),
|
||||
Self::Synthesized(synthesized) => synthesized.members(db),
|
||||
}
|
||||
pub(super) fn protocol_members(self, db: &'db dyn Db) -> &'db FxOrderSet<Name> {
|
||||
self.0.protocol_members(db)
|
||||
}
|
||||
|
||||
pub(super) fn inner(self) -> Protocol<'db> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Self::FromClass(class) => SubclassOfType::from(db, class),
|
||||
match self.0 {
|
||||
Protocol::FromClass(class) => SubclassOfType::from(db, class),
|
||||
|
||||
// TODO: we can and should do better here.
|
||||
//
|
||||
@@ -133,17 +133,32 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
// reveal_type(type(x)) # mypy: "type[def (builtins.int) -> builtins.str]"
|
||||
// reveal_type(type(x).__call__) # mypy: "def (*args: Any, **kwds: Any) -> Any"
|
||||
// ```
|
||||
Self::Synthesized(_) => KnownClass::Type.to_instance(db),
|
||||
Protocol::Synthesized(_) => KnownClass::Type.to_instance(db),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
match self {
|
||||
Self::FromClass(_) => {
|
||||
Self::Synthesized(SynthesizedProtocolType::new(db, self.protocol_members(db)))
|
||||
}
|
||||
Self::Synthesized(_) => self,
|
||||
pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let members = self.protocol_members(db);
|
||||
let object = KnownClass::Object.to_instance(db);
|
||||
if members
|
||||
.iter()
|
||||
.all(|member| !object.member(db, member).symbol.is_unbound())
|
||||
{
|
||||
return object;
|
||||
}
|
||||
match self.0 {
|
||||
Protocol::FromClass(_) => Type::ProtocolInstance(Self(Protocol::Synthesized(
|
||||
SynthesizedProtocolType::new(db, self.protocol_members(db)),
|
||||
))),
|
||||
Protocol::Synthesized(_) => Type::ProtocolInstance(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: should iterate over the types of the members
|
||||
/// and check if any of them contain `Todo` types
|
||||
#[expect(clippy::unused_self)]
|
||||
pub(super) fn contains_todo(self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// TODO: should not be considered fully static if any members do not have fully static types
|
||||
@@ -181,7 +196,35 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned(debug)]
|
||||
pub struct SynthesizedProtocolType<'db> {
|
||||
members: FxOrderSet<Name>,
|
||||
/// Private inner enum to represent the two kinds of protocol types.
|
||||
/// This is not exposed publicly, so that the only way of constructing `Protocol` instances
|
||||
/// is through the [`Type::instance`] constructor function.
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, salsa::Supertype, PartialOrd, Ord,
|
||||
)]
|
||||
pub(super) enum Protocol<'db> {
|
||||
FromClass(ClassType<'db>),
|
||||
Synthesized(SynthesizedProtocolType<'db>),
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> Protocol<'db> {
|
||||
#[salsa::tracked(return_ref)]
|
||||
fn protocol_members(self, db: &'db dyn Db) -> FxOrderSet<Name> {
|
||||
match self {
|
||||
Self::FromClass(class) => class
|
||||
.class_literal(db)
|
||||
.0
|
||||
.into_protocol_class(db)
|
||||
.expect("Protocol class literal should be a protocol class")
|
||||
.protocol_members(db),
|
||||
Self::Synthesized(synthesized) => synthesized.members(db).clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned(debug)]
|
||||
pub(super) struct SynthesizedProtocolType<'db> {
|
||||
#[return_ref]
|
||||
pub(super) members: FxOrderSet<Name>,
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ impl KnownConstraintFunction {
|
||||
/// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type.
|
||||
fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option<Type<'db>> {
|
||||
let constraint_fn = |class| match self {
|
||||
KnownConstraintFunction::IsInstance => Type::instance(class),
|
||||
KnownConstraintFunction::IsInstance => Type::instance(db, class),
|
||||
KnownConstraintFunction::IsSubclass => SubclassOfType::from(db, class),
|
||||
};
|
||||
|
||||
@@ -682,7 +682,7 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
let symbol = self.expect_expr_name_symbol(id);
|
||||
constraints.insert(
|
||||
symbol,
|
||||
Type::instance(rhs_class.unknown_specialization(self.db)),
|
||||
Type::instance(self.db, rhs_class.unknown_specialization(self.db)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,9 +94,9 @@ impl<'db> SubclassOfType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_instance(self) -> Type<'db> {
|
||||
pub(crate) fn to_instance(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self.subclass_of {
|
||||
SubclassOfInner::Class(class) => Type::instance(class),
|
||||
SubclassOfInner::Class(class) => Type::instance(db, class),
|
||||
SubclassOfInner::Dynamic(dynamic_type) => Type::Dynamic(dynamic_type),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,13 +126,21 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
|
||||
(Type::SubclassOf(_), _) => Ordering::Less,
|
||||
(_, Type::SubclassOf(_)) => Ordering::Greater,
|
||||
|
||||
(Type::NominalInstance(left), Type::NominalInstance(right)) => {
|
||||
left.class().cmp(&right.class())
|
||||
}
|
||||
|
||||
(Type::NominalInstance(_), _) => Ordering::Less,
|
||||
(_, Type::NominalInstance(_)) => Ordering::Greater,
|
||||
|
||||
(Type::ProtocolInstance(left_proto), Type::ProtocolInstance(right_proto)) => {
|
||||
debug_assert_eq!(*left, left_proto.normalized(db));
|
||||
debug_assert_eq!(*right, right_proto.normalized(db));
|
||||
left_proto.cmp(right_proto)
|
||||
}
|
||||
(Type::ProtocolInstance(_), _) => Ordering::Less,
|
||||
(_, Type::ProtocolInstance(_)) => Ordering::Greater,
|
||||
|
||||
(Type::TypeVar(left), Type::TypeVar(right)) => left.cmp(right),
|
||||
(Type::TypeVar(_), _) => Ordering::Less,
|
||||
(_, Type::TypeVar(_)) => Ordering::Greater,
|
||||
|
||||
Reference in New Issue
Block a user