mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PyReferenceExpression.multiFollowAssignmentsChain has an ability to not follow some references (PY-28243)
Update PyTypeHintsInspection to use it.
This commit is contained in:
@@ -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
|
||||
* <i>Note: the returned list does not contain null values.</i>
|
||||
*/
|
||||
@NotNull
|
||||
List<QualifiedRatedResolveResult> multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext);
|
||||
default List<QualifiedRatedResolveResult> 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.
|
||||
* <i>Note: the returned list does not contain null values.</i>
|
||||
*/
|
||||
@NotNull
|
||||
List<QualifiedRatedResolveResult> multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext,
|
||||
@NotNull Predicate<? super PyTargetExpression> follow);
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
PsiPolyVariantReference getReference();
|
||||
}
|
||||
|
||||
@@ -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<String> OPAQUE_NAMES = ImmutableSet.<String>builder()
|
||||
public static final ImmutableSet<String> OPAQUE_NAMES = ImmutableSet.<String>builder()
|
||||
.add(PyKnownDecoratorUtil.KnownDecorator.TYPING_OVERLOAD.name())
|
||||
.add(ANY)
|
||||
.add(TYPE_VAR)
|
||||
|
||||
@@ -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<PyImportElement>()
|
||||
.mapNotNull { PyResolveUtil.getImportedElementQName(it) }
|
||||
.filterIsInstance<PyQualifiedNameOwner>()
|
||||
.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<PyTargetExpression>()
|
||||
.mapNotNull { it.findAssignedValue() as? PySubscriptionExpression }
|
||||
.filterIsInstance<PySubscriptionExpression>()
|
||||
.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<PySubscriptionExpression>()
|
||||
.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<PsiElement>?, Set<PsiElement>> {
|
||||
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<PySubscriptionExpression>()
|
||||
.filter { myTypeEvalContext.maySwitchToAST(it) }
|
||||
.forEach {
|
||||
val operand = it.operand
|
||||
val generic =
|
||||
@@ -368,7 +352,7 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
val superClassTypeVars = parameters
|
||||
.asSequence()
|
||||
.filterIsInstance<PyReferenceExpression>()
|
||||
.map { PyResolveUtil.fullMultiResolveLocally(it).toSet() }
|
||||
.map { multiFollowAssignmentsChain(it, this::followNotTypeVar).toSet() }
|
||||
.fold(emptySet<PsiElement>(), { 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<PsiElement> {
|
||||
val resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext)
|
||||
return referenceExpression.multiFollowAssignmentsChain(resolveContext, follow).mapNotNull { it.element }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<QualifiedRatedResolveResult> multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext) {
|
||||
public List<QualifiedRatedResolveResult> multiFollowAssignmentsChain(@NotNull PyResolveContext resolveContext,
|
||||
@NotNull Predicate<? super PyTargetExpression> follow) {
|
||||
final List<QualifiedRatedResolveResult> result = new ArrayList<>();
|
||||
final Queue<MultiFollowQueueNode> queue = new LinkedList<>();
|
||||
final Set<PyReferenceExpression> 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<PsiElement> assignedFromElements = context.maySwitchToAST(target)
|
||||
|
||||
@@ -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.
|
||||
* <i>Note: the returned list could contain null values.</i>
|
||||
*/
|
||||
@NotNull
|
||||
public static List<PsiElement> fullMultiResolveLocally(@NotNull PyReferenceExpression referenceExpression) {
|
||||
return fullMultiResolveLocally(referenceExpression, new HashSet<>());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PsiElement> fullMultiResolveLocally(@NotNull PyReferenceExpression referenceExpression,
|
||||
@NotNull Set<PyReferenceExpression> visited) {
|
||||
|
||||
Reference in New Issue
Block a user