From 97058e80930969d63bc2cdc6fcff8f84a2d7de4e Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Mon, 19 May 2025 11:45:40 -0400 Subject: [PATCH] [ty] Infer function call typevars in both directions (#18155) This primarily comes up with annotated `self` parameters in constructors: ```py class C[T]: def __init__(self: C[int]): ... ``` Here, we want infer a specialization of `{T = int}` for a call that hits this overload. Normally when inferring a specialization of a function call, typevars appear in the parameter annotations, and not in the argument types. In this case, this is reversed: we need to verify that the `self` argument (`C[T]`, as we have not yet completed specialization inference) is assignable to the parameter type `C[int]`. To do this, we simply look for a typevar/type in both directions when performing inference, and apply the inferred specialization to argument types as well as parameter types before verifying assignability. As a wrinkle, this exposed that we were not checking subtyping/assignability for function literals correctly. Our function literal representation includes an optional specialization that should be applied to the signature. Before, function literals were considered subtypes of (assignable to) each other only if they were identical Salsa objects. Two function literals with different specializations should still be considered subtypes of (assignable to) each other if those specializations result in the same function signature (typically because the function doesn't use the typevars in the specialization). Closes https://github.com/astral-sh/ty/issues/370 Closes https://github.com/astral-sh/ty/issues/100 Closes https://github.com/astral-sh/ty/issues/258 --------- Co-authored-by: Carl Meyer --- .../mdtest/generics/legacy/classes.md | 33 +++ .../mdtest/generics/pep695/classes.md | 31 ++ .../resources/mdtest/generics/scoping.md | 2 +- crates/ty_python_semantic/src/types.rs | 270 +++++++++++++++++- .../ty_python_semantic/src/types/call/bind.rs | 5 +- .../ty_python_semantic/src/types/display.rs | 20 +- .../ty_python_semantic/src/types/generics.rs | 44 +-- .../src/types/signatures.rs | 14 +- 8 files changed, 366 insertions(+), 53 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 380452f6c2..186139e1c3 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -341,6 +341,39 @@ reveal_type(C(1, True)) # revealed: C[Literal[1]] wrong_innards: C[int] = C("five", 1) ``` +### Some `__init__` overloads only apply to certain specializations + +```py +from typing import overload, Generic, TypeVar + +T = TypeVar("T") + +class C(Generic[T]): + @overload + def __init__(self: "C[str]", x: str) -> None: ... + @overload + def __init__(self: "C[bytes]", x: bytes) -> None: ... + @overload + def __init__(self, x: int) -> None: ... + def __init__(self, x: str | bytes | int) -> None: ... + +reveal_type(C("string")) # revealed: C[str] +reveal_type(C(b"bytes")) # revealed: C[bytes] +reveal_type(C(12)) # revealed: C[Unknown] + +C[str]("string") +C[str](b"bytes") # error: [no-matching-overload] +C[str](12) + +C[bytes]("string") # error: [no-matching-overload] +C[bytes](b"bytes") +C[bytes](12) + +C[None]("string") # error: [no-matching-overload] +C[None](b"bytes") # error: [no-matching-overload] +C[None](12) +``` + ## Generic subclass When a generic subclass fills its superclass's type parameter with one of its own, the actual types diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index c5d4772d36..9d4c0a429d 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -279,6 +279,37 @@ reveal_type(C(1, True)) # revealed: C[Literal[1]] wrong_innards: C[int] = C("five", 1) ``` +### Some `__init__` overloads only apply to certain specializations + +```py +from typing import overload + +class C[T]: + @overload + def __init__(self: C[str], x: str) -> None: ... + @overload + def __init__(self: C[bytes], x: bytes) -> None: ... + @overload + def __init__(self, x: int) -> None: ... + def __init__(self, x: str | bytes | int) -> None: ... + +reveal_type(C("string")) # revealed: C[str] +reveal_type(C(b"bytes")) # revealed: C[bytes] +reveal_type(C(12)) # revealed: C[Unknown] + +C[str]("string") +C[str](b"bytes") # error: [no-matching-overload] +C[str](12) + +C[bytes]("string") # error: [no-matching-overload] +C[bytes](b"bytes") +C[bytes](12) + +C[None]("string") # error: [no-matching-overload] +C[None](b"bytes") # error: [no-matching-overload] +C[None](12) +``` + ## Generic subclass When a generic subclass fills its superclass's type parameter with one of its own, the actual types diff --git a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md index 4376f1db0d..90426ff6b1 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md @@ -102,7 +102,7 @@ class C[T]: return "a" reveal_type(getattr_static(C[int], "f")) # revealed: def f(self, x: int) -> str -reveal_type(getattr_static(C[int], "f").__get__) # revealed: +reveal_type(getattr_static(C[int], "f").__get__) # revealed: reveal_type(getattr_static(C[int], "f").__get__(None, C[int])) # revealed: def f(self, x: int) -> str # revealed: bound method C[int].f(x: int) -> str reveal_type(getattr_static(C[int], "f").__get__(C[int](), C[int])) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 15d675a869..ede7203475 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1170,6 +1170,21 @@ impl<'db> Type<'db> { target.is_equivalent_to(db, Type::object(db)) } + // These clauses handle type variants that include function literals. A function + // literal is the subtype of itself, and not of any other function literal. However, + // our representation of a function literal includes any specialization that should be + // applied to the signature. Different specializations of the same function literal are + // only subtypes of each other if they result in the same signature. + (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { + self_function.is_subtype_of(db, target_function) + } + (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { + self_method.is_subtype_of(db, target_method) + } + (Type::MethodWrapper(self_method), Type::MethodWrapper(target_method)) => { + self_method.is_subtype_of(db, target_method) + } + // No literal type is a subtype of any other literal type, unless they are the same // type (which is handled above). This case is not necessary from a correctness // perspective (the fallback cases below will handle it correctly), but it is important @@ -1504,6 +1519,21 @@ impl<'db> Type<'db> { true } + // These clauses handle type variants that include function literals. A function + // literal is assignable to itself, and not to any other function literal. However, our + // representation of a function literal includes any specialization that should be + // applied to the signature. Different specializations of the same function literal are + // only assignable to each other if they result in the same signature. + (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { + self_function.is_assignable_to(db, target_function) + } + (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { + self_method.is_assignable_to(db, target_method) + } + (Type::MethodWrapper(self_method), Type::MethodWrapper(target_method)) => { + self_method.is_assignable_to(db, target_method) + } + // `type[Any]` is assignable to any `type[...]` type, because `type[Any]` can // materialize to any `type[...]` type. (Type::SubclassOf(subclass_of_ty), Type::SubclassOf(_)) @@ -1627,6 +1657,15 @@ impl<'db> Type<'db> { left.is_equivalent_to(db, right) } (Type::Tuple(left), Type::Tuple(right)) => left.is_equivalent_to(db, right), + (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { + self_function.is_equivalent_to(db, target_function) + } + (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { + self_method.is_equivalent_to(db, target_method) + } + (Type::MethodWrapper(self_method), Type::MethodWrapper(target_method)) => { + self_method.is_equivalent_to(db, target_method) + } (Type::Callable(left), Type::Callable(right)) => left.is_equivalent_to(db, right), (Type::NominalInstance(left), Type::NominalInstance(right)) => { left.is_equivalent_to(db, right) @@ -1682,6 +1721,15 @@ impl<'db> Type<'db> { first.is_gradual_equivalent_to(db, second) } + (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { + self_function.is_gradual_equivalent_to(db, target_function) + } + (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { + self_method.is_gradual_equivalent_to(db, target_method) + } + (Type::MethodWrapper(self_method), Type::MethodWrapper(target_method)) => { + self_method.is_gradual_equivalent_to(db, target_method) + } (Type::Callable(first), Type::Callable(second)) => { first.is_gradual_equivalent_to(db, second) } @@ -6727,6 +6775,7 @@ impl<'db> FunctionType<'db> { /// would depend on the function's AST and rerun for every change in that file. #[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)] pub(crate) fn signature(self, db: &'db dyn Db) -> FunctionSignature<'db> { + let inherited_generic_context = self.inherited_generic_context(db); let specialization = self.specialization(db); if let Some(overloaded) = self.to_overloaded(db) { FunctionSignature { @@ -6734,13 +6783,13 @@ impl<'db> FunctionType<'db> { Type::FunctionLiteral(self), overloaded.overloads.iter().copied().map(|overload| { overload - .internal_signature(db) + .internal_signature(db, inherited_generic_context) .apply_optional_specialization(db, specialization) }), ), implementation: overloaded.implementation.map(|implementation| { implementation - .internal_signature(db) + .internal_signature(db, inherited_generic_context) .apply_optional_specialization(db, specialization) }), } @@ -6748,7 +6797,7 @@ impl<'db> FunctionType<'db> { FunctionSignature { overloads: CallableSignature::single( Type::FunctionLiteral(self), - self.internal_signature(db) + self.internal_signature(db, inherited_generic_context) .apply_optional_specialization(db, specialization), ), implementation: None, @@ -6766,7 +6815,11 @@ impl<'db> FunctionType<'db> { /// /// Don't call this when checking any other file; only when type-checking the function body /// scope. - fn internal_signature(self, db: &'db dyn Db) -> Signature<'db> { + fn internal_signature( + self, + db: &'db dyn Db, + inherited_generic_context: Option>, + ) -> Signature<'db> { let scope = self.body_scope(db); let function_stmt_node = scope.node(db).expect_function(); let definition = self.definition(db); @@ -6777,7 +6830,7 @@ impl<'db> FunctionType<'db> { Signature::from_function( db, generic_context, - self.inherited_generic_context(db), + inherited_generic_context, definition, function_stmt_node, ) @@ -6934,6 +6987,42 @@ impl<'db> FunctionType<'db> { } } + fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { + // A function literal is the subtype of itself, and not of any other function literal. + // However, our representation of a function literal includes any specialization that + // should be applied to the signature. Different specializations of the same function + // literal are only subtypes of each other if they result in subtype signatures. + self.body_scope(db) == other.body_scope(db) + && self + .into_callable_type(db) + .is_subtype_of(db, other.into_callable_type(db)) + } + + fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { + // A function literal is assignable to itself, and not to any other function literal. + // However, our representation of a function literal includes any specialization that + // should be applied to the signature. Different specializations of the same function + // literal are only assignable to each other if they result in assignable signatures. + self.body_scope(db) == other.body_scope(db) + && self + .into_callable_type(db) + .is_assignable_to(db, other.into_callable_type(db)) + } + + fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + self.body_scope(db) == other.body_scope(db) + && self + .into_callable_type(db) + .is_equivalent_to(db, other.into_callable_type(db)) + } + + fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + self.body_scope(db) == other.body_scope(db) + && self + .into_callable_type(db) + .is_gradual_equivalent_to(db, other.into_callable_type(db)) + } + /// Returns a tuple of two spans. The first is /// the span for the identifier of the function /// definition for `self`. The second is @@ -7215,6 +7304,43 @@ impl<'db> BoundMethodType<'db> { .map(signatures::Signature::bind_self), )) } + + fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { + // A bound method is a typically a subtype of itself. However, we must explicitly verify + // the subtyping of the underlying function signatures (since they might be specialized + // differently), and of the bound self parameter (taking care that parameters, including a + // bound self parameter, are contravariant.) + self.function(db).is_subtype_of(db, other.function(db)) + && other + .self_instance(db) + .is_subtype_of(db, self.self_instance(db)) + } + + fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { + // A bound method is a typically assignable to itself. However, we must explicitly verify + // the assignability of the underlying function signatures (since they might be specialized + // differently), and of the bound self parameter (taking care that parameters, including a + // bound self parameter, are contravariant.) + self.function(db).is_assignable_to(db, other.function(db)) + && other + .self_instance(db) + .is_assignable_to(db, self.self_instance(db)) + } + + fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + self.function(db).is_equivalent_to(db, other.function(db)) + && other + .self_instance(db) + .is_equivalent_to(db, self.self_instance(db)) + } + + fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + self.function(db) + .is_gradual_equivalent_to(db, other.function(db)) + && other + .self_instance(db) + .is_gradual_equivalent_to(db, self.self_instance(db)) + } } /// This type represents the set of all callable objects with a certain, possibly overloaded, @@ -7445,6 +7571,140 @@ pub enum MethodWrapperKind<'db> { StrStartswith(StringLiteralType<'db>), } +impl<'db> MethodWrapperKind<'db> { + fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { + match (self, other) { + ( + MethodWrapperKind::FunctionTypeDunderGet(self_function), + MethodWrapperKind::FunctionTypeDunderGet(other_function), + ) => self_function.is_subtype_of(db, other_function), + + ( + MethodWrapperKind::FunctionTypeDunderCall(self_function), + MethodWrapperKind::FunctionTypeDunderCall(other_function), + ) => self_function.is_subtype_of(db, other_function), + + (MethodWrapperKind::PropertyDunderGet(_), MethodWrapperKind::PropertyDunderGet(_)) + | (MethodWrapperKind::PropertyDunderSet(_), MethodWrapperKind::PropertyDunderSet(_)) + | (MethodWrapperKind::StrStartswith(_), MethodWrapperKind::StrStartswith(_)) => { + self == other + } + + ( + MethodWrapperKind::FunctionTypeDunderGet(_) + | MethodWrapperKind::FunctionTypeDunderCall(_) + | MethodWrapperKind::PropertyDunderGet(_) + | MethodWrapperKind::PropertyDunderSet(_) + | MethodWrapperKind::StrStartswith(_), + MethodWrapperKind::FunctionTypeDunderGet(_) + | MethodWrapperKind::FunctionTypeDunderCall(_) + | MethodWrapperKind::PropertyDunderGet(_) + | MethodWrapperKind::PropertyDunderSet(_) + | MethodWrapperKind::StrStartswith(_), + ) => false, + } + } + + fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { + match (self, other) { + ( + MethodWrapperKind::FunctionTypeDunderGet(self_function), + MethodWrapperKind::FunctionTypeDunderGet(other_function), + ) => self_function.is_assignable_to(db, other_function), + + ( + MethodWrapperKind::FunctionTypeDunderCall(self_function), + MethodWrapperKind::FunctionTypeDunderCall(other_function), + ) => self_function.is_assignable_to(db, other_function), + + (MethodWrapperKind::PropertyDunderGet(_), MethodWrapperKind::PropertyDunderGet(_)) + | (MethodWrapperKind::PropertyDunderSet(_), MethodWrapperKind::PropertyDunderSet(_)) + | (MethodWrapperKind::StrStartswith(_), MethodWrapperKind::StrStartswith(_)) => { + self == other + } + + ( + MethodWrapperKind::FunctionTypeDunderGet(_) + | MethodWrapperKind::FunctionTypeDunderCall(_) + | MethodWrapperKind::PropertyDunderGet(_) + | MethodWrapperKind::PropertyDunderSet(_) + | MethodWrapperKind::StrStartswith(_), + MethodWrapperKind::FunctionTypeDunderGet(_) + | MethodWrapperKind::FunctionTypeDunderCall(_) + | MethodWrapperKind::PropertyDunderGet(_) + | MethodWrapperKind::PropertyDunderSet(_) + | MethodWrapperKind::StrStartswith(_), + ) => false, + } + } + + fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + match (self, other) { + ( + MethodWrapperKind::FunctionTypeDunderGet(self_function), + MethodWrapperKind::FunctionTypeDunderGet(other_function), + ) => self_function.is_equivalent_to(db, other_function), + + ( + MethodWrapperKind::FunctionTypeDunderCall(self_function), + MethodWrapperKind::FunctionTypeDunderCall(other_function), + ) => self_function.is_equivalent_to(db, other_function), + + (MethodWrapperKind::PropertyDunderGet(_), MethodWrapperKind::PropertyDunderGet(_)) + | (MethodWrapperKind::PropertyDunderSet(_), MethodWrapperKind::PropertyDunderSet(_)) + | (MethodWrapperKind::StrStartswith(_), MethodWrapperKind::StrStartswith(_)) => { + self == other + } + + ( + MethodWrapperKind::FunctionTypeDunderGet(_) + | MethodWrapperKind::FunctionTypeDunderCall(_) + | MethodWrapperKind::PropertyDunderGet(_) + | MethodWrapperKind::PropertyDunderSet(_) + | MethodWrapperKind::StrStartswith(_), + MethodWrapperKind::FunctionTypeDunderGet(_) + | MethodWrapperKind::FunctionTypeDunderCall(_) + | MethodWrapperKind::PropertyDunderGet(_) + | MethodWrapperKind::PropertyDunderSet(_) + | MethodWrapperKind::StrStartswith(_), + ) => false, + } + } + + fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + match (self, other) { + ( + MethodWrapperKind::FunctionTypeDunderGet(self_function), + MethodWrapperKind::FunctionTypeDunderGet(other_function), + ) => self_function.is_gradual_equivalent_to(db, other_function), + + ( + MethodWrapperKind::FunctionTypeDunderCall(self_function), + MethodWrapperKind::FunctionTypeDunderCall(other_function), + ) => self_function.is_gradual_equivalent_to(db, other_function), + + (MethodWrapperKind::PropertyDunderGet(_), MethodWrapperKind::PropertyDunderGet(_)) + | (MethodWrapperKind::PropertyDunderSet(_), MethodWrapperKind::PropertyDunderSet(_)) + | (MethodWrapperKind::StrStartswith(_), MethodWrapperKind::StrStartswith(_)) => { + self == other + } + + ( + MethodWrapperKind::FunctionTypeDunderGet(_) + | MethodWrapperKind::FunctionTypeDunderCall(_) + | MethodWrapperKind::PropertyDunderGet(_) + | MethodWrapperKind::PropertyDunderSet(_) + | MethodWrapperKind::StrStartswith(_), + MethodWrapperKind::FunctionTypeDunderGet(_) + | MethodWrapperKind::FunctionTypeDunderCall(_) + | MethodWrapperKind::PropertyDunderGet(_) + | MethodWrapperKind::PropertyDunderSet(_) + | MethodWrapperKind::StrStartswith(_), + ) => false, + } + } +} + /// Represents a specific instance of `types.WrapperDescriptorType` #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, salsa::Update)] pub enum WrapperDescriptorKind { diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 990681c8da..7394436fcd 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1412,7 +1412,7 @@ impl<'db> Binding<'db> { } num_synthetic_args = 0; - for (argument_index, (argument, argument_type)) in argument_types.iter().enumerate() { + for (argument_index, (argument, mut argument_type)) in argument_types.iter().enumerate() { if matches!(argument, Argument::Synthetic) { num_synthetic_args += 1; } @@ -1424,9 +1424,12 @@ impl<'db> Binding<'db> { let parameter = ¶meters[parameter_index]; if let Some(mut expected_ty) = parameter.annotated_type() { if let Some(specialization) = self.specialization { + argument_type = argument_type.apply_specialization(db, specialization); expected_ty = expected_ty.apply_specialization(db, specialization); } if let Some(inherited_specialization) = self.inherited_specialization { + argument_type = + argument_type.apply_specialization(db, inherited_specialization); expected_ty = expected_ty.apply_specialization(db, inherited_specialization); } if !argument_type.is_assignable_to(db, expected_ty) { diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 8faca50825..d0f3e9a61e 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -170,31 +170,15 @@ impl Display for DisplayRepresentation<'_> { Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => { write!( f, - "", + "", function = function.name(self.db), - specialization = if let Some(specialization) = function.specialization(self.db) - { - specialization - .display_short(self.db, TupleSpecialization::No) - .to_string() - } else { - String::new() - }, ) } Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(function)) => { write!( f, - "", + "", function = function.name(self.db), - specialization = if let Some(specialization) = function.specialization(self.db) - { - specialization - .display_short(self.db, TupleSpecialization::No) - .to_string() - } else { - String::new() - }, ) } Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(_)) => { diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 2b9d12cba6..93c3b5bbd0 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -641,32 +641,34 @@ impl<'db> SpecializationBuilder<'db> { } match (formal, actual) { - (Type::TypeVar(typevar), _) => match typevar.bound_or_constraints(self.db) { - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - if !actual.is_assignable_to(self.db, bound) { - return Err(SpecializationError::MismatchedBound { + (Type::TypeVar(typevar), ty) | (ty, Type::TypeVar(typevar)) => { + match typevar.bound_or_constraints(self.db) { + Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { + if !ty.is_assignable_to(self.db, bound) { + return Err(SpecializationError::MismatchedBound { + typevar, + argument: ty, + }); + } + self.add_type_mapping(typevar, ty); + } + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { + for constraint in constraints.iter(self.db) { + if ty.is_assignable_to(self.db, *constraint) { + self.add_type_mapping(typevar, *constraint); + return Ok(()); + } + } + return Err(SpecializationError::MismatchedConstraint { typevar, - argument: actual, + argument: ty, }); } - self.add_type_mapping(typevar, actual); - } - Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { - for constraint in constraints.iter(self.db) { - if actual.is_assignable_to(self.db, *constraint) { - self.add_type_mapping(typevar, *constraint); - return Ok(()); - } + _ => { + self.add_type_mapping(typevar, ty); } - return Err(SpecializationError::MismatchedConstraint { - typevar, - argument: actual, - }); } - _ => { - self.add_type_mapping(typevar, actual); - } - }, + } (Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => { let formal_elements = formal_tuple.elements(self.db); diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 192db5d298..41552b85e6 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -1528,7 +1528,7 @@ mod tests { db.write_dedented("/src/a.py", "def f(): ...").unwrap(); let func = get_function_f(&db, "/src/a.py"); - let sig = func.internal_signature(&db); + let sig = func.internal_signature(&db, None); assert!(sig.return_ty.is_none()); assert_params(&sig, &[]); @@ -1551,7 +1551,7 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.py"); - let sig = func.internal_signature(&db); + let sig = func.internal_signature(&db, None); assert_eq!(sig.return_ty.unwrap().display(&db).to_string(), "bytes"); assert_params( @@ -1602,7 +1602,7 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.py"); - let sig = func.internal_signature(&db); + let sig = func.internal_signature(&db, None); let [ Parameter { @@ -1638,7 +1638,7 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.pyi"); - let sig = func.internal_signature(&db); + let sig = func.internal_signature(&db, None); let [ Parameter { @@ -1674,7 +1674,7 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.py"); - let sig = func.internal_signature(&db); + let sig = func.internal_signature(&db, None); let [ Parameter { @@ -1720,7 +1720,7 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.pyi"); - let sig = func.internal_signature(&db); + let sig = func.internal_signature(&db, None); let [ Parameter { @@ -1756,7 +1756,7 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.py"); - let expected_sig = func.internal_signature(&db); + let expected_sig = func.internal_signature(&db, None); // With no decorators, internal and external signature are the same assert_eq!(