[ty] Infer parameter specializations of generic aliases (#18021)
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

This updates our function specialization inference to infer type
mappings from parameters that are generic aliases, e.g.:

```py
def f[T](x: list[T]) -> T: ...

reveal_type(f(["a", "b"]))  # revealed: str
```

Though note that we're still inferring the type of list literals as
`list[Unknown]`, so for now we actually need something like the
following in our tests:

```py
def _(x: list[str]):
    reveal_type(f(x))  # revealed: str
```
This commit is contained in:
Douglas Creager 2025-05-12 22:12:44 -04:00 committed by GitHub
parent 55df9271ba
commit 0fb94c052e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 98 additions and 85 deletions

View file

@ -17,7 +17,6 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
```py
def _(x: tuple[int, ...], y: tuple[str, ...]):
# TODO: should be `tuple[int | str, ...]`
reveal_type(x + y) # revealed: tuple[int | Unknown, ...]
reveal_type(x + y) # revealed: tuple[int | str, ...]
reveal_type(x + (1, 2)) # revealed: tuple[int, ...]
```

View file

@ -88,14 +88,12 @@ def takes_in_protocol(x: CanIndex[T]) -> T:
return x[0]
def deep_list(x: list[str]) -> None:
# TODO: revealed: list[str]
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
reveal_type(takes_in_list(x)) # revealed: list[str]
# TODO: revealed: str
reveal_type(takes_in_protocol(x)) # revealed: Unknown
def deeper_list(x: list[set[str]]) -> None:
# TODO: revealed: list[set[str]]
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
reveal_type(takes_in_list(x)) # revealed: list[set[str]]
# TODO: revealed: set[str]
reveal_type(takes_in_protocol(x)) # revealed: Unknown
@ -119,13 +117,11 @@ This also works when passing in arguments that are subclasses of the parameter t
class Sub(list[int]): ...
class GenericSub(list[T]): ...
# TODO: revealed: list[int]
reveal_type(takes_in_list(Sub())) # revealed: list[Unknown]
reveal_type(takes_in_list(Sub())) # revealed: list[int]
# TODO: revealed: int
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown
# TODO: revealed: list[str]
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown]
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[str]
# TODO: revealed: str
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown

View file

@ -83,14 +83,12 @@ def takes_in_protocol[T](x: CanIndex[T]) -> T:
return x[0]
def deep_list(x: list[str]) -> None:
# TODO: revealed: list[str]
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
reveal_type(takes_in_list(x)) # revealed: list[str]
# TODO: revealed: str
reveal_type(takes_in_protocol(x)) # revealed: Unknown
def deeper_list(x: list[set[str]]) -> None:
# TODO: revealed: list[set[str]]
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
reveal_type(takes_in_list(x)) # revealed: list[set[str]]
# TODO: revealed: set[str]
reveal_type(takes_in_protocol(x)) # revealed: Unknown
@ -114,13 +112,11 @@ This also works when passing in arguments that are subclasses of the parameter t
class Sub(list[int]): ...
class GenericSub[T](list[T]): ...
# TODO: revealed: list[int]
reveal_type(takes_in_list(Sub())) # revealed: list[Unknown]
reveal_type(takes_in_list(Sub())) # revealed: list[int]
# TODO: revealed: int
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown
# TODO: revealed: list[str]
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown]
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[str]
# TODO: revealed: str
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown

View file

@ -562,25 +562,22 @@ impl<'db> Type<'db> {
fn is_none(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance()
.is_some_and(|instance| instance.class().is_known(db, KnownClass::NoneType))
.is_some_and(|instance| instance.class.is_known(db, KnownClass::NoneType))
}
fn is_bool(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance()
.is_some_and(|instance| instance.class().is_known(db, KnownClass::Bool))
.is_some_and(|instance| instance.class.is_known(db, KnownClass::Bool))
}
pub fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance().is_some_and(|instance| {
instance
.class()
.is_known(db, KnownClass::NotImplementedType)
})
self.into_nominal_instance()
.is_some_and(|instance| instance.class.is_known(db, KnownClass::NotImplementedType))
}
pub fn is_object(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance()
.is_some_and(|instance| instance.class().is_object(db))
.is_some_and(|instance| instance.class.is_object(db))
}
pub const fn is_todo(&self) -> bool {
@ -1063,7 +1060,7 @@ impl<'db> Type<'db> {
(_, Type::Never) => false,
// Everything is a subtype of `object`.
(_, Type::NominalInstance(instance)) if instance.class().is_object(db) => true,
(_, Type::NominalInstance(instance)) if instance.class.is_object(db) => true,
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
@ -1373,7 +1370,7 @@ impl<'db> Type<'db> {
// All types are assignable to `object`.
// TODO this special case might be removable once the below cases are comprehensive
(_, Type::NominalInstance(instance)) if instance.class().is_object(db) => true,
(_, Type::NominalInstance(instance)) if instance.class.is_object(db) => true,
// In general, a TypeVar `T` is not assignable to a type `S` unless one of the two conditions is satisfied:
// 1. `T` is a bound TypeVar and `T`'s upper bound is assignable to `S`.
@ -1547,7 +1544,7 @@ impl<'db> Type<'db> {
}
(Type::NominalInstance(instance), Type::Callable(_))
if instance.class().is_subclass_of_any_or_unknown(db) =>
if instance.class.is_subclass_of_any_or_unknown(db) =>
{
true
}
@ -1616,7 +1613,7 @@ impl<'db> Type<'db> {
}
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
n.class().is_object(db) && protocol.normalized(db) == nominal
n.class.is_object(db) && protocol.normalized(db) == nominal
}
_ => self == other && self.is_fully_static(db) && other.is_fully_static(db),
}
@ -1671,7 +1668,7 @@ impl<'db> Type<'db> {
}
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
n.class().is_object(db) && protocol.normalized(db) == nominal
n.class.is_object(db) && protocol.normalized(db) == nominal
}
_ => false,
}
@ -1883,7 +1880,7 @@ impl<'db> Type<'db> {
// 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)
n.class.is_final(db) && !nominal.satisfies_protocol(db, protocol)
}
(
@ -1948,7 +1945,7 @@ impl<'db> Type<'db> {
(Type::KnownInstance(known_instance), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => {
!known_instance.is_instance_of(db, instance.class())
!known_instance.is_instance_of(db, instance.class)
}
(known_instance_ty @ Type::KnownInstance(_), Type::Tuple(tuple))
@ -1960,7 +1957,7 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::BooleanLiteral(..)) => {
// A `Type::BooleanLiteral()` must be an instance of exactly `bool`
// (it cannot be an instance of a `bool` subclass)
!KnownClass::Bool.is_subclass_of(db, instance.class())
!KnownClass::Bool.is_subclass_of(db, instance.class)
}
(Type::BooleanLiteral(..), _) | (_, Type::BooleanLiteral(..)) => true,
@ -1969,7 +1966,7 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::IntLiteral(..)) => {
// A `Type::IntLiteral()` must be an instance of exactly `int`
// (it cannot be an instance of an `int` subclass)
!KnownClass::Int.is_subclass_of(db, instance.class())
!KnownClass::Int.is_subclass_of(db, instance.class)
}
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => true,
@ -1981,7 +1978,7 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
// A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
// (it cannot be an instance of a `str` subclass)
!KnownClass::Str.is_subclass_of(db, instance.class())
!KnownClass::Str.is_subclass_of(db, instance.class)
}
(Type::LiteralString, Type::LiteralString) => false,
@ -1991,7 +1988,7 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::BytesLiteral(..)) => {
// A `Type::BytesLiteral()` must be an instance of exactly `bytes`
// (it cannot be an instance of a `bytes` subclass)
!KnownClass::Bytes.is_subclass_of(db, instance.class())
!KnownClass::Bytes.is_subclass_of(db, instance.class)
}
// A class-literal type `X` is always disjoint from an instance type `Y`,
@ -2012,7 +2009,7 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::FunctionLiteral(..)) => {
// A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
// (it cannot be an instance of a `types.FunctionType` subclass)
!KnownClass::FunctionType.is_subclass_of(db, instance.class())
!KnownClass::FunctionType.is_subclass_of(db, instance.class)
}
(Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType
@ -2440,7 +2437,7 @@ impl<'db> Type<'db> {
// i.e. Type::NominalInstance(type). So looking up a name in the MRO of
// `Type::NominalInstance(type)` is equivalent to looking up the name in the
// MRO of the class `object`.
Type::NominalInstance(instance) if instance.class().is_known(db, KnownClass::Type) => {
Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Type) => {
KnownClass::Object
.to_class_literal(db)
.find_name_in_mro_with_policy(db, name, policy)
@ -2530,7 +2527,7 @@ impl<'db> Type<'db> {
Type::Dynamic(_) | Type::Never => Symbol::bound(self).into(),
Type::NominalInstance(instance) => instance.class().instance_member(db, name),
Type::NominalInstance(instance) => instance.class.instance_member(db, name),
Type::ProtocolInstance(protocol) => protocol.instance_member(db, name),
@ -2978,7 +2975,7 @@ impl<'db> Type<'db> {
Type::NominalInstance(instance)
if matches!(name.as_str(), "major" | "minor")
&& instance.class().is_known(db, KnownClass::VersionInfo) =>
&& instance.class.is_known(db, KnownClass::VersionInfo) =>
{
let python_version = Program::get(db).python_version(db);
let segment = if name == "major" {
@ -3050,7 +3047,7 @@ impl<'db> Type<'db> {
// resolve the attribute.
if matches!(
self.into_nominal_instance()
.and_then(|instance| instance.class().known(db)),
.and_then(|instance| instance.class.known(db)),
Some(KnownClass::ModuleType | KnownClass::GenericAlias)
) {
return Symbol::Unbound.into();
@ -3309,7 +3306,7 @@ impl<'db> Type<'db> {
}
},
Type::NominalInstance(instance) => match instance.class().known(db) {
Type::NominalInstance(instance) => match instance.class.known(db) {
Some(known_class) => known_class.bool(),
None => try_dunder_bool()?,
},
@ -4863,7 +4860,7 @@ impl<'db> Type<'db> {
Type::Dynamic(_) => Ok(*self),
Type::NominalInstance(instance) => match instance.class().known(db) {
Type::NominalInstance(instance) => match instance.class.known(db) {
Some(KnownClass::TypeVar) => Ok(todo_type!(
"Support for `typing.TypeVar` instances in type expressions"
)),
@ -5291,7 +5288,7 @@ impl<'db> Type<'db> {
}
Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))),
Self::NominalInstance(instance) => {
Some(TypeDefinition::Class(instance.class().definition(db)))
Some(TypeDefinition::Class(instance.class.definition(db)))
}
Self::KnownInstance(instance) => match instance {
KnownInstanceType::TypeVar(var) => {
@ -8046,7 +8043,7 @@ impl<'db> SuperOwnerKind<'db> {
Either::Left(ClassBase::Dynamic(dynamic).mro(db, None))
}
SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)),
SuperOwnerKind::Instance(instance) => Either::Right(instance.class().iter_mro(db)),
SuperOwnerKind::Instance(instance) => Either::Right(instance.class.iter_mro(db)),
}
}
@ -8062,7 +8059,7 @@ impl<'db> SuperOwnerKind<'db> {
match self {
SuperOwnerKind::Dynamic(_) => None,
SuperOwnerKind::Class(class) => Some(class),
SuperOwnerKind::Instance(instance) => Some(instance.class()),
SuperOwnerKind::Instance(instance) => Some(instance.class),
}
}
@ -8240,7 +8237,7 @@ impl<'db> BoundSuperType<'db> {
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`")
}
SuperOwnerKind::Class(class) => class,
SuperOwnerKind::Instance(instance) => instance.class(),
SuperOwnerKind::Instance(instance) => instance.class,
};
let (class_literal, _) = class.class_literal(db);

View file

@ -614,7 +614,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
_ => {
let known_instance = new_positive
.into_nominal_instance()
.and_then(|instance| instance.class().known(db));
.and_then(|instance| instance.class.known(db));
if known_instance == Some(KnownClass::Object) {
// `object & T` -> `T`; it is always redundant to add `object` to an intersection
@ -634,7 +634,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
new_positive = Type::BooleanLiteral(false);
}
Type::NominalInstance(instance)
if instance.class().is_known(db, KnownClass::Bool) =>
if instance.class.is_known(db, KnownClass::Bool) =>
{
match new_positive {
// `bool & AlwaysTruthy` -> `Literal[True]`
@ -728,7 +728,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
self.positive
.iter()
.filter_map(|ty| ty.into_nominal_instance())
.filter_map(|instance| instance.class().known(db))
.filter_map(|instance| instance.class.known(db))
.any(KnownClass::is_bool)
};
@ -744,7 +744,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
Type::Never => {
// Adding ~Never to an intersection is a no-op.
}
Type::NominalInstance(instance) if instance.class().is_object(db) => {
Type::NominalInstance(instance) if instance.class.is_object(db) => {
// Adding ~object to an intersection results in Never.
*self = Self::default();
self.positive.insert(Type::Never);

View file

@ -2733,7 +2733,7 @@ impl<'db> Type<'db> {
/// The type must be a specialization of the `slice` builtin type, where the specialized
/// typevars are statically known integers or `None`.
pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> {
let ClassType::Generic(alias) = self.into_nominal_instance()?.class() else {
let ClassType::Generic(alias) = self.into_nominal_instance()?.class else {
return None;
};
if !alias.origin(db).is_known(db, KnownClass::Slice) {
@ -2747,7 +2747,7 @@ impl<'db> Type<'db> {
Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(),
Type::BooleanLiteral(b) => Some(Some(i32::from(*b))),
Type::NominalInstance(instance)
if instance.class().is_known(db, KnownClass::NoneType) =>
if instance.class.is_known(db, KnownClass::NoneType) =>
{
Some(None)
}

View file

@ -103,7 +103,7 @@ impl<'db> ClassBase<'db> {
}
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
Type::NominalInstance(instance)
if instance.class().is_known(db, KnownClass::GenericAlias) =>
if instance.class.is_known(db, KnownClass::GenericAlias) =>
{
Self::try_from_type(db, todo_type!("GenericAlias instance"))
}

View file

@ -69,7 +69,7 @@ impl Display for DisplayRepresentation<'_> {
Type::Dynamic(dynamic) => dynamic.fmt(f),
Type::Never => f.write_str("Never"),
Type::NominalInstance(instance) => {
match (instance.class(), instance.class().known(self.db)) {
match (instance.class, instance.class.known(self.db)) {
(_, Some(KnownClass::NoneType)) => f.write_str("None"),
(_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
(ClassType::NonGeneric(class), _) => f.write_str(class.name(self.db)),

View file

@ -2,6 +2,9 @@ use ruff_python_ast as ast;
use rustc_hash::FxHashMap;
use crate::semantic_index::SemanticIndex;
use crate::types::class::ClassType;
use crate::types::class_base::ClassBase;
use crate::types::instance::NominalInstanceType;
use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::{
declaration_type, todo_type, KnownInstanceType, Type, TypeVarBoundOrConstraints,
@ -671,6 +674,35 @@ impl<'db> SpecializationBuilder<'db> {
}
}
(
Type::NominalInstance(NominalInstanceType {
class: ClassType::Generic(formal_alias),
..
}),
Type::NominalInstance(NominalInstanceType {
class: actual_class,
..
}),
) => {
let formal_origin = formal_alias.origin(self.db);
for base in actual_class.iter_mro(self.db) {
let ClassBase::Class(ClassType::Generic(base_alias)) = base else {
continue;
};
if formal_origin != base_alias.origin(self.db) {
continue;
}
let formal_specialization = formal_alias.specialization(self.db).types(self.db);
let base_specialization = base_alias.specialization(self.db).types(self.db);
for (formal_ty, base_ty) in
formal_specialization.iter().zip(base_specialization)
{
self.infer(*formal_ty, *base_ty)?;
}
return Ok(());
}
}
(Type::Union(formal), _) => {
// TODO: We haven't implemented a full unification solver yet. If typevars appear
// in multiple union elements, we ideally want to express that _only one_ of them

View file

@ -1384,7 +1384,7 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
Type::NominalInstance(instance)
if matches!(
instance.class().known(self.db()),
instance.class.known(self.db()),
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
) => {}
_ => return false,
@ -2477,7 +2477,7 @@ impl<'db> TypeInferenceBuilder<'db> {
definition: Definition<'db>,
) {
fn extract_tuple_specialization<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
let class = ty.into_nominal_instance()?.class();
let class = ty.into_nominal_instance()?.class;
if !class.is_known(db, KnownClass::Tuple) {
return None;
}
@ -2933,7 +2933,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
// Super instances do not allow attribute assignment
Type::NominalInstance(instance) if instance.class().is_known(db, KnownClass::Super) => {
Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Super) => {
if emit_diagnostics {
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
builder.into_diagnostic(format_args!(
@ -3462,10 +3462,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// Handle various singletons.
if let Type::NominalInstance(instance) = declared_ty.inner_type() {
if instance
.class()
.is_known(self.db(), KnownClass::SpecialForm)
{
if instance.class.is_known(self.db(), KnownClass::SpecialForm) {
if let Some(name_expr) = target.as_name_expr() {
if let Some(known_instance) = KnownInstanceType::try_from_file_and_name(
self.db(),
@ -6593,9 +6590,7 @@ impl<'db> TypeInferenceBuilder<'db> {
range,
),
(Type::Tuple(_), Type::NominalInstance(instance))
if instance
.class()
.is_known(self.db(), KnownClass::VersionInfo) =>
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
{
self.infer_binary_type_comparison(
left,
@ -6605,9 +6600,7 @@ impl<'db> TypeInferenceBuilder<'db> {
)
}
(Type::NominalInstance(instance), Type::Tuple(_))
if instance
.class()
.is_known(self.db(), KnownClass::VersionInfo) =>
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
{
self.infer_binary_type_comparison(
Type::version_info_tuple(self.db()),
@ -6999,9 +6992,7 @@ impl<'db> TypeInferenceBuilder<'db> {
) -> Type<'db> {
match (value_ty, slice_ty, slice_ty.slice_literal(self.db())) {
(Type::NominalInstance(instance), _, _)
if instance
.class()
.is_known(self.db(), KnownClass::VersionInfo) =>
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
{
self.infer_subscript_expression_types(
value_node,
@ -7362,7 +7353,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let type_to_slice_argument = |ty: Option<Type<'db>>| match ty {
Some(ty @ (Type::IntLiteral(_) | Type::BooleanLiteral(_))) => SliceArg::Arg(ty),
Some(ty @ Type::NominalInstance(instance))
if instance.class().is_known(self.db(), KnownClass::NoneType) =>
if instance.class.is_known(self.db(), KnownClass::NoneType) =>
{
SliceArg::Arg(ty)
}

View file

@ -1,5 +1,7 @@
//! Instance types: both nominal and structural.
use std::marker::PhantomData;
use super::protocol_class::ProtocolInterface;
use super::{ClassType, KnownClass, SubclassOfType, Type};
use crate::symbol::{Symbol, SymbolAndQualifiers};
@ -14,7 +16,10 @@ impl<'db> Type<'db> {
if class.class_literal(db).0.is_protocol(db) {
Self::ProtocolInstance(ProtocolInstanceType(Protocol::FromClass(class)))
} else {
Self::NominalInstance(NominalInstanceType { class })
Self::NominalInstance(NominalInstanceType {
class,
_phantom: PhantomData,
})
}
}
@ -56,16 +61,14 @@ impl<'db> Type<'db> {
/// A type representing the set of runtime objects which are instances of a certain nominal class.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
pub struct NominalInstanceType<'db> {
pub(super) class: ClassType<'db>,
// Keep this field private, so that the only way of constructing `NominalInstanceType` instances
// is through the `Type::instance` constructor function.
class: ClassType<'db>,
_phantom: PhantomData<()>,
}
impl<'db> NominalInstanceType<'db> {
pub(super) fn class(self) -> ClassType<'db> {
self.class
}
pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
// N.B. The subclass relation is fully static
self.class.is_subclass_of(db, other.class)
@ -130,6 +133,7 @@ impl<'db> NominalInstanceType<'db> {
) -> Self {
Self {
class: self.class.apply_type_mapping(db, type_mapping),
_phantom: PhantomData,
}
}

View file

@ -474,7 +474,7 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
}
// Treat `bool` as `Literal[True, False]`.
Type::NominalInstance(instance)
if instance.class().is_known(db, KnownClass::Bool) =>
if instance.class.is_known(db, KnownClass::Bool) =>
{
UnionType::from_elements(
db,
@ -505,7 +505,7 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
fn evaluate_expr_ne(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> {
match (lhs_ty, rhs_ty) {
(Type::NominalInstance(instance), Type::IntLiteral(i))
if instance.class().is_known(self.db, KnownClass::Bool) =>
if instance.class.is_known(self.db, KnownClass::Bool) =>
{
if i == 0 {
Some(Type::BooleanLiteral(false).negate(self.db))

View file

@ -126,9 +126,7 @@ 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(left), Type::NominalInstance(right)) => left.class.cmp(&right.class),
(Type::NominalInstance(_), _) => Ordering::Less,
(_, Type::NominalInstance(_)) => Ordering::Greater,
@ -171,7 +169,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(SuperOwnerKind::Class(_), _) => Ordering::Less,
(_, SuperOwnerKind::Class(_)) => Ordering::Greater,
(SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => {
left.class().cmp(&right.class())
left.class.cmp(&right.class)
}
(SuperOwnerKind::Instance(_), _) => Ordering::Less,
(_, SuperOwnerKind::Instance(_)) => Ordering::Greater,