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 9e0696a5c23da..bc6ccdb7c1d05 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -301,6 +301,7 @@ consistent with each other. ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") @@ -308,6 +309,11 @@ class C(Generic[T]): def __new__(cls, x: T) -> "C[T]": return object.__new__(cls) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1)) # revealed: C[int] # error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`" @@ -318,12 +324,18 @@ wrong_innards: C[int] = C("five") ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") class C(Generic[T]): def __init__(self, x: T) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1)) # revealed: C[int] # error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`" @@ -334,6 +346,7 @@ wrong_innards: C[int] = C("five") ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") @@ -343,6 +356,11 @@ class C(Generic[T]): def __init__(self, x: T) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1)) # revealed: C[int] # error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`" @@ -353,6 +371,7 @@ wrong_innards: C[int] = C("five") ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") @@ -362,6 +381,11 @@ class C(Generic[T]): def __init__(self, x: T) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1)) # revealed: C[int] # error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`" @@ -373,6 +397,11 @@ class D(Generic[T]): def __init__(self, *args, **kwargs) -> None: ... +# revealed: ty_extensions.GenericContext[T@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D(1)) # revealed: D[int] # error: [invalid-assignment] "Object of type `D[int | str]` is not assignable to `D[int]`" @@ -386,6 +415,7 @@ to specialize the class. ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") U = TypeVar("U") @@ -398,6 +428,11 @@ class C(Generic[T, U]): class D(C[V, int]): def __init__(self, x: V) -> None: ... +# revealed: ty_extensions.GenericContext[V@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[V@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D(1)) # revealed: D[int] ``` @@ -405,6 +440,7 @@ reveal_type(D(1)) # revealed: D[int] ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") U = TypeVar("U") @@ -415,6 +451,11 @@ class C(Generic[T, U]): class D(C[T, U]): pass +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(C(1, "str")) # revealed: C[int, str] reveal_type(D(1, "str")) # revealed: D[int, str] ``` @@ -425,6 +466,7 @@ This is a specific example of the above, since it was reported specifically by a ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") U = TypeVar("U") @@ -432,6 +474,11 @@ U = TypeVar("U") class D(dict[T, U]): pass +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D(key=1)) # revealed: D[str, int] ``` @@ -443,12 +490,18 @@ context. But from the user's point of view, this is another example of the above ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") U = TypeVar("U") class C(tuple[T, U]): ... +# revealed: ty_extensions.GenericContext[T@C, U@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C, U@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C((1, 2))) # revealed: C[int, int] ``` @@ -480,6 +533,7 @@ def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: t ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable S = TypeVar("S") T = TypeVar("T") @@ -487,6 +541,11 @@ T = TypeVar("T") class C(Generic[T]): def __init__(self, x: T, y: S) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C, S@__init__] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1, 1)) # revealed: C[int] reveal_type(C(1, "string")) # revealed: C[int] reveal_type(C(1, True)) # revealed: C[int] @@ -499,6 +558,7 @@ wrong_innards: C[int] = C("five", 1) ```py from typing_extensions import overload, Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") U = TypeVar("U") @@ -514,6 +574,11 @@ class C(Generic[T]): def __init__(self, x: int) -> None: ... def __init__(self, x: str | bytes | int) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C("string")) # revealed: C[str] reveal_type(C(b"bytes")) # revealed: C[bytes] reveal_type(C(12)) # revealed: C[Unknown] @@ -541,6 +606,11 @@ class D(Generic[T, U]): def __init__(self, t: T, u: U) -> None: ... def __init__(self, *args) -> None: ... +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D("string")) # revealed: D[str, str] reveal_type(D(1)) # revealed: D[str, int] reveal_type(D(1, "string")) # revealed: D[int, str] @@ -551,6 +621,7 @@ reveal_type(D(1, "string")) # revealed: D[int, str] ```py from dataclasses import dataclass from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") @@ -558,6 +629,11 @@ T = TypeVar("T") class A(Generic[T]): x: T +# revealed: ty_extensions.GenericContext[T@A] +reveal_type(generic_context(A)) +# revealed: ty_extensions.GenericContext[T@A] +reveal_type(generic_context(into_callable(A))) + reveal_type(A(x=1)) # revealed: A[int] ``` @@ -565,17 +641,28 @@ reveal_type(A(x=1)) # revealed: A[int] ```py from typing_extensions import Generic, TypeVar +from ty_extensions import generic_context, into_callable T = TypeVar("T") U = TypeVar("U", default=T) class C(Generic[T, U]): ... +# revealed: ty_extensions.GenericContext[T@C, U@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C, U@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C()) # revealed: C[Unknown, Unknown] class D(Generic[T, U]): def __init__(self) -> None: ... +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D()) # revealed: D[Unknown, Unknown] ``` 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 a110783ed2fc7..dbb249b45ec7a 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -264,12 +264,19 @@ signatures don't count towards variance). ### `__new__` only ```py +from ty_extensions import generic_context, into_callable + class C[T]: x: T def __new__(cls, x: T) -> "C[T]": return object.__new__(cls) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1)) # revealed: C[int] # error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`" @@ -279,11 +286,18 @@ wrong_innards: C[int] = C("five") ### `__init__` only ```py +from ty_extensions import generic_context, into_callable + class C[T]: x: T def __init__(self, x: T) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1)) # revealed: C[int] # error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`" @@ -293,6 +307,8 @@ wrong_innards: C[int] = C("five") ### Identical `__new__` and `__init__` signatures ```py +from ty_extensions import generic_context, into_callable + class C[T]: x: T @@ -301,6 +317,11 @@ class C[T]: def __init__(self, x: T) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1)) # revealed: C[int] # error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`" @@ -310,6 +331,8 @@ wrong_innards: C[int] = C("five") ### Compatible `__new__` and `__init__` signatures ```py +from ty_extensions import generic_context, into_callable + class C[T]: x: T @@ -318,6 +341,11 @@ class C[T]: def __init__(self, x: T) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1)) # revealed: C[int] # error: [invalid-assignment] "Object of type `C[int | str]` is not assignable to `C[int]`" @@ -331,6 +359,11 @@ class D[T]: def __init__(self, *args, **kwargs) -> None: ... +# revealed: ty_extensions.GenericContext[T@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D(1)) # revealed: D[int] # error: [invalid-assignment] "Object of type `D[int | str]` is not assignable to `D[int]`" @@ -343,6 +376,8 @@ If either method comes from a generic base class, we don't currently use its inf to specialize the class. ```py +from ty_extensions import generic_context, into_callable + class C[T, U]: def __new__(cls, *args, **kwargs) -> "C[T, U]": return object.__new__(cls) @@ -350,18 +385,30 @@ class C[T, U]: class D[V](C[V, int]): def __init__(self, x: V) -> None: ... +# revealed: ty_extensions.GenericContext[V@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[V@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D(1)) # revealed: D[Literal[1]] ``` ### Generic class inherits `__init__` from generic base class ```py +from ty_extensions import generic_context, into_callable + class C[T, U]: def __init__(self, t: T, u: U) -> None: ... class D[T, U](C[T, U]): pass +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(C(1, "str")) # revealed: C[Literal[1], Literal["str"]] reveal_type(D(1, "str")) # revealed: D[Literal[1], Literal["str"]] ``` @@ -371,9 +418,16 @@ reveal_type(D(1, "str")) # revealed: D[Literal[1], Literal["str"]] This is a specific example of the above, since it was reported specifically by a user. ```py +from ty_extensions import generic_context, into_callable + class D[T, U](dict[T, U]): pass +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D(key=1)) # revealed: D[str, int] ``` @@ -384,8 +438,15 @@ for `tuple`, so we use a different mechanism to make sure it has the right inher context. But from the user's point of view, this is another example of the above.) ```py +from ty_extensions import generic_context, into_callable + class C[T, U](tuple[T, U]): ... +# revealed: ty_extensions.GenericContext[T@C, U@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C, U@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C((1, 2))) # revealed: C[Literal[1], Literal[2]] ``` @@ -409,11 +470,18 @@ def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: t ### `__init__` is itself generic ```py +from ty_extensions import generic_context, into_callable + class C[T]: x: T def __init__[S](self, x: T, y: S) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C, S@__init__] +reveal_type(generic_context(into_callable(C))) + reveal_type(C(1, 1)) # revealed: C[int] reveal_type(C(1, "string")) # revealed: C[int] reveal_type(C(1, True)) # revealed: C[int] @@ -427,6 +495,7 @@ wrong_innards: C[int] = C("five", 1) ```py from __future__ import annotations from typing import overload +from ty_extensions import generic_context, into_callable class C[T]: # we need to use the type variable or else the class is bivariant in T, and @@ -443,6 +512,11 @@ class C[T]: def __init__(self, x: int) -> None: ... def __init__(self, x: str | bytes | int) -> None: ... +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C("string")) # revealed: C[str] reveal_type(C(b"bytes")) # revealed: C[bytes] reveal_type(C(12)) # revealed: C[Unknown] @@ -470,6 +544,11 @@ class D[T, U]: def __init__(self, t: T, u: U) -> None: ... def __init__(self, *args) -> None: ... +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D("string")) # revealed: D[str, Literal["string"]] reveal_type(D(1)) # revealed: D[str, Literal[1]] reveal_type(D(1, "string")) # revealed: D[Literal[1], Literal["string"]] @@ -479,24 +558,42 @@ reveal_type(D(1, "string")) # revealed: D[Literal[1], Literal["string"]] ```py from dataclasses import dataclass +from ty_extensions import generic_context, into_callable @dataclass class A[T]: x: T +# revealed: ty_extensions.GenericContext[T@A] +reveal_type(generic_context(A)) +# revealed: ty_extensions.GenericContext[T@A] +reveal_type(generic_context(into_callable(A))) + reveal_type(A(x=1)) # revealed: A[int] ``` ### Class typevar has another typevar as a default ```py +from ty_extensions import generic_context, into_callable + class C[T, U = T]: ... +# revealed: ty_extensions.GenericContext[T@C, U@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[T@C, U@C] +reveal_type(generic_context(into_callable(C))) + reveal_type(C()) # revealed: C[Unknown, Unknown] class D[T, U = T]: def __init__(self) -> None: ... +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(D)) +# revealed: ty_extensions.GenericContext[T@D, U@D] +reveal_type(generic_context(into_callable(D))) + reveal_type(D()) # revealed: D[Unknown, Unknown] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/liskov.md b/crates/ty_python_semantic/resources/mdtest/liskov.md index ad16b99f08ce9..2dca2dd558e17 100644 --- a/crates/ty_python_semantic/resources/mdtest/liskov.md +++ b/crates/ty_python_semantic/resources/mdtest/liskov.md @@ -218,8 +218,8 @@ class E(A[int]): def method(self, x: object) -> None: ... # fine class F[T](A[T]): - # TODO: we should emit `invalid-method-override` on this: # `str` is not necessarily a supertype of `T`! + # error: [invalid-method-override] def method(self, x: str) -> None: ... class G(A[int]): diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 81d056f91aa17..d4e39c932b7d9 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -8534,12 +8534,9 @@ impl<'db> TypeMapping<'_, 'db> { | TypeMapping::Materialize(_) | TypeMapping::ReplaceParameterDefaults | TypeMapping::EagerExpansion => context, - TypeMapping::BindSelf { .. } => GenericContext::from_typevar_instances( - db, - context - .variables(db) - .filter(|var| !var.typevar(db).is_self(db)), - ), + TypeMapping::BindSelf { + binding_context, .. + } => context.remove_self(db, *binding_context), TypeMapping::ReplaceSelf { new_upper_bound } => GenericContext::from_typevar_instances( db, context.variables(db).map(|typevar| { diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 2093f2f377b96..da63e4520843c 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -32,7 +32,9 @@ use crate::types::function::{ use crate::types::generics::{ InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError, }; -use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters}; +use crate::types::signatures::{ + CallableSignature, Parameter, ParameterForm, ParameterKind, Parameters, +}; use crate::types::tuple::{TupleLength, TupleType}; use crate::types::{ BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DATACLASS_FLAGS, DataclassFlags, @@ -788,51 +790,67 @@ impl<'db> Bindings<'db> { )) }; - let function_generic_context = |function: FunctionType<'db>| { - let union = UnionType::from_elements( - db, - function - .signature(db) - .overloads - .iter() - .filter_map(|signature| signature.generic_context) - .map(wrap_generic_context), - ); - if union.is_never() { - Type::none(db) - } else { - union - } - }; + let signature_generic_context = + |signature: &CallableSignature<'db>| { + UnionType::try_from_elements( + db, + signature.overloads.iter().map(|signature| { + signature.generic_context.map(wrap_generic_context) + }), + ) + }; - // TODO: Handle generic functions, and unions/intersections of - // generic types - overload.set_return_type(match ty { - Type::ClassLiteral(class) => class - .generic_context(db) - .map(wrap_generic_context) - .unwrap_or_else(|| Type::none(db)), + let generic_context_for_simple_type = |ty: Type<'db>| match ty { + Type::ClassLiteral(class) => { + class.generic_context(db).map(wrap_generic_context) + } Type::FunctionLiteral(function) => { - function_generic_context(*function) + signature_generic_context(function.signature(db)) } - Type::BoundMethod(bound_method) => { - function_generic_context(bound_method.function(db)) + Type::BoundMethod(bound_method) => signature_generic_context( + bound_method.function(db).signature(db), + ), + + Type::Callable(callable) => { + signature_generic_context(callable.signatures(db)) } Type::KnownInstance(KnownInstanceType::TypeAliasType( TypeAliasType::PEP695(alias), - )) => alias - .generic_context(db) - .map(wrap_generic_context) - .unwrap_or_else(|| Type::none(db)), + )) => alias.generic_context(db).map(wrap_generic_context), - _ => Type::none(db), - }); + _ => None, + }; + + let generic_context = match ty { + Type::Union(union_type) => UnionType::try_from_elements( + db, + union_type + .elements(db) + .iter() + .map(|ty| generic_context_for_simple_type(*ty)), + ), + _ => generic_context_for_simple_type(*ty), + }; + + overload.set_return_type( + generic_context.unwrap_or_else(|| Type::none(db)), + ); } } + Some(KnownFunction::IntoCallable) => { + let [Some(ty)] = overload.parameter_types() else { + continue; + }; + let Some(callables) = ty.try_upcast_to_callable(db) else { + continue; + }; + overload.set_return_type(callables.into_type(db)); + } + Some(KnownFunction::DunderAllNames) => { if let [Some(ty)] = overload.parameter_types() { overload.set_return_type(match ty { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 06f91502fc770..00b5bed3ec966 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1133,6 +1133,13 @@ impl<'db> ClassType<'db> { /// constructor signature of this class. #[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)] pub(super) fn into_callable(self, db: &'db dyn Db) -> CallableTypes<'db> { + // TODO: This mimics a lot of the logic in Type::try_call_from_constructor. Can we + // consolidate the two? Can we invoke a class by upcasting the class into a Callable, and + // then relying on the call binding machinery to Just Work™? + + let (class_literal, _) = self.class_literal(db); + let class_generic_context = class_literal.generic_context(db); + let self_ty = Type::from(self); let metaclass_dunder_call_function_symbol = self_ty .member_lookup_with_policy( @@ -1206,39 +1213,58 @@ impl<'db> ClassType<'db> { // If the class defines an `__init__` method, then we synthesize a callable type with the // same parameters as the `__init__` method after it is bound, and with the return type of // the concrete type of `Self`. - let synthesized_dunder_init_callable = - if let Place::Defined(ty, _, _) = dunder_init_function_symbol { - let signature = match ty { - Type::FunctionLiteral(dunder_init_function) => { - Some(dunder_init_function.signature(db)) - } - Type::Callable(callable) => Some(callable.signatures(db)), - _ => None, - }; - - if let Some(signature) = signature { - let synthesized_signature = |signature: &Signature<'db>| { - let instance_ty = Type::instance(db, self); - Signature::new(signature.parameters().clone(), Some(correct_return_type)) - .with_definition(signature.definition()) - .bind_self(db, Some(instance_ty)) - }; + let synthesized_dunder_init_callable = if let Place::Defined(ty, _, _) = + dunder_init_function_symbol + { + let signature = match ty { + Type::FunctionLiteral(dunder_init_function) => { + Some(dunder_init_function.signature(db)) + } + Type::Callable(callable) => Some(callable.signatures(db)), + _ => None, + }; - let synthesized_dunder_init_signature = CallableSignature::from_overloads( - signature.overloads.iter().map(synthesized_signature), + if let Some(signature) = signature { + let synthesized_signature = |signature: &Signature<'db>| { + let self_annotation = signature + .parameters() + .get_positional(0) + .and_then(Parameter::annotated_type) + .filter(|ty| { + ty.as_typevar() + .is_none_or(|bound_typevar| !bound_typevar.typevar(db).is_self(db)) + }); + let return_type = self_annotation.unwrap_or(correct_return_type); + let instance_ty = self_annotation.unwrap_or_else(|| Type::instance(db, self)); + let generic_context = GenericContext::merge_optional( + db, + class_generic_context, + signature.generic_context, ); + Signature::new_generic( + generic_context, + signature.parameters().clone(), + Some(return_type), + ) + .with_definition(signature.definition()) + .bind_self(db, Some(instance_ty)) + }; - Some(CallableType::new( - db, - synthesized_dunder_init_signature, - true, - )) - } else { - None - } + let synthesized_dunder_init_signature = CallableSignature::from_overloads( + signature.overloads.iter().map(synthesized_signature), + ); + + Some(CallableType::new( + db, + synthesized_dunder_init_signature, + true, + )) } else { None - }; + } + } else { + None + }; match (dunder_new_function, synthesized_dunder_init_callable) { (Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => { @@ -1261,9 +1287,13 @@ impl<'db> ClassType<'db> { ) .place; - if let Place::Defined(Type::FunctionLiteral(new_function), _, _) = + if let Place::Defined(Type::FunctionLiteral(mut new_function), _, _) = new_function_symbol { + if let Some(class_generic_context) = class_generic_context { + new_function = + new_function.with_inherited_generic_context(db, class_generic_context); + } CallableTypes::one( new_function .into_bound_method_type(db, correct_return_type) @@ -1273,7 +1303,11 @@ impl<'db> ClassType<'db> { // Fallback if no `object.__new__` is found. CallableTypes::one(CallableType::single( db, - Signature::new(Parameters::empty(), Some(correct_return_type)), + Signature::new_generic( + class_generic_context, + Parameters::empty(), + Some(correct_return_type), + ), )) } } diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index f8537c27a74c8..0b49d3f14af45 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1339,6 +1339,8 @@ pub enum KnownFunction { IsSingleValued, /// `ty_extensions.generic_context` GenericContext, + /// `ty_extensions.into_callable` + IntoCallable, /// `ty_extensions.dunder_all_names` DunderAllNames, /// `ty_extensions.enum_members` @@ -1411,6 +1413,7 @@ impl KnownFunction { | Self::IsSingleton | Self::IsSubtypeOf | Self::GenericContext + | Self::IntoCallable | Self::DunderAllNames | Self::EnumMembers | Self::StaticAssert @@ -1941,6 +1944,7 @@ pub(crate) mod tests { KnownFunction::IsSingleton | KnownFunction::IsSubtypeOf | KnownFunction::GenericContext + | KnownFunction::IntoCallable | KnownFunction::DunderAllNames | KnownFunction::EnumMembers | KnownFunction::StaticAssert diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index de357dfbce2c5..432785b778d5c 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -17,11 +17,12 @@ use crate::types::signatures::Parameters; use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type}; use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard}; use crate::types::{ - ApplyTypeMappingVisitor, BoundTypeVarIdentity, BoundTypeVarInstance, ClassLiteral, - FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, - KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext, - TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance, - TypeVarKind, TypeVarVariance, UnionType, declaration_type, walk_bound_type_var_type, + ApplyTypeMappingVisitor, BindingContext, BoundTypeVarIdentity, BoundTypeVarInstance, + ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, + IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, + Type, TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, + TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type, + walk_bound_type_var_type, }; use crate::{Db, FxOrderMap, FxOrderSet}; @@ -261,6 +262,34 @@ impl<'db> GenericContext<'db> { ) } + pub(crate) fn merge_optional( + db: &'db dyn Db, + left: Option, + right: Option, + ) -> Option { + match (left, right) { + (None, None) => None, + (Some(one), None) | (None, Some(one)) => Some(one), + (Some(left), Some(right)) => Some(left.merge(db, right)), + } + } + + pub(crate) fn remove_self( + self, + db: &'db dyn Db, + binding_context: Option>, + ) -> Self { + Self::from_typevar_instances( + db, + self.variables(db).filter(|bound_typevar| { + !(bound_typevar.typevar(db).is_self(db) + && binding_context.is_none_or(|binding_context| { + bound_typevar.binding_context(db) == binding_context + })) + }), + ) + } + pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> { #[derive(Default)] struct CollectTypeVars<'db> { diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index d091b8a53f6cc..c76a086cfa60f 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -667,10 +667,11 @@ impl<'db> Signature<'db> { let mut parameters = Parameters::new(db, parameters); let mut return_ty = self.return_ty; + let binding_context = self.definition.map(BindingContext::Definition); if let Some(self_type) = self_type { let self_mapping = TypeMapping::BindSelf { self_type, - binding_context: self.definition.map(BindingContext::Definition), + binding_context, }; parameters = parameters.apply_type_mapping_impl( db, @@ -682,7 +683,9 @@ impl<'db> Signature<'db> { .map(|ty| ty.apply_type_mapping(db, &self_mapping, TypeContext::default())); } Self { - generic_context: self.generic_context, + generic_context: self + .generic_context + .map(|generic_context| generic_context.remove_self(db, binding_context)), definition: self.definition, parameters, return_ty, diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index 8f45b292387d7..347b6b4b3491d 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -147,6 +147,10 @@ def is_single_valued(ty: Any) -> bool: # type is not generic. def generic_context(ty: Any) -> GenericContext | None: ... +# Converts a value into a `Callable`, if possible. This is the value equivalent +# of `CallableTypeOf`, which operates on types. +def into_callable(ty: Any) -> Any: ... + # Returns the `__all__` names of a module as a tuple of sorted strings, or `None` if # either the module does not have `__all__` or it has invalid elements. def dunder_all_names(module: Any) -> Any: ...