Compare commits
6 Commits
micha/add-
...
dcreager/g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b31a3be5b | ||
|
|
b12a72b8dc | ||
|
|
5e74cf07fb | ||
|
|
b7afaa219a | ||
|
|
8dfb59a2b1 | ||
|
|
2a47422dd5 |
@@ -13,8 +13,6 @@ class C[T]: ...
|
||||
A class that inherits from a generic class, and fills its type parameters with typevars, is generic:
|
||||
|
||||
```py
|
||||
# TODO: no error
|
||||
# error: [non-subscriptable]
|
||||
class D[U](C[U]): ...
|
||||
```
|
||||
|
||||
@@ -22,8 +20,6 @@ A class that inherits from a generic class, but fills its type parameters with c
|
||||
_not_ generic:
|
||||
|
||||
```py
|
||||
# TODO: no error
|
||||
# error: [non-subscriptable]
|
||||
class E(C[int]): ...
|
||||
```
|
||||
|
||||
@@ -57,7 +53,7 @@ class D(C[T]): ...
|
||||
|
||||
(Examples `E` and `F` from above do not have analogues in the legacy syntax.)
|
||||
|
||||
## Inferring generic class parameters
|
||||
## Specializing generic classes explicitly
|
||||
|
||||
The type parameter can be specified explicitly:
|
||||
|
||||
@@ -65,15 +61,54 @@ The type parameter can be specified explicitly:
|
||||
class C[T]:
|
||||
x: T
|
||||
|
||||
# TODO: no error
|
||||
# TODO: revealed: C[int]
|
||||
# error: [non-subscriptable]
|
||||
reveal_type(C[int]()) # revealed: C
|
||||
```
|
||||
|
||||
The specialization must match the generic types:
|
||||
|
||||
```py
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to explicit specialization of class `C`: expected 2, got 3"
|
||||
reveal_type(C[int, int]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type variable has an upper bound, the specialized type must satisfy that bound:
|
||||
|
||||
```py
|
||||
class Bounded[T: int]:
|
||||
x: T
|
||||
|
||||
# TODO: revealed: Bounded[int]
|
||||
reveal_type(Bounded[int]()) # revealed: Bounded
|
||||
|
||||
# TODO: error: [invalid-argument]
|
||||
reveal_type(Bounded[str]()) # revealed: Bounded
|
||||
```
|
||||
|
||||
If the type variable is constrained, the specialized type must satisfy those constraints:
|
||||
|
||||
```py
|
||||
class Constrained[T: (int, str)]:
|
||||
x: T
|
||||
|
||||
# TODO: revealed: Constrained[int]
|
||||
reveal_type(Constrained[int]()) # revealed: Constrained
|
||||
|
||||
# TODO: revealed: Constrained[str]
|
||||
reveal_type(Constrained[str]()) # revealed: Constrained
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(Constrained[object]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring generic class parameters
|
||||
|
||||
We can infer the type parameter from a type context:
|
||||
|
||||
```py
|
||||
class C[T]:
|
||||
x: T
|
||||
|
||||
c: C[int] = C()
|
||||
# TODO: revealed: C[int]
|
||||
reveal_type(c) # revealed: C
|
||||
@@ -131,16 +166,11 @@ propagate through:
|
||||
class Base[T]:
|
||||
x: T | None = None
|
||||
|
||||
# TODO: no error
|
||||
# error: [non-subscriptable]
|
||||
class Sub[U](Base[U]): ...
|
||||
|
||||
# TODO: no error
|
||||
# TODO: revealed: int | None
|
||||
# error: [non-subscriptable]
|
||||
reveal_type(Base[int].x) # revealed: T | None
|
||||
# TODO: revealed: int | None
|
||||
# error: [non-subscriptable]
|
||||
reveal_type(Sub[int].x) # revealed: T | None
|
||||
```
|
||||
|
||||
@@ -155,8 +185,6 @@ Here, `Sub` is not a generic class, since it fills its superclass's type paramet
|
||||
|
||||
```pyi
|
||||
class Base[T]: ...
|
||||
# TODO: no error
|
||||
# error: [non-subscriptable]
|
||||
class Sub(Base[Sub]): ...
|
||||
|
||||
reveal_type(Sub) # revealed: Literal[Sub]
|
||||
@@ -169,8 +197,6 @@ A similar case can work in a non-stub file, if forward references are stringifie
|
||||
```py
|
||||
class Base[T]: ...
|
||||
|
||||
# TODO: no error
|
||||
# error: [non-subscriptable]
|
||||
class Sub(Base["Sub"]): ...
|
||||
|
||||
reveal_type(Sub) # revealed: Literal[Sub]
|
||||
@@ -183,8 +209,6 @@ In a non-stub file, without stringified forward references, this raises a `NameE
|
||||
```py
|
||||
class Base[T]: ...
|
||||
|
||||
# TODO: the unresolved-reference error is correct, the non-subscriptable is not
|
||||
# error: [non-subscriptable]
|
||||
# error: [unresolved-reference]
|
||||
class Sub(Base[Sub]): ...
|
||||
```
|
||||
|
||||
@@ -8,8 +8,6 @@ In type stubs, classes can reference themselves in their base class definitions.
|
||||
```pyi
|
||||
class Foo[T]: ...
|
||||
|
||||
# TODO: actually is subscriptable
|
||||
# error: [non-subscriptable]
|
||||
class Bar(Foo[Bar]): ...
|
||||
|
||||
reveal_type(Bar) # revealed: Literal[Bar]
|
||||
|
||||
@@ -49,6 +49,7 @@ mod class_base;
|
||||
mod context;
|
||||
mod diagnostic;
|
||||
mod display;
|
||||
mod generics;
|
||||
mod infer;
|
||||
mod mro;
|
||||
mod narrow;
|
||||
@@ -655,6 +656,7 @@ impl<'db> Type<'db> {
|
||||
.to_instance(db)
|
||||
.is_subtype_of(db, target)
|
||||
}
|
||||
(Type::Callable(CallableType::SpecializeClass(_)), _) => self == target,
|
||||
|
||||
(
|
||||
Type::Callable(CallableType::General(self_callable)),
|
||||
@@ -1036,7 +1038,8 @@ impl<'db> Type<'db> {
|
||||
| Type::Callable(
|
||||
CallableType::BoundMethod(..)
|
||||
| CallableType::MethodWrapperDunderGet(..)
|
||||
| CallableType::WrapperDescriptorDunderGet,
|
||||
| CallableType::WrapperDescriptorDunderGet
|
||||
| CallableType::SpecializeClass(..),
|
||||
)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::ClassLiteral(..)
|
||||
@@ -1050,7 +1053,8 @@ impl<'db> Type<'db> {
|
||||
| Type::Callable(
|
||||
CallableType::BoundMethod(..)
|
||||
| CallableType::MethodWrapperDunderGet(..)
|
||||
| CallableType::WrapperDescriptorDunderGet,
|
||||
| CallableType::WrapperDescriptorDunderGet
|
||||
| CallableType::SpecializeClass(..),
|
||||
)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::ClassLiteral(..)
|
||||
@@ -1250,6 +1254,9 @@ impl<'db> Type<'db> {
|
||||
Type::Callable(CallableType::WrapperDescriptorDunderGet),
|
||||
) => !KnownClass::WrapperDescriptorType.is_subclass_of(db, class),
|
||||
|
||||
(Type::Callable(CallableType::SpecializeClass(_)), Type::Instance(_))
|
||||
| (Type::Instance(_), Type::Callable(CallableType::SpecializeClass(_))) => true,
|
||||
|
||||
(Type::ModuleLiteral(..), other @ Type::Instance(..))
|
||||
| (other @ Type::Instance(..), Type::ModuleLiteral(..)) => {
|
||||
// Modules *can* actually be instances of `ModuleType` subclasses
|
||||
@@ -1304,7 +1311,8 @@ impl<'db> Type<'db> {
|
||||
| Type::Callable(
|
||||
CallableType::BoundMethod(_)
|
||||
| CallableType::MethodWrapperDunderGet(_)
|
||||
| CallableType::WrapperDescriptorDunderGet,
|
||||
| CallableType::WrapperDescriptorDunderGet
|
||||
| CallableType::SpecializeClass(_),
|
||||
)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::IntLiteral(_)
|
||||
@@ -1374,7 +1382,8 @@ impl<'db> Type<'db> {
|
||||
| Type::Callable(
|
||||
CallableType::BoundMethod(_)
|
||||
| CallableType::MethodWrapperDunderGet(_)
|
||||
| CallableType::WrapperDescriptorDunderGet,
|
||||
| CallableType::WrapperDescriptorDunderGet
|
||||
| CallableType::SpecializeClass(_),
|
||||
)
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
@@ -1423,7 +1432,8 @@ impl<'db> Type<'db> {
|
||||
| Type::Callable(
|
||||
CallableType::BoundMethod(..)
|
||||
| CallableType::MethodWrapperDunderGet(..)
|
||||
| CallableType::WrapperDescriptorDunderGet,
|
||||
| CallableType::WrapperDescriptorDunderGet
|
||||
| CallableType::SpecializeClass(..),
|
||||
)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::ClassLiteral(..)
|
||||
@@ -1486,6 +1496,12 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::Dynamic(_) | Type::Never => Some(Symbol::bound(self).into()),
|
||||
|
||||
Type::ClassLiteral(ClassLiteralType { class })
|
||||
if name == "__class_getitem__" && class.generic_context(db).is_some() =>
|
||||
{
|
||||
Some(Symbol::bound(Type::Callable(CallableType::SpecializeClass(*class))).into())
|
||||
}
|
||||
|
||||
Type::ClassLiteral(class_literal @ ClassLiteralType { class }) => {
|
||||
match (class.known(db), name) {
|
||||
(Some(KnownClass::FunctionType), "__get__") => Some(
|
||||
@@ -1635,7 +1651,8 @@ impl<'db> Type<'db> {
|
||||
.to_instance(db)
|
||||
.instance_member(db, name)
|
||||
}
|
||||
Type::Callable(CallableType::General(_)) => {
|
||||
Type::Callable(CallableType::SpecializeClass(_))
|
||||
| Type::Callable(CallableType::General(_)) => {
|
||||
KnownClass::Object.to_instance(db).instance_member(db, name)
|
||||
}
|
||||
|
||||
@@ -1989,7 +2006,8 @@ impl<'db> Type<'db> {
|
||||
.to_instance(db)
|
||||
.member(db, &name)
|
||||
}
|
||||
Type::Callable(CallableType::General(_)) => {
|
||||
Type::Callable(CallableType::SpecializeClass(_))
|
||||
| Type::Callable(CallableType::General(_)) => {
|
||||
KnownClass::Object.to_instance(db).member(db, &name)
|
||||
}
|
||||
|
||||
@@ -2438,6 +2456,16 @@ impl<'db> Type<'db> {
|
||||
Signatures::single(signature)
|
||||
}
|
||||
|
||||
Type::Callable(CallableType::SpecializeClass(class)) => {
|
||||
let generic_context = class
|
||||
.generic_context(db)
|
||||
.expect("should not be able to specialize non-generic class");
|
||||
Signatures::single(CallableSignature::single(
|
||||
self,
|
||||
generic_context.signature(db, Type::class_literal(class)),
|
||||
))
|
||||
}
|
||||
|
||||
Type::FunctionLiteral(function_type) => match function_type.known(db) {
|
||||
Some(
|
||||
KnownFunction::IsEquivalentTo
|
||||
@@ -3153,7 +3181,8 @@ impl<'db> Type<'db> {
|
||||
Type::Callable(CallableType::WrapperDescriptorDunderGet) => {
|
||||
KnownClass::WrapperDescriptorType.to_class_literal(db)
|
||||
}
|
||||
Type::Callable(CallableType::General(_)) => KnownClass::Type.to_instance(db),
|
||||
Type::Callable(CallableType::SpecializeClass(_))
|
||||
| Type::Callable(CallableType::General(_)) => KnownClass::Type.to_instance(db),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db),
|
||||
@@ -4857,6 +4886,9 @@ pub enum CallableType<'db> {
|
||||
/// type. We currently add this as a separate variant because `FunctionType.__get__`
|
||||
/// is an overloaded method and we do not support `@overload` yet.
|
||||
WrapperDescriptorDunderGet,
|
||||
|
||||
/// A special callable that specializes a generic class using subscription.
|
||||
SpecializeClass(Class<'db>),
|
||||
}
|
||||
|
||||
#[salsa::interned(debug)]
|
||||
@@ -5478,6 +5510,10 @@ impl<'db> TupleType<'db> {
|
||||
pub fn len(&self, db: &'db dyn Db) -> usize {
|
||||
self.elements(db).len()
|
||||
}
|
||||
|
||||
pub fn iter(&self, db: &'db dyn Db) -> impl Iterator<Item = Type<'db>> + 'db + '_ {
|
||||
self.elements(db).iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that the `Type` enum does not grow unexpectedly.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use super::Type;
|
||||
use crate::Db;
|
||||
|
||||
/// Arguments for a single call, in source order.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -32,6 +34,29 @@ impl<'a> CallArguments<'a> {
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = Argument<'a>> + '_ {
|
||||
self.0.iter().copied()
|
||||
}
|
||||
|
||||
/// Unpacks any subscript tuple arguments into distinct arguments.
|
||||
pub(crate) fn unpack_subscript_tuples(&self) -> Cow<'_, CallArguments<'a>> {
|
||||
// If there are no subscript tuples, we can use the existing argument list as-is.
|
||||
if self
|
||||
.0
|
||||
.iter()
|
||||
.all(|argument| !matches!(argument, Argument::PositionalSubscriptTuple(_)))
|
||||
{
|
||||
return Cow::Borrowed(self);
|
||||
}
|
||||
|
||||
let mut arguments = VecDeque::with_capacity(self.0.len());
|
||||
for argument in self.iter() {
|
||||
match argument {
|
||||
Argument::PositionalSubscriptTuple(count) => {
|
||||
arguments.extend(std::iter::repeat_n(Argument::Positional, count))
|
||||
}
|
||||
_ => arguments.push_back(argument),
|
||||
}
|
||||
}
|
||||
Cow::Owned(CallArguments(arguments))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<Argument<'a>> for CallArguments<'a> {
|
||||
@@ -46,6 +71,8 @@ pub(crate) enum Argument<'a> {
|
||||
Synthetic,
|
||||
/// A positional argument.
|
||||
Positional,
|
||||
/// A positional argument that is a packed tuple of multiple subscript expression arguments.
|
||||
PositionalSubscriptTuple(usize),
|
||||
/// A starred positional argument (e.g. `*args`).
|
||||
Variadic,
|
||||
/// A keyword argument (e.g. `a=1`).
|
||||
@@ -54,7 +81,23 @@ pub(crate) enum Argument<'a> {
|
||||
Keywords,
|
||||
}
|
||||
|
||||
impl Argument<'_> {
|
||||
pub(crate) fn subscript_argument<'db>(
|
||||
db: &'db dyn Db,
|
||||
slice_type: Type<'db>,
|
||||
) -> (Self, Type<'db>) {
|
||||
match slice_type {
|
||||
Type::Tuple(tuple) => (
|
||||
Argument::PositionalSubscriptTuple(tuple.len(db)),
|
||||
slice_type,
|
||||
),
|
||||
_ => (Argument::Positional, slice_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Arguments for a single call, in source order, along with inferred types for each argument.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CallArgumentTypes<'a, 'db> {
|
||||
arguments: CallArguments<'a>,
|
||||
types: VecDeque<Type<'db>>,
|
||||
@@ -68,6 +111,16 @@ impl<'a, 'db> CallArgumentTypes<'a, 'db> {
|
||||
Self { arguments, types }
|
||||
}
|
||||
|
||||
/// Create a [`CallArgumentTypes`] from an iterator over non-variadic positional argument
|
||||
/// types.
|
||||
pub(crate) fn from_arguments(
|
||||
arguments: impl IntoIterator<Item = (Argument<'a>, Type<'db>)>,
|
||||
) -> Self {
|
||||
let (arguments, types): (VecDeque<_>, VecDeque<_>) = arguments.into_iter().collect();
|
||||
let arguments = CallArguments(arguments);
|
||||
Self { arguments, types }
|
||||
}
|
||||
|
||||
/// Create a [`CallArgumentTypes`] from an iterator over non-variadic positional argument
|
||||
/// types.
|
||||
pub(crate) fn positional(positional_tys: impl IntoIterator<Item = Type<'db>>) -> Self {
|
||||
@@ -112,6 +165,37 @@ impl<'a, 'db> CallArgumentTypes<'a, 'db> {
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (Argument<'a>, Type<'db>)> + '_ {
|
||||
self.arguments.iter().zip(self.types.iter().copied())
|
||||
}
|
||||
|
||||
/// Unpacks any subscript tuple arguments into distinct arguments.
|
||||
pub(crate) fn unpack_subscript_tuples(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
) -> Cow<'_, CallArgumentTypes<'a, 'db>> {
|
||||
// If there are no subscript tuples, we can use the existing argument list as-is.
|
||||
if self
|
||||
.arguments
|
||||
.iter()
|
||||
.all(|argument| !matches!(argument, Argument::PositionalSubscriptTuple(_)))
|
||||
{
|
||||
return Cow::Borrowed(self);
|
||||
}
|
||||
|
||||
let mut types = VecDeque::with_capacity(self.types.len());
|
||||
for (argument, ty) in self.iter() {
|
||||
match (argument, ty) {
|
||||
(Argument::PositionalSubscriptTuple(_), Type::Tuple(tuple)) => {
|
||||
for ty in tuple.iter(db) {
|
||||
types.push_back(ty);
|
||||
}
|
||||
}
|
||||
_ => types.push_back(ty),
|
||||
}
|
||||
}
|
||||
Cow::Owned(CallArgumentTypes {
|
||||
arguments: self.arguments.unpack_subscript_tuples().into_owned(),
|
||||
types,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for CallArgumentTypes<'a, '_> {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
//! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a
|
||||
//! union of types, each of which might contain multiple overloads.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::{
|
||||
@@ -16,7 +18,7 @@ use crate::types::diagnostic::{
|
||||
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS,
|
||||
UNKNOWN_ARGUMENT,
|
||||
};
|
||||
use crate::types::signatures::{Parameter, ParameterForm};
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
||||
use crate::types::{
|
||||
todo_type, BoundMethodType, CallableType, ClassLiteralType, KnownClass, KnownFunction,
|
||||
KnownInstanceType, UnionType,
|
||||
@@ -321,6 +323,11 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Type::Callable(CallableType::SpecializeClass(class)) => {
|
||||
// TODO: Actually, you know, specialize the class
|
||||
overload.set_return_type(Type::class_literal(class));
|
||||
}
|
||||
|
||||
Type::FunctionLiteral(function_type) => match function_type.known(db) {
|
||||
Some(KnownFunction::IsEquivalentTo) => {
|
||||
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
|
||||
@@ -542,10 +549,12 @@ impl<'db> CallableBinding<'db> {
|
||||
// two phases.
|
||||
//
|
||||
// [1] https://github.com/python/typing/pull/1839
|
||||
let callable_type = signature.callable_type;
|
||||
let overloads = signature
|
||||
.into_iter()
|
||||
.map(|signature| {
|
||||
Binding::match_parameters(
|
||||
callable_type,
|
||||
signature,
|
||||
arguments,
|
||||
argument_forms,
|
||||
@@ -572,8 +581,9 @@ impl<'db> CallableBinding<'db> {
|
||||
// If this callable is a bound method, prepend the self instance onto the arguments list
|
||||
// before checking.
|
||||
argument_types.with_self(signature.bound_type, |argument_types| {
|
||||
let callable_type = signature.callable_type;
|
||||
for (signature, overload) in signature.iter().zip(&mut self.overloads) {
|
||||
overload.check_types(db, signature, argument_types);
|
||||
overload.check_types(db, callable_type, signature, argument_types);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -711,102 +721,190 @@ pub(crate) struct Binding<'db> {
|
||||
|
||||
impl<'db> Binding<'db> {
|
||||
fn match_parameters(
|
||||
callable_type: Type<'db>,
|
||||
signature: &Signature<'db>,
|
||||
arguments: &CallArguments<'_>,
|
||||
argument_forms: &mut [Option<ParameterForm>],
|
||||
conflicting_forms: &mut [bool],
|
||||
) -> Self {
|
||||
// Special case: you explicitly specialize a generic class via a subscript expression,
|
||||
// Class[T1, T2, ...]. This ends up calling a `__class_getitem__` method, defined on
|
||||
// `type`, which specializes the class. Like all subscript expressions, multiple arguments
|
||||
// are packed into a tuple before calling `__class_getitem__`.
|
||||
//
|
||||
// We would rather treat this as `__class_getitem__` taking in a distinct parameter for
|
||||
// each type variable. This gives us better error messages if parameter matching fails, and
|
||||
// makes it easier to type-check the arguments against any type parameter bounds or
|
||||
// constraints.
|
||||
let unpack_subscript_tuples = matches!(
|
||||
callable_type,
|
||||
Type::Callable(CallableType::SpecializeClass(_))
|
||||
);
|
||||
|
||||
BindingBuilder::new(
|
||||
signature,
|
||||
arguments.len(),
|
||||
argument_forms,
|
||||
conflicting_forms,
|
||||
unpack_subscript_tuples,
|
||||
)
|
||||
.build(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
struct BindingBuilder<'a, 'db> {
|
||||
signature: &'a Signature<'db>,
|
||||
argument_forms: &'a mut [Option<ParameterForm>],
|
||||
conflicting_forms: &'a mut [bool],
|
||||
parameters: &'a Parameters<'db>,
|
||||
/// The parameter that each argument is matched with.
|
||||
argument_parameters: Vec<Option<usize>>,
|
||||
/// Whether each parameter has been matched with an argument.
|
||||
parameter_matched: Vec<bool>,
|
||||
errors: Vec<BindingError<'db>>,
|
||||
next_positional: usize,
|
||||
first_excess_positional: Option<usize>,
|
||||
num_synthetic_args: usize,
|
||||
unpack_subscript_tuples: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'db> BindingBuilder<'a, 'db> {
|
||||
fn new(
|
||||
signature: &'a Signature<'db>,
|
||||
argument_count: usize,
|
||||
argument_forms: &'a mut [Option<ParameterForm>],
|
||||
conflicting_forms: &'a mut [bool],
|
||||
unpack_subscript_tuples: bool,
|
||||
) -> Self {
|
||||
let parameters = signature.parameters();
|
||||
// The parameter that each argument is matched with.
|
||||
let mut argument_parameters = vec![None; arguments.len()];
|
||||
// Whether each parameter has been matched with an argument.
|
||||
let mut parameter_matched = vec![false; parameters.len()];
|
||||
let mut errors = vec![];
|
||||
let mut next_positional = 0;
|
||||
let mut first_excess_positional = None;
|
||||
let mut num_synthetic_args = 0;
|
||||
let get_argument_index = |argument_index: usize, num_synthetic_args: usize| {
|
||||
if argument_index >= num_synthetic_args {
|
||||
// Adjust the argument index to skip synthetic args, which don't appear at the call
|
||||
// site and thus won't be in the Call node arguments list.
|
||||
Some(argument_index - num_synthetic_args)
|
||||
} else {
|
||||
// we are erroring on a synthetic argument, we'll just emit the diagnostic on the
|
||||
// entire Call node, since there's no argument node for this argument at the call site
|
||||
None
|
||||
}
|
||||
|
||||
Self {
|
||||
signature,
|
||||
argument_forms,
|
||||
conflicting_forms,
|
||||
parameters,
|
||||
argument_parameters: vec![None; argument_count],
|
||||
parameter_matched: vec![false; parameters.len()],
|
||||
errors: vec![],
|
||||
next_positional: 0,
|
||||
first_excess_positional: None,
|
||||
num_synthetic_args: 0,
|
||||
unpack_subscript_tuples,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_argument_index(&self, argument_index: usize) -> Option<usize> {
|
||||
if argument_index >= self.num_synthetic_args {
|
||||
// Adjust the argument index to skip synthetic args, which don't appear at the call
|
||||
// site and thus won't be in the Call node arguments list.
|
||||
Some(argument_index - self.num_synthetic_args)
|
||||
} else {
|
||||
// we are erroring on a synthetic argument, we'll just emit the diagnostic on the
|
||||
// entire Call node, since there's no argument node for this argument at the call site
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn match_positional(&mut self, argument_index: usize, is_synthetic: bool) {
|
||||
if is_synthetic {
|
||||
self.num_synthetic_args += 1;
|
||||
}
|
||||
let Some((index, parameter)) = self
|
||||
.parameters
|
||||
.get_positional(self.next_positional)
|
||||
.map(|param| (self.next_positional, param))
|
||||
.or_else(|| self.parameters.variadic())
|
||||
else {
|
||||
self.first_excess_positional.get_or_insert(argument_index);
|
||||
self.next_positional += 1;
|
||||
return;
|
||||
};
|
||||
self.next_positional += 1;
|
||||
self.assign_parameter(
|
||||
argument_index,
|
||||
is_synthetic,
|
||||
index,
|
||||
parameter,
|
||||
!parameter.is_variadic(),
|
||||
);
|
||||
}
|
||||
|
||||
fn match_keyword(&mut self, argument_index: usize, name: &'a str) {
|
||||
let Some((index, parameter)) = self
|
||||
.parameters
|
||||
.keyword_by_name(name)
|
||||
.or_else(|| self.parameters.keyword_variadic())
|
||||
else {
|
||||
self.errors.push(BindingError::UnknownArgument {
|
||||
argument_name: ast::name::Name::new(name),
|
||||
argument_index: self.get_argument_index(argument_index),
|
||||
});
|
||||
return;
|
||||
};
|
||||
self.assign_parameter(argument_index, false, index, parameter, false);
|
||||
}
|
||||
|
||||
fn assign_parameter(
|
||||
&mut self,
|
||||
argument_index: usize,
|
||||
is_synthetic: bool,
|
||||
index: usize,
|
||||
parameter: &Parameter,
|
||||
positional: bool,
|
||||
) {
|
||||
if !is_synthetic {
|
||||
if let Some(existing) = self.argument_forms[argument_index - self.num_synthetic_args]
|
||||
.replace(parameter.form)
|
||||
{
|
||||
if existing != parameter.form {
|
||||
self.conflicting_forms[argument_index - self.num_synthetic_args] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.parameter_matched[index] {
|
||||
if !parameter.is_variadic() && !parameter.is_keyword_variadic() {
|
||||
self.errors.push(BindingError::ParameterAlreadyAssigned {
|
||||
argument_index: self.get_argument_index(argument_index),
|
||||
parameter: ParameterContext::new(parameter, index, positional),
|
||||
});
|
||||
}
|
||||
}
|
||||
self.argument_parameters[argument_index] = Some(index);
|
||||
self.parameter_matched[index] = true;
|
||||
}
|
||||
|
||||
fn build(mut self, arguments: &'a CallArguments<'_>) -> Binding<'db> {
|
||||
for (argument_index, argument) in arguments.iter().enumerate() {
|
||||
let (index, parameter, positional) = match argument {
|
||||
Argument::Positional | Argument::Synthetic => {
|
||||
if matches!(argument, Argument::Synthetic) {
|
||||
num_synthetic_args += 1;
|
||||
match argument {
|
||||
Argument::PositionalSubscriptTuple(count) if self.unpack_subscript_tuples => {
|
||||
for _ in 0..count {
|
||||
self.match_positional(argument_index, false);
|
||||
}
|
||||
let Some((index, parameter)) = parameters
|
||||
.get_positional(next_positional)
|
||||
.map(|param| (next_positional, param))
|
||||
.or_else(|| parameters.variadic())
|
||||
else {
|
||||
first_excess_positional.get_or_insert(argument_index);
|
||||
next_positional += 1;
|
||||
continue;
|
||||
};
|
||||
next_positional += 1;
|
||||
(index, parameter, !parameter.is_variadic())
|
||||
}
|
||||
Argument::Keyword(name) => {
|
||||
let Some((index, parameter)) = parameters
|
||||
.keyword_by_name(name)
|
||||
.or_else(|| parameters.keyword_variadic())
|
||||
else {
|
||||
errors.push(BindingError::UnknownArgument {
|
||||
argument_name: ast::name::Name::new(name),
|
||||
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
||||
});
|
||||
continue;
|
||||
};
|
||||
(index, parameter, false)
|
||||
Argument::Positional | Argument::PositionalSubscriptTuple(_) => {
|
||||
self.match_positional(argument_index, false)
|
||||
}
|
||||
Argument::Synthetic => self.match_positional(argument_index, true),
|
||||
Argument::Keyword(name) => self.match_keyword(argument_index, name),
|
||||
|
||||
Argument::Variadic | Argument::Keywords => {
|
||||
// TODO
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if !matches!(argument, Argument::Synthetic) {
|
||||
if let Some(existing) =
|
||||
argument_forms[argument_index - num_synthetic_args].replace(parameter.form)
|
||||
{
|
||||
if existing != parameter.form {
|
||||
conflicting_forms[argument_index - num_synthetic_args] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if parameter_matched[index] {
|
||||
if !parameter.is_variadic() && !parameter.is_keyword_variadic() {
|
||||
errors.push(BindingError::ParameterAlreadyAssigned {
|
||||
argument_index: get_argument_index(argument_index, num_synthetic_args),
|
||||
parameter: ParameterContext::new(parameter, index, positional),
|
||||
});
|
||||
}
|
||||
}
|
||||
argument_parameters[argument_index] = Some(index);
|
||||
parameter_matched[index] = true;
|
||||
}
|
||||
if let Some(first_excess_argument_index) = first_excess_positional {
|
||||
errors.push(BindingError::TooManyPositionalArguments {
|
||||
first_excess_argument_index: get_argument_index(
|
||||
first_excess_argument_index,
|
||||
num_synthetic_args,
|
||||
),
|
||||
expected_positional_count: parameters.positional().count(),
|
||||
provided_positional_count: next_positional,
|
||||
|
||||
if let Some(first_excess_argument_index) = self.first_excess_positional {
|
||||
self.errors.push(BindingError::TooManyPositionalArguments {
|
||||
first_excess_argument_index: self.get_argument_index(first_excess_argument_index),
|
||||
expected_positional_count: self.parameters.positional().count(),
|
||||
provided_positional_count: self.next_positional,
|
||||
});
|
||||
}
|
||||
let mut missing = vec![];
|
||||
for (index, matched) in parameter_matched.iter().copied().enumerate() {
|
||||
for (index, matched) in self.parameter_matched.iter().copied().enumerate() {
|
||||
if !matched {
|
||||
let param = ¶meters[index];
|
||||
let param = &self.parameters[index];
|
||||
if param.is_variadic()
|
||||
|| param.is_keyword_variadic()
|
||||
|| param.default_type().is_some()
|
||||
@@ -819,25 +917,46 @@ impl<'db> Binding<'db> {
|
||||
}
|
||||
|
||||
if !missing.is_empty() {
|
||||
errors.push(BindingError::MissingArguments {
|
||||
self.errors.push(BindingError::MissingArguments {
|
||||
parameters: ParameterContexts(missing),
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
return_ty: signature.return_ty.unwrap_or(Type::unknown()),
|
||||
argument_parameters: argument_parameters.into_boxed_slice(),
|
||||
parameter_tys: vec![None; parameters.len()].into_boxed_slice(),
|
||||
errors,
|
||||
Binding {
|
||||
return_ty: self.signature.return_ty.unwrap_or(Type::unknown()),
|
||||
argument_parameters: self.argument_parameters.into_boxed_slice(),
|
||||
parameter_tys: vec![None; self.parameters.len()].into_boxed_slice(),
|
||||
errors: self.errors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Binding<'db> {
|
||||
fn check_types(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
callable_type: Type<'db>,
|
||||
signature: &Signature<'db>,
|
||||
argument_types: &CallArgumentTypes<'_, 'db>,
|
||||
) {
|
||||
// Special case: you explicitly specialize a generic class via a subscript expression,
|
||||
// Class[T1, T2, ...]. This ends up calling a `__class_getitem__` method, defined on
|
||||
// `type`, which specializes the class. Like all subscript expressions, multiple arguments
|
||||
// are packed into a tuple before calling `__class_getitem__`.
|
||||
//
|
||||
// We would rather treat this as `__class_getitem__` taking in a distinct parameter for
|
||||
// each type variable. This gives us better error messages if parameter matching fails, and
|
||||
// makes it easier to type-check the arguments against any type parameter bounds or
|
||||
// constraints.
|
||||
let argument_types = if matches!(
|
||||
callable_type,
|
||||
Type::Callable(CallableType::SpecializeClass(_))
|
||||
) {
|
||||
argument_types.unpack_subscript_tuples(db)
|
||||
} else {
|
||||
Cow::Borrowed(argument_types)
|
||||
};
|
||||
|
||||
let parameters = signature.parameters();
|
||||
let mut num_synthetic_args = 0;
|
||||
let get_argument_index = |argument_index: usize, num_synthetic_args: usize| {
|
||||
@@ -949,6 +1068,10 @@ impl<'db> CallableDescription<'db> {
|
||||
kind: "wrapper descriptor",
|
||||
name: "FunctionType.__get__",
|
||||
}),
|
||||
Type::Callable(CallableType::SpecializeClass(class)) => Some(CallableDescription {
|
||||
kind: "explicit specialization of class",
|
||||
name: class.name(db),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use super::{
|
||||
KnownFunction, Mro, MroError, MroIterator, SubclassOfType, Truthiness, Type, TypeAliasType,
|
||||
TypeQualifiers, TypeVarInstance,
|
||||
};
|
||||
use crate::types::generics::GenericContext;
|
||||
|
||||
/// Representation of a runtime class object.
|
||||
///
|
||||
@@ -38,6 +39,7 @@ pub struct Class<'db> {
|
||||
#[return_ref]
|
||||
pub(crate) name: ast::name::Name,
|
||||
|
||||
pub(crate) generic_context: Option<GenericContext<'db>>,
|
||||
body_scope: ScopeId<'db>,
|
||||
|
||||
pub(crate) known: Option<KnownClass>,
|
||||
|
||||
@@ -110,6 +110,13 @@ impl Display for DisplayRepresentation<'_> {
|
||||
Type::Callable(CallableType::WrapperDescriptorDunderGet) => {
|
||||
f.write_str("<wrapper-descriptor `__get__` of `function` objects>")
|
||||
}
|
||||
Type::Callable(CallableType::SpecializeClass(class)) => {
|
||||
write!(
|
||||
f,
|
||||
"<specialize-generic of `{class}`>",
|
||||
class = class.name(self.db)
|
||||
)
|
||||
}
|
||||
Type::Union(union) => union.display(self.db).fmt(f),
|
||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||
Type::IntLiteral(n) => n.fmt(f),
|
||||
|
||||
77
crates/red_knot_python_semantic/src/types/generics.rs
Normal file
77
crates/red_knot_python_semantic/src/types/generics.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||
use crate::types::{
|
||||
declaration_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
UnionType,
|
||||
};
|
||||
use crate::Db;
|
||||
|
||||
/// A list of formal type variables for a generic function, class, or type alias.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct GenericContext<'db> {
|
||||
variables: Box<[TypeVarInstance<'db>]>,
|
||||
}
|
||||
|
||||
impl<'db> GenericContext<'db> {
|
||||
pub(crate) fn from_type_params(
|
||||
db: &'db dyn Db,
|
||||
index: &'db SemanticIndex<'db>,
|
||||
type_params_node: &ast::TypeParams,
|
||||
) -> Self {
|
||||
let variables = type_params_node
|
||||
.iter()
|
||||
.filter_map(|type_param| Self::variable_from_type_param(db, index, type_param))
|
||||
.collect();
|
||||
Self { variables }
|
||||
}
|
||||
|
||||
fn variable_from_type_param(
|
||||
db: &'db dyn Db,
|
||||
index: &'db SemanticIndex<'db>,
|
||||
type_param_node: &ast::TypeParam,
|
||||
) -> Option<TypeVarInstance<'db>> {
|
||||
match type_param_node {
|
||||
ast::TypeParam::TypeVar(node) => {
|
||||
let definition = index.definition(node);
|
||||
let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) =
|
||||
declaration_type(db, definition).inner_type()
|
||||
else {
|
||||
panic!("typevar should be inferred as a TypeVarInstance");
|
||||
};
|
||||
Some(typevar)
|
||||
}
|
||||
// TODO: Support these!
|
||||
ast::TypeParam::ParamSpec(_) => None,
|
||||
ast::TypeParam::TypeVarTuple(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn signature(&self, db: &'db dyn Db, class: Type<'db>) -> Signature<'db> {
|
||||
let parameters = Parameters::new(
|
||||
std::iter::once(Parameter::positional_only(None).with_annotated_type(class)).chain(
|
||||
self.variables
|
||||
.iter()
|
||||
.map(|typevar| Self::parameter_from_typevar(db, typevar)),
|
||||
),
|
||||
);
|
||||
Signature::new(parameters, None)
|
||||
}
|
||||
|
||||
fn parameter_from_typevar(db: &'db dyn Db, typevar: &TypeVarInstance<'db>) -> Parameter<'db> {
|
||||
let mut parameter = Parameter::positional_only(Some(typevar.name(db).clone()));
|
||||
match typevar.bound_or_constraints(db) {
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(_)) => {
|
||||
// TODO: This should be TypeForm[bound]
|
||||
parameter = parameter.with_annotated_type(Type::any());
|
||||
}
|
||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||
parameter = parameter
|
||||
.with_annotated_type(UnionType::from_elements(db, constraints.iter(db)));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
parameter
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ use crate::symbol::{
|
||||
typing_extensions_symbol, Boundness, LookupError,
|
||||
};
|
||||
use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError};
|
||||
use crate::types::class::{ClassLiteralType, MetaclassErrorKind};
|
||||
use crate::types::diagnostic::{
|
||||
report_implicit_return_type, report_invalid_arguments_to_annotated,
|
||||
report_invalid_arguments_to_callable, report_invalid_assignment,
|
||||
@@ -76,15 +77,15 @@ use crate::types::diagnostic::{
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPORT, UNDEFINED_REVEAL,
|
||||
UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
|
||||
};
|
||||
use crate::types::generics::GenericContext;
|
||||
use crate::types::mro::MroErrorKind;
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
class::MetaclassErrorKind, todo_type, Class, DynamicType, FunctionType, InstanceType,
|
||||
IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType,
|
||||
MetaclassCandidate, Parameter, ParameterForm, Parameters, SliceLiteralType, SubclassOfType,
|
||||
Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
|
||||
TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder,
|
||||
UnionType,
|
||||
todo_type, Class, DynamicType, FunctionType, InstanceType, IntersectionBuilder,
|
||||
IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, Parameter,
|
||||
ParameterForm, Parameters, SliceLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers,
|
||||
Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay,
|
||||
TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
|
||||
};
|
||||
use crate::types::{CallableType, GeneralCallableType, Signature};
|
||||
use crate::unpack::Unpack;
|
||||
@@ -1633,6 +1634,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_decorator(decorator);
|
||||
}
|
||||
|
||||
let generic_context = type_params.as_ref().map(|type_params| {
|
||||
GenericContext::from_type_params(self.db(), self.index, type_params)
|
||||
});
|
||||
|
||||
let body_scope = self
|
||||
.index
|
||||
.node_scope(NodeWithScopeRef::Class(class_node))
|
||||
@@ -1640,7 +1645,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
let maybe_known_class = KnownClass::try_from_file_and_name(self.db(), self.file(), name);
|
||||
|
||||
let class = Class::new(self.db(), &name.id, body_scope, maybe_known_class);
|
||||
let class = Class::new(
|
||||
self.db(),
|
||||
&name.id,
|
||||
generic_context,
|
||||
body_scope,
|
||||
maybe_known_class,
|
||||
);
|
||||
let class_ty = Type::class_literal(class);
|
||||
|
||||
self.add_declaration_with_binding(
|
||||
@@ -1963,7 +1974,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let tuple = TupleType::new(
|
||||
self.db(),
|
||||
elts.iter()
|
||||
.map(|expr| self.infer_type_expression(expr))
|
||||
.map(|expr| self.infer_expression(expr))
|
||||
.collect::<Box<_>>(),
|
||||
);
|
||||
let constraints = TypeVarBoundOrConstraints::Constraints(tuple);
|
||||
@@ -5700,11 +5711,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// If the class defines `__getitem__`, return its return type.
|
||||
//
|
||||
// See: https://docs.python.org/3/reference/datamodel.html#class-getitem-versus-getitem
|
||||
match value_ty.try_call_dunder(
|
||||
let arguments = CallArgumentTypes::from_arguments([Argument::subscript_argument(
|
||||
self.db(),
|
||||
"__getitem__",
|
||||
CallArgumentTypes::positional([slice_ty]),
|
||||
) {
|
||||
slice_ty,
|
||||
)]);
|
||||
match value_ty.try_call_dunder(self.db(), "__getitem__", arguments) {
|
||||
Ok(outcome) => return outcome.return_type(self.db()),
|
||||
Err(err @ CallDunderError::PossiblyUnbound { .. }) => {
|
||||
self.context.report_lint(
|
||||
@@ -5763,30 +5774,24 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
);
|
||||
}
|
||||
|
||||
match ty.try_call(
|
||||
self.db(),
|
||||
CallArgumentTypes::positional([value_ty, slice_ty]),
|
||||
) {
|
||||
let arguments = CallArgumentTypes::from_arguments([
|
||||
(Argument::Synthetic, value_ty),
|
||||
Argument::subscript_argument(self.db(), slice_ty),
|
||||
]);
|
||||
match ty.try_call(self.db(), arguments) {
|
||||
Ok(bindings) => return bindings.return_type(self.db()),
|
||||
Err(CallError(_, bindings)) => {
|
||||
self.context.report_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
value_node,
|
||||
format_args!(
|
||||
"Method `__class_getitem__` of type `{}` is not callable on object of type `{}`",
|
||||
bindings.callable_type().display(self.db()),
|
||||
value_ty.display(self.db()),
|
||||
),
|
||||
);
|
||||
bindings.report_diagnostics(&self.context, value_node.into());
|
||||
return bindings.return_type(self.db());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(value_ty, Type::ClassLiteral(class_literal) if class_literal.class().is_known(self.db(), KnownClass::Type))
|
||||
{
|
||||
return KnownClass::GenericAlias.to_instance(self.db());
|
||||
if let Type::ClassLiteral(ClassLiteralType { class }) = value_ty {
|
||||
if class.is_known(self.db(), KnownClass::Type) {
|
||||
return KnownClass::GenericAlias.to_instance(self.db());
|
||||
}
|
||||
}
|
||||
|
||||
report_non_subscriptable(
|
||||
@@ -5809,6 +5814,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// TODO: proper support for generic classes
|
||||
// For now, just infer `Sequence`, if we see something like `Sequence[str]`. This allows us
|
||||
// to look up attributes on generic base classes, even if we don't understand generics yet.
|
||||
// Note that this isn't handled by the clause up above for generic classes
|
||||
// that use legacy type variables and an explicit `Generic` base class.
|
||||
// Once we handle legacy typevars, this special case will be removed in
|
||||
// favor of the specialization logic above.
|
||||
value_ty
|
||||
}
|
||||
_ => Type::unknown(),
|
||||
@@ -7490,7 +7499,7 @@ mod tests {
|
||||
|
||||
check_typevar("T", None, None, None);
|
||||
check_typevar("U", Some("A"), None, None);
|
||||
check_typevar("V", None, Some(&["A", "B"]), None);
|
||||
check_typevar("V", None, Some(&["Literal[A]", "Literal[B]"]), None);
|
||||
check_typevar("W", None, None, Some("A"));
|
||||
check_typevar("X", Some("A"), None, Some("A1"));
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
) => Ordering::Equal,
|
||||
(Type::Callable(CallableType::WrapperDescriptorDunderGet), _) => Ordering::Less,
|
||||
(_, Type::Callable(CallableType::WrapperDescriptorDunderGet)) => Ordering::Greater,
|
||||
(Type::Callable(CallableType::SpecializeClass(_)), _) => Ordering::Less,
|
||||
(_, Type::Callable(CallableType::SpecializeClass(_))) => Ordering::Greater,
|
||||
|
||||
(Type::Callable(CallableType::General(_)), Type::Callable(CallableType::General(_))) => {
|
||||
Ordering::Equal
|
||||
|
||||
Reference in New Issue
Block a user