Unify generic types of qualified reference expressions (PY-16267)

We used to unify generic types in call expressions, including the
receivers of calls, but there was no unification of generics for
non-callable attributes.
This commit is contained in:
Andrey Vlasovskikh
2015-12-29 03:51:05 +03:00
parent 4e5a2af27a
commit 09600fd538
3 changed files with 64 additions and 1 deletions
@@ -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<PyExpression, PyNamedParameter> parameters = Collections.emptyMap();
final Map<PyGenericType, PyType> 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) {
@@ -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<TypeEvalContext> getTypeEvalContexts(@NotNull PyExpression element) {
return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(),
TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing());
@@ -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());