Commit 19d8b45b authored by aleksei.kniazev's avatar aleksei.kniazev
Browse files

added a type for generic descriptor to improve type hints (PY-26187)

parent 562886e2
Branches unavailable Tags unavailable
No related merge requests found
Showing with 78 additions and 19 deletions
+78 -19
......@@ -9,10 +9,7 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
......@@ -636,7 +633,11 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
if (genericTypes.isEmpty()) {
return null;
}
return new PyCollectionTypeImpl(cls, false, genericTypes);
return StreamEx.of(cls.getMethods())
.filterBy(PsiNamedElement::getName, PyNames.GET)
.findAny()
.<PyType>map(getter -> new PyDescriptorTypeImpl(cls, false, context.getReturnType(getter)))
.orElseGet(() -> new PyCollectionTypeImpl(cls, false, genericTypes));
}
@NotNull
......
......@@ -252,6 +252,10 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
@Nullable
private Ref<PyType> getDescriptorType(@Nullable PyType typeFromTargets, @NotNull TypeEvalContext context) {
if (typeFromTargets instanceof PyDescriptorType) {
PyDescriptorType descriptorType = (PyDescriptorType)typeFromTargets;
return Ref.create(descriptorType.getMyGetterReturnType());
}
if (!isQualified()) return null;
final PyClassLikeType targetType = as(typeFromTargets, PyClassLikeType.class);
if (targetType == null) return null;
......@@ -265,20 +269,6 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
.map((callable) -> context.getReturnType(callable))
.toList();
final PyType type = PyUnionType.union(types);
if (PyTypeChecker.hasGenerics(type, context)) {
PyAssignmentStatement assignmentStatement = PyAssignmentStatementNavigator.getStatementByTarget(getReference().resolve());
if (assignmentStatement != null && assignmentStatement.getAssignedValue() instanceof PyCallExpression) {
PyCallExpression callExpression = (PyCallExpression) assignmentStatement.getAssignedValue();
Map<PyGenericType, PyType> result = PyTypeChecker.unifyGenericCall(callExpression, Collections.emptyMap(), context);
if (!ContainerUtil.isEmpty(result)) {
PyType actualType = PyTypeChecker.substitute(type, result, context);
if (actualType != null) {
return Ref.create(actualType);
}
}
}
}
return Ref.create(type);
}
......
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi.types
interface PyDescriptorType : PyClassType {
val myGetterReturnType: PyType?
}
\ No newline at end of file
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi.types
import com.jetbrains.python.psi.PyClass
class PyDescriptorTypeImpl(source: PyClass,
isDefinition: Boolean,
override val myGetterReturnType: PyType?) : PyClassTypeImpl(source, isDefinition), PyDescriptorType {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
if (!super.equals(other)) return false
other as PyDescriptorTypeImpl
if (myGetterReturnType != other.myGetterReturnType) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + (myGetterReturnType?.hashCode() ?: 0)
return result
}
}
\ No newline at end of file
......@@ -606,6 +606,10 @@ public class PyTypeChecker {
collectGenerics(elementType, context, collected, visited);
}
}
else if (type instanceof PyDescriptorType) {
PyDescriptorType descriptor = (PyDescriptorType)type;
collectGenerics(descriptor.getMyGetterReturnType(), context, collected, visited);
}
else if (type instanceof PyCallableType) {
final PyCallableType callable = (PyCallableType)type;
final List<PyCallableParameter> parameters = callable.getParameters(context);
......@@ -664,6 +668,11 @@ public class PyTypeChecker {
}
return new PyCollectionTypeImpl(collection.getPyClass(), collection.isDefinition(), substitutes);
}
else if (type instanceof PyDescriptorType) {
final PyDescriptorType descriptor = (PyDescriptorType)type;
PyType newGetterReturnType = substitute(descriptor.getMyGetterReturnType(), substitutions, context);
return new PyDescriptorTypeImpl(descriptor.getPyClass(), descriptor.isDefinition(), newGetterReturnType);
}
else if (type instanceof PyTupleType) {
final PyTupleType tupleType = (PyTupleType)type;
final PyClass tupleClass = tupleType.getPyClass();
......
......@@ -3418,6 +3418,32 @@ public class PyTypeTest extends PyTestCase {
);
}
// PY-26184
public void testGenericDescriptorFromTupleAssignment() {
runWithLanguageLevel(LanguageLevel.PYTHON37, () ->
doTest("int",
"from typing import Generic, TypeVar\n" +
"\n" +
"T = TypeVar(\"T\")\n" +
"U = TypeVar(\"U\")\n" +
"class Descr(Generic[T]):\n" +
" def __init__(self, one: T) -> None:\n" +
" self.one = one\n" +
"\n" +
" def __get__(self, instance, owner) -> T:\n" +
" return self.one\n" +
"\n\n" +
"def foo(one: T, two: U) -> (Descr[T], Descr[U]):\n" +
" return Descr(one), Descr(two)" +
"\n\n" +
"class MyClass:\n" +
" descr_target, irrelevant = foo(42, \"string\")\n" +
"\n" +
"expr = MyClass().descr_target"
)
);
}
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());
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment