[java-highlighting] IDEA-340618 Update String templates type and exception for Java 22

GitOrigin-RevId: fc208b0f19ef048bce0baf462b227f6b16d34a2f
This commit is contained in:
Tagir Valeev
2023-12-12 16:50:47 +01:00
committed by intellij-monorepo-bot
parent 36046b5513
commit 46e00356c2
5 changed files with 167 additions and 46 deletions

View File

@@ -2,6 +2,7 @@
package com.intellij.codeInsight;
import com.intellij.openapi.util.Pair;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.PsiClassImplUtil;
@@ -266,7 +267,7 @@ public final class ExceptionUtil {
private static Set<PsiClassType> collectUnhandledExceptions(@NotNull PsiElement element,
@Nullable PsiElement topElement,
@Nullable Set<PsiClassType> foundExceptions,
@NotNull Predicate<? super PsiCallExpression> callFilter) {
@NotNull Predicate<? super PsiCall> callFilter) {
Collection<PsiClassType> unhandledExceptions = null;
if (element instanceof PsiCallExpression) {
PsiCallExpression expression = (PsiCallExpression)element;
@@ -450,9 +451,9 @@ public final class ExceptionUtil {
}
@NotNull
private static List<PsiClassType> getUnhandledExceptions(@NotNull final PsiCallExpression methodCall,
private static List<PsiClassType> getUnhandledExceptions(@NotNull final PsiCall methodCall,
@Nullable final PsiElement topElement,
@NotNull Predicate<? super PsiCallExpression> skipCondition) {
@NotNull Predicate<? super PsiCall> skipCondition) {
//exceptions only influence the invocation type after overload resolution is complete
if (MethodCandidateInfo.isOverloadCheck()) {
return Collections.emptyList();
@@ -471,49 +472,83 @@ public final class ExceptionUtil {
if (thrownExceptions.length == 0) {
return Collections.emptyList();
}
if (!isArrayClone(method, methodCall) && methodCall instanceof PsiMethodCallExpression) {
PsiFile containingFile = methodCall.getContainingFile();
MethodResolverProcessor processor = new MethodResolverProcessor((PsiMethodCallExpression)methodCall, containingFile);
try {
PsiScopesUtil.setupAndRunProcessor(processor, methodCall, false);
// Resolve other signatures in case of multiple interface inheritance
// e.g. consider
// interface X {void a() throws A;} interface Y{void a() throws B;} class Z extends X, Y {}
// here normal resolve returns X.a(), but we should check throws clauses for both a() methods.
// (see JLS 15.12.2.5)
final List<Pair<PsiMethod, PsiSubstitutor>> candidates = ContainerUtil.mapNotNull(
processor.getResults(), info -> {
PsiElement element1 = info.getElement();
if (info instanceof MethodCandidateInfo &&
element1 != method && //don't check self
MethodSignatureUtil.areSignaturesEqual(method, (PsiMethod)element1) &&
!((PsiMethod)element1).hasModifierProperty(PsiModifier.PRIVATE) &&
!MethodSignatureUtil.isSuperMethod((PsiMethod)element1, method) &&
!(((MethodCandidateInfo)info).isToInferApplicability() && !((MethodCandidateInfo)info).isApplicable())) {
return Pair.create((PsiMethod)element1, ((MethodCandidateInfo)info).getSubstitutor(false));
}
return null;
});
if (!candidates.isEmpty()) {
GlobalSearchScope scope = methodCall.getResolveScope();
List<PsiClassType> ex = collectSubstituted(result.getSubstitutor(), thrownExceptions, scope);
for (Pair<PsiMethod, PsiSubstitutor> pair : candidates) {
final PsiClassType[] exceptions = pair.first.getThrowsList().getReferencedTypes();
if (exceptions.length == 0) {
return Collections.emptyList();
}
retainExceptions(ex, collectSubstituted(pair.second, exceptions, scope));
}
return getUnhandledExceptions(methodCall, topElement, PsiSubstitutor.EMPTY, ex.toArray(PsiClassType.EMPTY_ARRAY));
}
}
catch (MethodProcessorSetupFailedException ignore) {
List<Pair<PsiMethod, PsiSubstitutor>> otherTargets;
if (methodCall instanceof PsiTemplateExpression) {
otherTargets = getTemplateTargets((PsiTemplateExpression)methodCall, method);
}
else if (!isArrayClone(method, methodCall) && methodCall instanceof PsiMethodCallExpression) {
otherTargets = getMethodCallTargets((PsiMethodCallExpression)methodCall, method);
}
else {
otherTargets = Collections.emptyList();
}
GlobalSearchScope scope = methodCall.getResolveScope();
List<PsiClassType> ex = collectSubstituted(result.getSubstitutor(), thrownExceptions, scope);
for (Pair<PsiMethod, PsiSubstitutor> pair : otherTargets) {
final PsiClassType[] exceptions = pair.first.getThrowsList().getReferencedTypes();
if (exceptions.length == 0) {
return Collections.emptyList();
}
retainExceptions(ex, collectSubstituted(pair.second, exceptions, scope));
}
return getUnhandledExceptions(methodCall, topElement, PsiSubstitutor.EMPTY, ex.toArray(PsiClassType.EMPTY_ARRAY));
}
return getUnhandledExceptions(method, methodCall, topElement, result::getSubstitutor);
private static List<Pair<PsiMethod, PsiSubstitutor>> getMethodCallTargets(@NotNull PsiMethodCallExpression call,
@NotNull PsiMethod method) {
PsiFile containingFile = call.getContainingFile();
MethodResolverProcessor processor = new MethodResolverProcessor(call, containingFile);
try {
PsiScopesUtil.setupAndRunProcessor(processor, call, false);
// Resolve other signatures in case of multiple interface inheritance
// e.g. consider
// interface X {void a() throws A;} interface Y{void a() throws B;} class Z extends X, Y {}
// here normal resolve returns X.a(), but we should check throws clauses for both a() methods.
// (see JLS 15.12.2.5)
return ContainerUtil.mapNotNull(
processor.getResults(), info -> {
PsiElement element1 = info.getElement();
if (info instanceof MethodCandidateInfo &&
element1 != method && //don't check self
MethodSignatureUtil.areSignaturesEqual(method, (PsiMethod)element1) &&
!((PsiMethod)element1).hasModifierProperty(PsiModifier.PRIVATE) &&
!MethodSignatureUtil.isSuperMethod((PsiMethod)element1, method) &&
!(((MethodCandidateInfo)info).isToInferApplicability() && !((MethodCandidateInfo)info).isApplicable())) {
return Pair.create((PsiMethod)element1, ((MethodCandidateInfo)info).getSubstitutor(false));
}
return null;
});
}
catch (MethodProcessorSetupFailedException ignore) {
}
return Collections.emptyList();
}
private static List<Pair<PsiMethod, PsiSubstitutor>> getTemplateTargets(@NotNull PsiTemplateExpression methodCall,
@NotNull PsiMethod baseMethod) {
PsiExpression processor = methodCall.getProcessor();
if (processor == null) return Collections.emptyList();
PsiType type = processor.getType();
if (type == null) return Collections.emptyList();
List<Pair<PsiMethod, PsiSubstitutor>> candidates = new ArrayList<>();
// Resolve other signatures in case of multiple interface inheritance
for (PsiClassType classType : PsiTypesUtil.getClassTypeComponents(type)) {
if (InheritanceUtil.isInheritor(classType, CommonClassNames.JAVA_LANG_STRING_TEMPLATE_PROCESSOR)) {
final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics();
final PsiClass aClass = resolveResult.getElement();
if (aClass == null) continue;
for (PsiMethod foundMethod : aClass.findMethodsBySignature(baseMethod, true)) {
if (foundMethod == baseMethod) continue;
PsiClass methodContainingClass = foundMethod.getContainingClass();
if (methodContainingClass == null) continue;
final PsiSubstitutor substitutor =
TypeConversionUtil.getClassSubstitutor(methodContainingClass, aClass, resolveResult.getSubstitutor());
if (substitutor == null) continue;
candidates.add(Pair.create(foundMethod, substitutor));
}
}
}
return candidates;
}
public static void retainExceptions(List<PsiClassType> ex, List<? extends PsiClassType> thrownEx) {
@@ -553,6 +588,13 @@ public final class ExceptionUtil {
ex.add((PsiClassType)upperBound);
}
}
else if (psiType instanceof PsiWildcardType) {
final PsiWildcardType capturedWildcardType = (PsiWildcardType)psiType;
final PsiType upperBound = capturedWildcardType.getExtendsBound();
if (upperBound instanceof PsiClassType) {
ex.add((PsiClassType)upperBound);
}
}
}
return ex;
}
@@ -620,6 +662,9 @@ public final class ExceptionUtil {
@NotNull
public static List<PsiClassType> getUnhandledProcessorExceptions(@NotNull PsiTemplateExpression templateExpression,
@Nullable PsiElement topElement) {
if (!PsiUtil.getLanguageLevel(templateExpression).equals(LanguageLevel.JDK_21_PREVIEW)) {
return getUnhandledExceptions(templateExpression, topElement, c -> false);
}
PsiExpression processor = templateExpression.getProcessor();
if (processor == null) return Collections.emptyList();
PsiType type = processor.getType();

View File

@@ -2,6 +2,7 @@
package com.intellij.psi.impl.source.tree.java;
import com.intellij.openapi.util.Ref;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.infos.MethodCandidateInfo;
@@ -117,6 +118,13 @@ public final class PsiTemplateExpressionImpl extends ExpressionPsiElement implem
@Override
public @Nullable PsiType getType() {
if (!PsiUtil.getLanguageLevel(this).equals(LanguageLevel.JDK_21_PREVIEW)) {
JavaResolveResult result = resolveMethodGenerics();
PsiMethod method = (PsiMethod)result.getElement();
if (method != null) {
return result.getSubstitutor().substitute(method.getReturnType());
}
}
final PsiExpression processor = getProcessor();
if (processor == null) return null;
PsiType type = processor.getType();

View File

@@ -1,3 +1,5 @@
import java.io.*;
class X {
void processorMissing() {
@@ -120,4 +122,34 @@ class X {
String a = STR."\{<error descr="Expression with type 'void' not allowed as string template embedded expression">voidExpression()</error>}";
System.out.println(a);
}
interface AnyProcessor extends StringTemplate.Processor<Object, Throwable> {}
interface FooProcessor extends AnyProcessor {
@Override
Object process(StringTemplate stringTemplate) throws Ex, IOException;
}
interface BarProcessor extends AnyProcessor {
@Override
Object process(StringTemplate stringTemplate) throws Ex2, EOFException, FileNotFoundException;
}
interface FooBarProcessor extends FooProcessor, BarProcessor {}
static void test(FooBarProcessor fooBarProcessor) {
System.out.println(<error descr="Unhandled exception: java.lang.Throwable">fooBarProcessor.""</error>);
}
static class IntegerProcessor implements StringTemplate.Processor<Object, RuntimeException> {
@Override
public Integer process(StringTemplate template) {
return 1;
}
}
void myTest() {
<error descr="Incompatible types. Found: 'java.lang.Object', required: 'java.lang.Integer'">Integer x = new IntegerProcessor()."hello";</error>
System.out.println(x);
}
}

View File

@@ -1,3 +1,5 @@
import java.io.*;
class X {
void processorMissing() {
@@ -61,8 +63,8 @@ class X {
public static void testCovariant() {
Covariant proc = new Covariant();
// As of Java 21, covariant processors are not supported
<error descr="Incompatible types. Found: 'java.lang.Object', required: 'java.lang.Integer'">Integer i = proc."hello";</error>
// In Java 22, covariant processors are supported
Integer i = proc."hello";
}
static class CovariantException implements StringTemplate.Processor<Integer, Exception> {
@@ -93,10 +95,10 @@ class X {
public static void testCovariantException() {
CovariantException proc = new CovariantException();
// As of Java 21, covariant processors are not supported
Integer i = <error descr="Unhandled exception: java.lang.Exception">proc."hello";</error>
Integer i = <error descr="Unhandled exception: X.Ex">proc."hello";</error>
try {
Integer i2 = <error descr="Unhandled exception: java.lang.Exception">proc."hello";</error>
Integer i2 = proc."hello";
}
catch (Ex ex) {}
}
@@ -120,4 +122,34 @@ class X {
String a = STR."\{<error descr="Expression with type 'void' not allowed as string template embedded expression">voidExpression()</error>}";
System.out.println(a);
}
interface AnyProcessor extends StringTemplate.Processor<Object, Throwable> {}
interface FooProcessor extends AnyProcessor {
@Override
Object process(StringTemplate stringTemplate) throws Ex, IOException;
}
interface BarProcessor extends AnyProcessor {
@Override
Object process(StringTemplate stringTemplate) throws Ex2, EOFException, FileNotFoundException;
}
interface FooBarProcessor extends FooProcessor, BarProcessor {}
static void test(FooBarProcessor fooBarProcessor) {
System.out.println(<error descr="Unhandled exceptions: java.io.EOFException, java.io.FileNotFoundException">fooBarProcessor.""</error>);
}
static class IntegerProcessor implements StringTemplate.Processor<Object, RuntimeException> {
@Override
public Integer process(StringTemplate template) {
return 1;
}
}
void myTest() {
Integer x = new IntegerProcessor()."hello";
System.out.println(x);
}
}

View File

@@ -2,6 +2,8 @@
package com.intellij.java.codeInsight.daemon;
import com.intellij.JavaTestUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
@@ -19,6 +21,8 @@ public class LightStringTemplatesHighlightingTest extends LightJavaCodeInsightFi
}
public void testStringTemplates() { doTest(); }
public void testStringTemplatesJava22() { IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_22_PREVIEW, () -> doTest()); }
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");