From fb3797d0d9865a5fe3755d364fc4b2e0938dd8d4 Mon Sep 17 00:00:00 2001 From: Daniil Kalinin Date: Mon, 22 Jun 2026 18:29:18 +0200 Subject: [PATCH] PY-63737 Pass the owner class to __get__ on instance access (cherry picked from commit fb9d4793d2309192ff479af9973b7e97f74c12ab) GitOrigin-RevId: 74ba94b675a740cfee66c14e3b1ff8dcb92396ca --- .../psi/types/PyDescriptorTypeUtil.java | 2 +- .../types/PyAttributeAndDescriptorTypeTest.kt | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyDescriptorTypeUtil.java b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyDescriptorTypeUtil.java index ab77dc5b0198..8ddf38d79231 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyDescriptorTypeUtil.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyDescriptorTypeUtil.java @@ -71,7 +71,7 @@ public final class PyDescriptorTypeUtil { } else { instanceArgumentType = classType; - instanceTypeArgument = noneType; + instanceTypeArgument = classType.toClass(); } List argumentTypes = List.of(instanceArgumentType, instanceTypeArgument); PyType type = PySyntheticCallHelper.getCallTypeByFunctionName(PyNames.DUNDER_GET, receiverType, argumentTypes, context); diff --git a/python/testSrc/com/jetbrains/python/types/PyAttributeAndDescriptorTypeTest.kt b/python/testSrc/com/jetbrains/python/types/PyAttributeAndDescriptorTypeTest.kt index d3ec23622b60..7ae933e98000 100644 --- a/python/testSrc/com/jetbrains/python/types/PyAttributeAndDescriptorTypeTest.kt +++ b/python/testSrc/com/jetbrains/python/types/PyAttributeAndDescriptorTypeTest.kt @@ -1188,6 +1188,38 @@ class PyAttributeAndDescriptorTypeTest : PyCodeInsightTestCase() { # └ TYPE list """) + @Test + @TestFor(issues = ["PY-63737"]) + fun `generic descriptor with own type parameter in get binds the return type variable on instance access`() = test(""" + from typing import Callable, TypeVar, Generic + T = TypeVar("T") + T_co = TypeVar("T_co", covariant=True) + class CachedSlotProperty(Generic[T, T_co]): + def __init__(self, f: Callable[[T], T_co]) -> None: + self.f = f + def __get__(self, instance: T, owner: type[T]) -> T_co: + return self.f(instance) + 1 + class Foo: + @CachedSlotProperty + def bar(self) -> int: + return 42 + expr = Foo().bar + #└ TYPE int + """) + + @Test + @TestFor(issues = ["PY-63737"]) + fun `instance access passes the owner class so a get typed with type T does not drop other bindings`() = test(""" + from typing import Callable + class CachedSlotProperty[T, V]: + def __init__(self, f: Callable[[T], V]) -> None: ... + def __get__(self, instance: T, owner: type[T]) -> V: ... + class Foo: + bar: CachedSlotProperty[Foo, int] + expr = Foo().bar + #└ TYPE int + """) + @Test @TestFor(issues = ["PY-63737"]) fun `generic descriptor subclass used as decorator accessed on instance`() = test("""