diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index b03b6ec101b5..f7b8ac717a84 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -325,11 +325,33 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere } return null; } - @Nullable private static PyType getTypeFromTarget(@NotNull final PsiElement target, final TypeEvalContext context, PyReferenceExpression anchor) { + final PyType type = getGenericTypeFromTarget(target, context, anchor); + if (context.maySwitchToAST(anchor)) { + final PyExpression qualifier = anchor.getQualifier(); + if (qualifier != null) { + if (!(type instanceof PyFunctionType) && PyTypeChecker.hasGenerics(type, context)) { + final Map parameters = Collections.emptyMap(); + final Map substitutions = PyTypeChecker.unifyGenericCall(qualifier, parameters, context); + if (substitutions != null && !substitutions.isEmpty()) { + final PyType substituted = PyTypeChecker.substitute(type, substitutions, context); + if (substituted != null) { + return substituted; + } + } + } + } + } + return type; + } + + @Nullable + private static PyType getGenericTypeFromTarget(@NotNull final PsiElement target, + final TypeEvalContext context, + PyReferenceExpression anchor) { if (!(target instanceof PyTargetExpression)) { // PyTargetExpression will ask about its type itself final PyType pyType = getReferenceTypeFromProviders(target, context, anchor); if (pyType != null) { diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index de485e54ae62..e469e4e4660f 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -960,6 +960,29 @@ public class PyTypeTest extends PyTestCase { "expr = f\n"); } + // PY-16267 + public void testGenericField() { + doTest("str", + "class D(object):\n" + + " def __init__(self, foo):\n" + + " '''\n" + + " :type foo: T\n" + + " :rtype: D[T]\n" + + " '''\n" + + " self.foo = foo\n" + + "\n" + + "\n" + + "def g():\n" + + " '''\n" + + " :rtype: D[str]\n" + + " '''\n" + + " return D('test')\n" + + "\n" + + "\n" + + "y = g()\n" + + "expr = y.foo\n"); + } + private static List getTypeEvalContexts(@NotNull PyExpression element) { return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(), TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing()); diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 6f840f5923ae..a9ae43a86e5e 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -419,6 +419,24 @@ public class PyTypingTest extends PyTestCase { " pass\n"); } + // PY-16267 + public void testGenericField() { + doTest("str", + "from typing import TypeVar, Generic\n" + + "\n" + + "T = TypeVar('T', covariant=True)\n" + + "\n" + + "class C(Generic[T]):\n" + + " def __init__(self, foo: T):\n" + + " self.foo = foo\n" + + "\n" + + "def f() -> C[str]:\n" + + " return C('test')\n" + + "\n" + + "x = f()\n" + + "expr = x.foo\n"); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());