From f0f60805deaf6d75fa3a81a85d93cc7484f6319a Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 10 Dec 2025 11:21:05 +0100 Subject: [PATCH] Fix assignability of P[int] to type[P] and type[P[int]] --- .../resources/mdtest/type_of/generics.md | 61 ++++++++++++++++++- crates/ty_python_semantic/src/types.rs | 12 ++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/mdtest/type_of/generics.md b/crates/ty_python_semantic/resources/mdtest/type_of/generics.md index a4ce1048eb..f99f0a10b7 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_of/generics.md +++ b/crates/ty_python_semantic/resources/mdtest/type_of/generics.md @@ -356,8 +356,16 @@ def expects_type_p_of_int(x: type[P[int]]): # OK, the default specialization of `P` is assignable to `type[P[Unknown]]` expects_type_p(P) -# also OK, because the default specialization is `P[Unknown]` which is assignable to `P[int]` +# Also OK, because `P[int]` and `P[str]` are both assignable to `P[Unknown]` +expects_type_p(P[int]) +expects_type_p(P[str]) + +# Also OK, because the default specialization is `P[Unknown]` which is assignable to `P[int]` expects_type_p_of_int(P) +expects_type_p_of_int(P[int]) + +# Not OK, because `P[str]` is not assignable to `P[int]` +expects_type_p_of_int(P[str]) # error: [invalid-argument-type] ``` The same principles apply when typevar defaults are used, but the results are a bit different @@ -390,3 +398,54 @@ expects_type_p(P[int]) # error: [invalid-argument-type] expects_type_p_of_int(P[str]) # error: [invalid-argument-type] expects_type_p_of_str(P[int]) # error: [invalid-argument-type] ``` + +This also works with `ParamSpec`: + +```py +@final +class C[**P]: ... + +def expects_type_c(f: type[C]): ... +def expects_type_c_of_int_and_str(x: type[C[int, str]]): ... + +# OK, the unspecialized `C` is assignable to `type[C[...]]` +expects_type_c(C) + +# Also OK, any specialization is assignable to the unspecialized `C` +expects_type_c(C[int]) +expects_type_c(C[str, int, bytes]) + +# Ok, the unspecialized `C` is assignable to `type[C[int, str]]` +expects_type_c_of_int_and_str(C) + +# Also OK, the specialized `C[int, str]` is assignable to `type[C[int, str]]` +expects_type_c_of_int_and_str(C[int, str]) + +# TODO: these should be errors +expects_type_c_of_int_and_str(C[str]) +expects_type_c_of_int_and_str(C[int, str, bytes]) +expects_type_c_of_int_and_str(C[str, int]) +``` + +And with a `ParamSpec` that has a default: + +```py +@final +class C[**P = [int, str]]: ... + +def expects_type_c_default(f: type[C]): ... +def expects_type_c_default_of_int(f: type[C[int]]): ... +def expects_type_c_default_of_int_str(f: type[C[int, str]]): ... + +expects_type_c_default(C) +expects_type_c_default(C[int, str]) +expects_type_c_default_of_int(C) +expects_type_c_default_of_int(C[int]) +expects_type_c_default_of_int_str(C) +expects_type_c_default_of_int_str(C[int, str]) + +# TODO: these should be errors +expects_type_c_default(C[int]) +expects_type_c_default_of_int(C[str]) +expects_type_c_default_of_int_str(C[str, int]) +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 5ef0294da4..8e42a0224f 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2725,6 +2725,18 @@ impl<'db> Type<'db> { ) } + // For generic aliases, we delegate to the underlying class type. + (Type::GenericAlias(self_alias), Type::GenericAlias(target_alias)) => { + ClassType::from(self_alias).has_relation_to_impl( + db, + ClassType::from(target_alias), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + } + (Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty .subclass_of() .into_class(db)