mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[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
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:
parent
55df9271ba
commit
0fb94c052e
13 changed files with 98 additions and 85 deletions
|
@ -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, ...]
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue