Detect recursive type parameter definition when checking for assignability

Fixes EA-137370 - SOE: TypeConversionUtil.isNarrowingReferenceConversionAllowed
This commit is contained in:
Tagir Valeev
2019-02-21 12:58:25 +07:00
parent 9182ce2d75
commit e72a28c3e6
5 changed files with 55 additions and 29 deletions
@@ -606,7 +606,7 @@ public class HighlightClassUtil {
@Nullable
static HighlightInfo checkCyclicInheritance(@NotNull PsiClass aClass) {
PsiClass circularClass = getCircularClass(aClass, new HashSet<>());
PsiClass circularClass = InheritanceUtil.getCircularClass(aClass);
if (circularClass != null) {
String description = JavaErrorMessages.message("cyclic.inheritance", HighlightUtil.formatClass(circularClass));
TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
@@ -615,31 +615,6 @@ public class HighlightClassUtil {
return null;
}
@Nullable
public static PsiClass getCircularClass(@NotNull PsiClass aClass, @NotNull Collection<? super PsiClass> usedClasses) {
if (usedClasses.contains(aClass)) {
return aClass;
}
try {
usedClasses.add(aClass);
PsiClass[] superTypes = aClass.getSupers();
for (PsiElement superType : superTypes) {
while (superType instanceof PsiClass) {
if (!CommonClassNames.JAVA_LANG_OBJECT.equals(((PsiClass)superType).getQualifiedName())) {
PsiClass circularClass = getCircularClass((PsiClass)superType, usedClasses);
if (circularClass != null) return circularClass;
}
// check class qualifier
superType = superType.getParent();
}
}
}
finally {
usedClasses.remove(aClass);
}
return null;
}
@Nullable
static HighlightInfo checkExtendsDuplicate(@NotNull PsiJavaCodeReferenceElement element, @Nullable PsiElement resolved, @NotNull PsiFile containingFile) {
if (!(element.getParent() instanceof PsiReferenceList)) return null;
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -188,4 +189,38 @@ public class InheritanceUtil {
return true;
}
@Nullable
private static PsiClass getCircularClass(@NotNull PsiClass aClass, @NotNull Collection<? super PsiClass> usedClasses) {
if (usedClasses.contains(aClass)) {
return aClass;
}
try {
usedClasses.add(aClass);
PsiClass[] superTypes = aClass.getSupers();
for (PsiElement superType : superTypes) {
while (superType instanceof PsiClass) {
if (!CommonClassNames.JAVA_LANG_OBJECT.equals(((PsiClass)superType).getQualifiedName())) {
PsiClass circularClass = getCircularClass((PsiClass)superType, usedClasses);
if (circularClass != null) return circularClass;
}
// check class qualifier
superType = superType.getParent();
}
}
}
finally {
usedClasses.remove(aClass);
}
return null;
}
/**
* Detects a circular inheritance
* @param aClass a class to check
* @return a class which is a part of the inheritance loop; null if no circular inheritance was detected
*/
@Nullable
public static PsiClass getCircularClass(@NotNull PsiClass aClass) {
return getCircularClass(aClass, new HashSet<>());
}
}
@@ -320,8 +320,12 @@ public class TypeConversionUtil {
final PsiClassType.ClassResolveResult result = superType.resolveGenerics();
final PsiClass superClass = result.getElement();
if (superClass != null) {
PsiElementFactory factory = JavaPsiFacade.getElementFactory(typeParameter.getProject());
if (superClass instanceof PsiTypeParameter && InheritanceUtil.getCircularClass(superClass) != null) {
return factory.createTypeByFQClassName(JAVA_LANG_OBJECT, typeParameter.getResolveScope());
}
final PsiSubstitutor substitutor = result.getSubstitutor().put(typeParameter, null);
return JavaPsiFacade.getElementFactory(typeParameter.getProject()).createType(superClass, substitutor);
return factory.createType(superClass, substitutor);
}
return superType;
}
@@ -49,3 +49,16 @@ class TestNonNarrowingConversion<T extends Z> {
return 1;
}
}
class TestRecursiveTypeParameter {
static <<error descr="Cyclic inheritance involving 'T'"></error>T extends T> void test(T t) {
<error descr="Incompatible types. Found: 'T', required: 'java.lang.String'">String x = t;</error>
<error descr="Incompatible types. Found: 'java.lang.String', required: 'T'">t = x</error>;
}
static <<error descr="Cyclic inheritance involving 'A'"></error>A extends B, B extends A> void test(A a, B b) {
a = b;
b = a;
<error descr="Incompatible types. Found: 'A', required: 'java.lang.String'">String x = a;</error>
<error descr="Incompatible types. Found: 'java.lang.String', required: 'A'">a = x</error>;
}
}
@@ -2,7 +2,6 @@
package org.jetbrains.plugins.groovy.annotator;
import com.intellij.codeInsight.ClassUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightClassUtil;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
@@ -1959,7 +1958,7 @@ public class GroovyAnnotator extends GroovyElementVisitor {
private static void checkCyclicInheritance(AnnotationHolder holder,
@NotNull GrTypeDefinition typeDefinition) {
final PsiClass psiClass = HighlightClassUtil.getCircularClass(typeDefinition, new HashSet<>());
final PsiClass psiClass = InheritanceUtil.getCircularClass(typeDefinition);
if (psiClass != null) {
String qname = psiClass.getQualifiedName();
assert qname != null;