[java] warn on non-instantiatable intersection types (IDEA-277529)

extend for any context

GitOrigin-RevId: d6f499348c8278bcb58951900ebf116440719082
This commit is contained in:
Anna Kozlova
2021-09-06 14:16:08 +02:00
committed by intellij-monorepo-bot
parent c752c69914
commit 1f0f1acfbc
4 changed files with 36 additions and 36 deletions

View File

@@ -557,7 +557,7 @@ type.constraint.assignability.explanation.subtype.of.subtype={0} is already know
type.constraint.assignability.explanation.not.instance.of={0} is known to be not {1}
type.constraint.assignability.explanation.not.instance.of.supertype={0} is known to be not {1} which is a supertype of {2}
type.constraint.assignability.explanation.definitely.inconvertible={0} is known to be {1} which is definitely incompatible with {2}
inspection.message.javac.quick.intersection.type.problem=Though assignment is formal correct, it could lead to ClassCastException at runtime. Expected: ''{0}'', actual: ''{1}''
inspection.message.javac.quick.intersection.type.problem=Intersection type ''{0}'' cannot be instantiated, because ''{1}'' is final
suggest.package.private.visibility.level.for.classes.in.exported.packages.java.9=Suggest package-private visibility level for classes in exported packages (Java 9+)
generate.members.position.at.caret=At caret
generate.members.position.after.equals.and.hashcode=After equals() and hashCode()

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.compiler;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
@@ -25,11 +25,15 @@ import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.PsiReplacementUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Objects;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
public class JavacQuirksInspectionVisitor extends JavaElementVisitor {
@@ -82,7 +86,6 @@ public class JavacQuirksInspectionVisitor extends JavaElementVisitor {
final PsiExpression rExpression = assignment.getRExpression();
if (rExpression == null) return;
PsiJavaToken operationSign = assignment.getOperationSign();
checkIntersectionType(lType, rExpression.getType(), operationSign);
IElementType eqOpSign = operationSign.getTokenType();
IElementType opSign = TypeConversionUtil.convertEQtoOperation(eqOpSign);
@@ -100,44 +103,16 @@ public class JavacQuirksInspectionVisitor extends JavaElementVisitor {
}
}
@Override
public void visitVariable(PsiVariable variable) {
super.visitVariable(variable);
final PsiExpression initializer = variable.getInitializer();
if (initializer != null) {
final PsiElement assignmentToken = PsiTreeUtil.skipWhitespacesBackward(initializer);
if (assignmentToken != null) {
checkIntersectionType(variable.getType(), initializer.getType(), assignmentToken);
}
}
}
private void checkIntersectionType(@NotNull PsiType lType, @Nullable PsiType rType, @NotNull PsiElement elementToHighlight) {
if (rType instanceof PsiIntersectionType && TypeConversionUtil.isAssignable(lType, rType)) {
final PsiClass psiClass = PsiUtil.resolveClassInType(lType);
if (psiClass != null && psiClass.hasModifierProperty(PsiModifier.FINAL)) {
final PsiType[] conjuncts = ((PsiIntersectionType)rType).getConjuncts();
for (PsiType conjunct : conjuncts) {
if (!TypeConversionUtil.isAssignable(conjunct, lType)) {
final String descriptionTemplate =
JavaAnalysisBundle.message("inspection.message.javac.quick.intersection.type.problem", lType.getPresentableText(),rType.getPresentableText());
myHolder.registerProblem(elementToHighlight, descriptionTemplate);
}
}
}
}
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
super.visitMethodCallExpression(expression);
if (PsiUtil.isLanguageLevel8OrHigher(expression) && expression.getTypeArguments().length == 0) {
if (expression.getTypeArguments().length == 0) {
PsiExpression[] args = expression.getArgumentList().getExpressions();
JavaResolveResult resolveResult = expression.resolveMethodGenerics();
if (resolveResult instanceof MethodCandidateInfo) {
PsiMethod method = ((MethodCandidateInfo)resolveResult).getElement();
if (method.isVarArgs() && method.hasTypeParameters() && args.length > method.getParameterList().getParametersCount() + 50) {
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
if (PsiUtil.isLanguageLevel8OrHigher(expression) && method.isVarArgs() && method.hasTypeParameters() && args.length > method.getParameterList().getParametersCount() + 50) {
for (PsiTypeParameter typeParameter : method.getTypeParameters()) {
if (!PsiTypesUtil.isDenotableType(substitutor.substitute(typeParameter), expression)) {
return;
@@ -155,6 +130,31 @@ public class JavacQuirksInspectionVisitor extends JavaElementVisitor {
}
}
}
if (resolveResult.isValidResult()) {
for (PsiType value : substitutor.getSubstitutionMap().values()) {
if (value instanceof PsiIntersectionType) {
PsiClass aClass = Arrays.stream(((PsiIntersectionType)value).getConjuncts())
.map(PsiUtil::resolveClassInClassTypeOnly)
.filter(_aClass -> _aClass != null && _aClass.hasModifierProperty(PsiModifier.FINAL))
.findFirst().orElse(null);
if (aClass != null && aClass.hasModifierProperty(PsiModifier.FINAL)) {
for (PsiType conjunct : ((PsiIntersectionType)value).getConjuncts()) {
PsiClass currentClass = PsiUtil.resolveClassInClassTypeOnly(conjunct);
if (currentClass != null &&
!aClass.equals(currentClass) &&
!aClass.isInheritor(currentClass, true)) {
final String descriptionTemplate =
JavaAnalysisBundle.message("inspection.message.javac.quick.intersection.type.problem",
value.getPresentableText(), ObjectUtils.notNull(aClass.getQualifiedName(),
Objects.requireNonNull(aClass.getName())));
myHolder.registerProblem(expression.getMethodExpression(), descriptionTemplate);
}
}
break;
}
}
}
}
}
}
}

View File

@@ -4,7 +4,7 @@ class Test {
}
{
String m <warning descr="Though assignment is formal correct, it could lead to ClassCastException at runtime. Expected: 'String', actual: 'Runnable & String'">=</warning> f();
String m = <warning descr="Intersection type 'Runnable & String' cannot be instantiated, because 'java.lang.String' is final">f</warning>();
System.out.println(m);
}
}

View File

@@ -160,7 +160,7 @@ Boolean.class, Boolean.TYPE /*,String[].class */ /*,BigDecimal.class*/);
///////////////////////
class Axx {
<T extends Runnable> T a() {
String s <warning descr="Though assignment is formal correct, it could lead to ClassCastException at runtime. Expected: 'String', actual: 'Runnable & String'">=</warning> a();
String s = <warning descr="Intersection type 'Runnable & String' cannot be instantiated, because 'java.lang.String' is final">a</warning>();
s.hashCode();
return null;
}