diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaVarTypeUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaVarTypeUtil.java new file mode 100644 index 000000000000..bb85b91e505f --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaVarTypeUtil.java @@ -0,0 +1,215 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.psi.impl.source; + +import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; + +public class JavaVarTypeUtil { + + public static PsiType getUpwardProjection(@NotNull PsiType t) { + return t.accept(new UpwardProjectionTypeVisitor()); + } + + public static PsiType getDownwardProjection(@NotNull PsiType type) { + return type.accept(new DownwardProjectionTypeVisitor()); + } + + private static boolean mentionsRestrictedTypeVariables(PsiType type) { + return type.accept(new PsiTypeVisitor() { + @Override + public Boolean visitType(PsiType type) { + return false; + } + + @Override + public Boolean visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) { + return true; + } + }); + } + + private static class UpwardProjectionTypeVisitor extends PsiTypeVisitorEx { + @Override + public PsiType visitType(PsiType type) { + return type; + } + + @Nullable + @Override + public PsiType visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) { + return capturedWildcardType.getUpperBound().accept(this); + } + + @Override + public PsiType visitArrayType(PsiArrayType arrayType) { + PsiType componentType = arrayType.getComponentType(); + return componentType.accept(this).createArrayType(); + } + + @Nullable + @Override + public PsiType visitLambdaExpressionType(PsiLambdaExpressionType lambdaExpressionType) { + return lambdaExpressionType; + } + + @Override + public PsiType visitMethodReferenceType(PsiMethodReferenceType methodReferenceType) { + return methodReferenceType; + } + + @Override + public PsiType visitIntersectionType(PsiIntersectionType intersectionType) { + return PsiIntersectionType.createIntersection(Arrays.stream(intersectionType.getConjuncts()) + .map(conjunct -> conjunct.accept(this)) + .toArray(PsiType[]::new)); + } + + @Override + public PsiType visitClassType(PsiClassType classType) { + PsiClassType.ClassResolveResult result = classType.resolveGenerics(); + PsiClass aClass = result.getElement(); + if (aClass != null) { + PsiManager manager = aClass.getManager(); + PsiSubstitutor targetSubstitutor = PsiSubstitutor.EMPTY; + PsiSubstitutor substitutor = result.getSubstitutor(); + for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(aClass)) { + PsiType ai = substitutor.substitute(parameter); + targetSubstitutor = targetSubstitutor.put(parameter, ai); + if (ai != null && mentionsRestrictedTypeVariables(ai)) { + if (ai instanceof PsiWildcardType) { + + if (((PsiWildcardType)ai).isExtends()) { + targetSubstitutor = targetSubstitutor.put(parameter, + PsiWildcardType.createExtends(manager, ((PsiWildcardType)ai).getExtendsBound().accept(this))); + } + + if (((PsiWildcardType)ai).isSuper()) { + targetSubstitutor = targetSubstitutor.put(parameter, createDownwardProjection(manager, ((PsiWildcardType)ai).getSuperBound())); + } + + } + else { + PsiType U = ai.accept(this); + if (!U.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) && tryUpperBound(aClass, parameter, U)) { + targetSubstitutor = targetSubstitutor.put(parameter, PsiWildcardType.createExtends(manager, U)); + } + else { + targetSubstitutor = targetSubstitutor.put(parameter, createDownwardProjection(manager, ai)); + } + } + } + } + return JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass, targetSubstitutor); + } + return classType; + } + + private static PsiWildcardType createDownwardProjection(PsiManager manager, PsiType bound) { + PsiType downwardProjection = getDownwardProjection(bound); + return downwardProjection != PsiType.NULL ? PsiWildcardType.createSuper(manager, downwardProjection) + : PsiWildcardType.createUnbounded(manager); + } + + private static boolean tryUpperBound(PsiClass aClass, PsiTypeParameter parameter, PsiType U) { + PsiClassType[] extendsListTypes = parameter.getExtendsListTypes(); + if (extendsListTypes.length == 0) return true; + PsiType bi = PsiIntersectionType.createIntersection(extendsListTypes); + return PsiPolyExpressionUtil.mentionsTypeParameters(bi, ContainerUtil.newHashSet(aClass.getTypeParameters())) || + !U.isAssignableFrom(bi); + } + } + + private static class DownwardProjectionTypeVisitor extends PsiTypeVisitor { + @Override + public PsiType visitType(PsiType type) { + return type; + } + + @Override + public PsiType visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) { + return capturedWildcardType.getLowerBound().accept(this); + } + + @Override + public PsiType visitArrayType(PsiArrayType arrayType) { + PsiType projection = arrayType.getComponentType().accept(this); + if (projection == PsiType.NULL) return PsiType.NULL; + return projection.createArrayType(); + } + + @Override + public PsiType visitIntersectionType(PsiIntersectionType intersectionType) { + PsiType[] conjuncts = Arrays.stream(intersectionType.getConjuncts()).map(conjunct -> conjunct.accept(this)).toArray(PsiType[]::new); + if (ArrayUtil.find(conjuncts, PsiType.NULL) > -1) return PsiType.NULL; + return PsiIntersectionType.createIntersection(conjuncts); + } + + @Override + public PsiType visitLambdaExpressionType(PsiLambdaExpressionType lambdaExpressionType) { + return lambdaExpressionType; + } + + @Override + public PsiType visitMethodReferenceType(PsiMethodReferenceType methodReferenceType) { + return methodReferenceType; + } + + @Override + public PsiType visitClassType(PsiClassType classType) { + PsiClassType.ClassResolveResult result = classType.resolveGenerics(); + PsiClass aClass = result.getElement(); + if (aClass != null) { + PsiSubstitutor substitutor = result.getSubstitutor(); + PsiSubstitutor targetSubstitutor = PsiSubstitutor.EMPTY; + for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(aClass)) { + PsiType ai = substitutor.substitute(parameter); + if (ai == null) return PsiType.NULL; + if (!mentionsRestrictedTypeVariables(ai)) { + targetSubstitutor = targetSubstitutor.put(parameter, ai); + } + else if (ai instanceof PsiWildcardType) { + if (((PsiWildcardType)ai).isExtends()) { + PsiType extendsBound = ((PsiWildcardType)ai).getExtendsBound(); + PsiType projection = extendsBound.accept(this); + if (projection == PsiType.NULL) return PsiType.NULL; + targetSubstitutor = targetSubstitutor.put(parameter, PsiWildcardType.createExtends(parameter.getManager(), projection)); + } + else if (((PsiWildcardType)ai).isSuper()) { + PsiType superBound = ((PsiWildcardType)ai).getSuperBound(); + targetSubstitutor = targetSubstitutor.put(parameter, getUpwardProjection(superBound)); + } + else { + return PsiType.NULL; + } + } + else { + return PsiType.NULL; + } + } + return JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass, targetSubstitutor); + } + return PsiType.NULL; + } + } +} diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiTypeElementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiTypeElementImpl.java index d0ca28db4fb5..4222380c92a6 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiTypeElementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiTypeElementImpl.java @@ -170,7 +170,8 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl if (e instanceof PsiExpression) { if (!(e instanceof PsiArrayInitializerExpression) && !isSelfReferenced((PsiExpression)e, parent)) { - return ((PsiExpression)e).getType(); + PsiType type = ((PsiExpression)e).getType(); + return type == null ? null : JavaVarTypeUtil.getUpwardProjection(type); } return null; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advLVTI/UpwardProjection.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advLVTI/UpwardProjection.java new file mode 100644 index 000000000000..9fd49ebf0422 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advLVTI/UpwardProjection.java @@ -0,0 +1,46 @@ + +import java.util.List; + +class Main { + + void fooExtends(List list) { + var x = list.get(0); + x = ""; + var y = list; + y.add(""); + } + + void fooSuper(List list) { + var x = list.get(0); + x = ""; + var y = list; + y.add(""); + } + + void fooX(X x) { + var v = x; + v.add(""); + + var e = x.get(); + e = ""; + } + + void fooXArray(X x) { + var v = m(x.get()); + String s = v[1]; + v[1] = s; + + var e = m(x); + e[0].add(""); + } + + + M[] m(M m) { + return null; + } +} + +abstract class X { + abstract T get(); + abstract void add(T t); +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvLVTIHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvLVTIHighlightingTest.java index f91aec55f6b7..34eaed628228 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvLVTIHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAdvLVTIHighlightingTest.java @@ -38,6 +38,7 @@ public class LightAdvLVTIHighlightingTest extends LightDaemonAnalyzerTestCase { public void testSimpleAvailability() { doTest(); } public void testVarClassNameConflicts() { doTest(); } public void testStandaloneInVarContext() { doTest(); } + public void testUpwardProjection() { doTest(); } @Override protected Sdk getProjectJDK() {