diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md index 2645221f01..da4ac30924 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -494,3 +494,25 @@ age, name = team.employees[0] reveal_type(age) # revealed: Age reveal_type(name) # revealed: Name ``` + +## `self` in PEP 695 generic methods + +When a generic method uses a PEP 695 generic context, an implict or explicit annotation of +`self: Self` is still part of the full generic context: + +```py +from typing import Self + +class C: + def explicit_self[T](self: Self, x: T) -> tuple[Self, T]: + return self, x + + def implicit_self[T](self, x: T) -> tuple[Self, T]: + return self, x + +def _(x: int): + reveal_type(C().explicit_self(x)) # revealed: tuple[C, int] + + # TODO: this should be `tuple[C, int]` as well, once we support implicit `self` + reveal_type(C().implicit_self(x)) # revealed: tuple[Unknown, int] +``` diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index bfb248202b..0eaaf037c3 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -134,6 +134,18 @@ impl<'db> GenericContext<'db> { Self::new(db, type_params.into_iter().collect::>()) } + /// Merge this generic context with another, returning a new generic context that + /// contains type variables from both contexts. + pub(crate) fn merge(self, db: &'db dyn Db, other: Self) -> Self { + Self::from_typevar_instances( + db, + self.variables(db) + .iter() + .chain(other.variables(db).iter()) + .copied(), + ) + } + fn variable_from_type_param( db: &'db dyn Db, index: &'db SemanticIndex<'db>, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index ffd11bf77f..1159b0556d 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -376,12 +376,25 @@ impl<'db> Signature<'db> { let legacy_generic_context = GenericContext::from_function_params(db, definition, ¶meters, return_ty); - if generic_context.is_some() && legacy_generic_context.is_some() { - // TODO: Raise a diagnostic! - } + let full_generic_context = match (legacy_generic_context, generic_context) { + (Some(legacy_ctx), Some(ctx)) => { + if legacy_ctx + .variables(db) + .iter() + .exactly_one() + .is_ok_and(|bound_typevar| bound_typevar.typevar(db).is_self(db)) + { + Some(legacy_ctx.merge(db, ctx)) + } else { + // TODO: Raise a diagnostic — mixing PEP 695 and legacy typevars is not allowed + Some(ctx) + } + } + (left, right) => left.or(right), + }; Self { - generic_context: generic_context.or(legacy_generic_context), + generic_context: full_generic_context, inherited_generic_context, definition: Some(definition), parameters,