A new Type variant

This commit is contained in:
Alex Waygood
2025-04-24 20:45:47 +01:00
parent 629efb02f1
commit 95bc2bbc79
10 changed files with 299 additions and 56 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(_)

View File

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

View File

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

View File

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

View File

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