diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyReferenceExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyReferenceExpression.java
index 7144acc48f3a..7e605a62c827 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/PyReferenceExpression.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/PyReferenceExpression.java
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.psi;
+import com.google.common.base.Predicates;
import com.intellij.psi.PsiPolyVariantReference;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedRatedResolveResult;
@@ -22,6 +23,7 @@ import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
import org.jetbrains.annotations.NotNull;
import java.util.List;
+import java.util.function.Predicate;
public interface PyReferenceExpression extends PyQualifiedExpression, PyReferenceOwner {
PyReferenceExpression[] EMPTY_ARRAY = new PyReferenceExpression[0];
@@ -48,8 +50,24 @@ public interface PyReferenceExpression extends PyQualifiedExpression, PyReferenc
* Note: the returned list does not contain null values.
*/
@NotNull
- List multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext);
+ default List multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext) {
+ return multiFollowAssignmentsChain(resolveContext, Predicates.alwaysTrue());
+ }
+ /**
+ * Goes through a chain of assignment statements until a non-assignment expression is encountered.
+ * Starts at this, expecting it to resolve to a target of an assignment.
+ *
+ * @param resolveContext resolve context
+ * @param follow predicate to test if target should be followed
+ * @return the values that could be assigned to this element via a chain of assignments, or an empty list.
+ * Note: the returned list does not contain null values.
+ */
+ @NotNull
+ List multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext,
+ @NotNull Predicate super PyTargetExpression> follow);
+
+ @Override
@NotNull
PsiPolyVariantReference getReference();
}
diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java
index 55beb7b33cc4..e24960023cfa 100644
--- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java
+++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java
@@ -129,7 +129,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
* since they are not type aliases already and in typing.pyi are assigned to
* some synthetic values.
*/
- private static final ImmutableSet OPAQUE_NAMES = ImmutableSet.builder()
+ public static final ImmutableSet OPAQUE_NAMES = ImmutableSet.builder()
.add(PyKnownDecoratorUtil.KnownDecorator.TYPING_OVERLOAD.name())
.add(ANY)
.add(TYPE_VAR)
diff --git a/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt b/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt
index 7092b2cc5582..5ab143093506 100644
--- a/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt
+++ b/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt
@@ -17,7 +17,6 @@ import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.impl.PyEvaluator
import com.jetbrains.python.psi.resolve.PyResolveContext
import com.jetbrains.python.psi.resolve.PyResolveUtil
-import com.jetbrains.python.psi.resolve.RatedResolveResult
import com.jetbrains.python.psi.types.PyGenericType
import com.jetbrains.python.psi.types.PyTypeChecker
@@ -188,29 +187,30 @@ class PyTypeHintsInspection : PyInspection() {
private fun checkInstanceAndClassChecksOnReference(base: PyExpression) {
if (base is PyReferenceExpression) {
- val resolvedBase = PyResolveUtil.fullMultiResolveLocally(base)
+ val resolvedBase = multiFollowAssignmentsChain(base)
resolvedBase
.asSequence()
- .filterIsInstance()
- .mapNotNull { PyResolveUtil.getImportedElementQName(it) }
+ .filterIsInstance()
+ .mapNotNull { it.qualifiedName }
.forEach {
- when (it.toString()) {
+ when (it) {
PyTypingTypeProvider.ANY,
PyTypingTypeProvider.UNION,
PyTypingTypeProvider.GENERIC,
PyTypingTypeProvider.OPTIONAL,
PyTypingTypeProvider.CLASS_VAR,
- PyTypingTypeProvider.NO_RETURN -> registerProblem(base,
- "'${it.lastComponent}' cannot be used with instance and class checks",
- ProblemHighlightType.GENERIC_ERROR)
+ PyTypingTypeProvider.NO_RETURN ->
+ registerProblem(base,
+ "'${it.substringAfterLast('.')}' cannot be used with instance and class checks",
+ ProblemHighlightType.GENERIC_ERROR)
}
}
resolvedBase
.asSequence()
- .filterIsInstance()
- .mapNotNull { it.findAssignedValue() as? PySubscriptionExpression }
+ .filterIsInstance()
+ .filter { myTypeEvalContext.maySwitchToAST(it) }
.forEach { checkInstanceAndClassChecksOnSubscriptionOperand(base, it.operand) }
}
}
@@ -223,63 +223,47 @@ class PyTypeHintsInspection : PyInspection() {
private fun checkInstanceAndClassChecksOnSubscriptionOperand(base: PyExpression, operand: PyExpression) {
if (operand is PyReferenceExpression) {
- PyResolveUtil
- .fullMultiResolveLocally(operand)
+ multiFollowAssignmentsChain(operand)
.forEach {
- if (it == null) return@forEach
+ if (it is PyQualifiedNameOwner) {
+ val qName = it.qualifiedName
- if (it is PyImportElement) {
- val qName = PyResolveUtil.getImportedElementQName(it)
+ when (qName) {
+ PyTypingTypeProvider.GENERIC -> {
+ registerProblem(base, "'Generic' cannot be used with instance and class checks", ProblemHighlightType.GENERIC_ERROR)
+ return@forEach
+ }
- if (qName == genericQName) {
- registerProblem(base,
- "'Generic' cannot be used with instance and class checks",
- ProblemHighlightType.GENERIC_ERROR)
-
- return@forEach
- }
-
- if (qName != null) {
- val qNameAsString = qName.toString()
-
- if (qNameAsString == PyTypingTypeProvider.UNION ||
- qNameAsString == PyTypingTypeProvider.OPTIONAL ||
- qNameAsString == PyTypingTypeProvider.CLASS_VAR) {
+ PyTypingTypeProvider.UNION,
+ PyTypingTypeProvider.OPTIONAL,
+ PyTypingTypeProvider.CLASS_VAR -> {
registerProblem(base,
- "'${qName.lastComponent}' cannot be used with instance and class checks",
+ "'${qName.substringAfterLast('.')}' cannot be used with instance and class checks",
ProblemHighlightType.GENERIC_ERROR)
+ return@forEach
+ }
+ PyTypingTypeProvider.CALLABLE,
+ PyTypingTypeProvider.TYPE,
+ PyTypingTypeProvider.PROTOCOL,
+ PyTypingTypeProvider.PROTOCOL_EXT -> {
+ registerProblem(base,
+ "Parameterized generics cannot be used with instance and class checks",
+ ProblemHighlightType.GENERIC_ERROR)
return@forEach
}
}
}
- val resolved = if (it is PyImportElement) it.multiResolve() else listOf(RatedResolveResult(RatedResolveResult.RATE_NORMAL, it))
+ if (it is PyTypedElement) {
+ val type = myTypeEvalContext.getType(it)
- resolved
- .asSequence()
- .filterNotNull()
- .mapNotNull { it.element as? PyTypedElement }
- .any {
- if (it is PyQualifiedNameOwner) {
- val qName = it.qualifiedName
-
- if (qName == PyTypingTypeProvider.CALLABLE ||
- qName == PyTypingTypeProvider.TYPE ||
- qName == PyTypingTypeProvider.PROTOCOL ||
- qName == PyTypingTypeProvider.PROTOCOL_EXT) return@any true
- }
-
- val type = myTypeEvalContext.getType(it)
- type is PyWithAncestors && PyTypingTypeProvider.isGeneric(type, myTypeEvalContext)
- }
- .let {
- if (it) {
- registerProblem(base,
- "Parameterized generics cannot be used with instance and class checks",
- ProblemHighlightType.GENERIC_ERROR)
- }
+ if (type is PyWithAncestors && PyTypingTypeProvider.isGeneric(type, myTypeEvalContext)) {
+ registerProblem(base,
+ "Parameterized generics cannot be used with instance and class checks",
+ ProblemHighlightType.GENERIC_ERROR)
}
+ }
}
}
}
@@ -296,12 +280,12 @@ class PyTypeHintsInspection : PyInspection() {
superClassExpressions
.asSequence()
.filter {
- val resolved = if (it is PyReferenceExpression) PyResolveUtil.fullMultiResolveLocally(it) else listOf(it)
+ val resolved = if (it is PyReferenceExpression) multiFollowAssignmentsChain(it) else listOf(it)
resolved
.asSequence()
- .map { if (it is PyTargetExpression) it.findAssignedValue() else it }
.filterIsInstance()
+ .filter { myTypeEvalContext.maySwitchToAST(it) }
.mapNotNull { it.operand as? PyReferenceExpression }
.any { genericQName in PyResolveUtil.resolveImportedElementQNameLocally(it) }
}
@@ -346,7 +330,7 @@ class PyTypeHintsInspection : PyInspection() {
private fun collectGenerics(superClassExpression: PyExpression): Pair?, Set> {
val resolvedSuperClass =
- if (superClassExpression is PyReferenceExpression) PyResolveUtil.fullMultiResolveLocally(superClassExpression)
+ if (superClassExpression is PyReferenceExpression) multiFollowAssignmentsChain(superClassExpression)
else listOf(superClassExpression)
var seenGeneric = false
@@ -355,8 +339,8 @@ class PyTypeHintsInspection : PyInspection() {
resolvedSuperClass
.asSequence()
- .map { if (it is PyTargetExpression) it.findAssignedValue() else it }
.filterIsInstance()
+ .filter { myTypeEvalContext.maySwitchToAST(it) }
.forEach {
val operand = it.operand
val generic =
@@ -368,7 +352,7 @@ class PyTypeHintsInspection : PyInspection() {
val superClassTypeVars = parameters
.asSequence()
.filterIsInstance()
- .map { PyResolveUtil.fullMultiResolveLocally(it).toSet() }
+ .map { multiFollowAssignmentsChain(it, this::followNotTypeVar).toSet() }
.fold(emptySet(), { acc, typeVars -> acc.union(typeVars) })
if (generic) genericTypeVars.addAll(superClassTypeVars) else nonGenericTypeVars.addAll(superClassTypeVars)
@@ -391,7 +375,7 @@ class PyTypeHintsInspection : PyInspection() {
if (type != null) {
if (type is PyGenericType) {
- if (!typeVars.addAll(PyResolveUtil.fullMultiResolveLocally(it))) {
+ if (!typeVars.addAll(multiFollowAssignmentsChain(it))) {
registerProblem(it, "Parameters to 'Generic[...]' must all be unique", ProblemHighlightType.GENERIC_ERROR)
}
}
@@ -402,5 +386,19 @@ class PyTypeHintsInspection : PyInspection() {
}
}
}
+
+ private fun followNotTypingOpaque(target: PyTargetExpression): Boolean {
+ return !PyTypingTypeProvider.OPAQUE_NAMES.contains(target.qualifiedName)
+ }
+
+ private fun followNotTypeVar(target: PyTargetExpression): Boolean {
+ return !myTypeEvalContext.maySwitchToAST(target) || target.findAssignedValue() !is PyCallExpression
+ }
+
+ private fun multiFollowAssignmentsChain(referenceExpression: PyReferenceExpression,
+ follow: (PyTargetExpression) -> Boolean = this::followNotTypingOpaque): List {
+ val resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext)
+ return referenceExpression.multiFollowAssignmentsChain(resolveContext, follow).mapNotNull { it.element }
+ }
}
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
index 2c707d367287..baf45f2200ee 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
@@ -47,6 +47,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
+import java.util.function.Predicate;
import static com.jetbrains.python.psi.PyUtil.as;
@@ -155,7 +156,8 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
@NotNull
@Override
- public List multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext) {
+ public List multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext,
+ @NotNull Predicate super PyTargetExpression> follow) {
final List result = new ArrayList<>();
final Queue queue = new LinkedList<>();
final Set visited = new HashSet<>();
@@ -169,7 +171,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
for (ResolveResult resolveResult : node.myReferenceExpression.getReference(resolveContext).multiResolve(false)) {
final PsiElement element = resolveResult.getElement();
- if (element instanceof PyTargetExpression) {
+ if (element instanceof PyTargetExpression && follow.test((PyTargetExpression)element)) {
final PyTargetExpression target = (PyTargetExpression)element;
final List assignedFromElements = context.maySwitchToAST(target)
diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
index 464a834c73c3..8acebf5d7acf 100644
--- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
+++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
@@ -163,7 +163,7 @@ public class PyResolveUtil {
}
else {
return StreamEx
- .of(fullMultiResolveLocally(expression))
+ .of(fullMultiResolveLocally(expression, new HashSet<>()))
.select(PyImportElement.class)
.map(PyResolveUtil::getImportedElementQName)
.nonNull()
@@ -172,7 +172,7 @@ public class PyResolveUtil {
}
@Nullable
- public static QualifiedName getImportedElementQName(@NotNull PyImportElement element) {
+ private static QualifiedName getImportedElementQName(@NotNull PyImportElement element) {
final PyStatement importStatement = element.getContainingImportStatement();
if (importStatement instanceof PyFromImportStatement) {
@@ -264,7 +264,6 @@ public class PyResolveUtil {
*
* @param referenceExpression expression to resolve
* @return resolved assigned value.
- * @see PyResolveUtil#fullMultiResolveLocally(PyReferenceExpression)
*/
@Nullable
public static PyExpression fullResolveLocally(@NotNull PyReferenceExpression referenceExpression) {
@@ -293,14 +292,10 @@ public class PyResolveUtil {
* Runs DFS on assignment chains and returns all reached assigned values.
*
* @param referenceExpression expression to resolve
+ * @param visited set to store visited references to prevent recursion
* @return resolved assigned values.
* Note: the returned list could contain null values.
*/
- @NotNull
- public static List fullMultiResolveLocally(@NotNull PyReferenceExpression referenceExpression) {
- return fullMultiResolveLocally(referenceExpression, new HashSet<>());
- }
-
@NotNull
private static List fullMultiResolveLocally(@NotNull PyReferenceExpression referenceExpression,
@NotNull Set visited) {