diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index 7ab50e550f75..183ba5b40369 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -311,6 +311,7 @@ call.ambiguous.tooltip=\ {1}in {2}\\ and\ {3}in {4}\\ match\ +call.parsed.as.deconstruction.pattern=Constant expression, pattern or null is required array.illegal.initializer=Illegal initializer for ''{0}'' array.initializer.not.allowed=Array initializer is not allowed here @@ -325,12 +326,22 @@ pattern.type.pattern.expected=Type pattern expected pattern.deconstruction.variable=Identifier is not allowed here pattern.deconstruction.annotation=Annotations are not allowed in deconstruction pattern types pattern.deconstruction.requires.record=Deconstruction pattern can only be applied to a record, ''{0}'' is not a record +pattern.deconstruction.count.mismatch=Incorrect number of nested patterns: expected {0} but found {1} pattern.not.exhaustive=Pattern ''{0}'' is not exhaustive on ''{1}'' pattern.unsafe.cast=''{0}'' cannot be safely cast to ''{1}'' pattern.cannot.infer.type=Cannot infer pattern type: {0} -pattern.deconstruction.count.mismatch=Incorrect number of nested patterns: expected {0} but found {1} +pattern.instanceof.supertype=Pattern type ''{0}'' is a supertype of expression type ''{1}'' +pattern.instanceof.equals=Pattern type ''{0}'' is the same as expression type + +instanceof.type.parameter=Class or array expected +instanceof.illegal.generic.type=Illegal generic type for instanceof +instanceof.unsafe.cast=''{0}'' cannot be safely cast to ''{1}'' cast.inconvertible=Inconvertible types; cannot cast ''{0}'' to ''{1}'' +cast.intersection.not.interface=Interface expected here +cast.intersection.unexpected.type=Unexpected type: class is expected +cast.intersection.repeated.interface=Repeated interface +cast.intersection.inheritance.clash={0} cannot be inherited with different arguments: {1} and {2} expression.expected=Expression expected expression.super.dot.expected='.' expected diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java index 368c4b25f2d9..f5940d05c7d1 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/ExpressionChecker.java @@ -6,6 +6,7 @@ import com.intellij.core.JavaPsiBundle; import com.intellij.java.codeserver.highlighting.errors.*; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; import com.intellij.pom.java.JavaFeature; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; @@ -720,6 +721,87 @@ final class ExpressionChecker { } } + void checkInconvertibleTypeCast(@NotNull PsiTypeCastExpression expression) { + PsiTypeElement castTypeElement = expression.getCastType(); + if (castTypeElement == null) return; + PsiType castType = castTypeElement.getType(); + + PsiExpression operand = expression.getOperand(); + if (operand == null) return; + PsiType operandType = operand.getType(); + + if (operandType != null && + !TypeConversionUtil.areTypesConvertible(operandType, castType, PsiUtil.getLanguageLevel(expression)) && + !PsiUtil.isInSignaturePolymorphicCall(expression)) { + if (IncompleteModelUtil.isIncompleteModel(expression) && IncompleteModelUtil.isPotentiallyConvertible(castType, operand)) { + return; + } + myVisitor.report(JavaErrorKinds.CAST_INCONVERTIBLE.create( + expression, new JavaIncompatibleTypeErrorContext(operandType, castType))); + } + } + + /** + * 15.16 Cast Expressions + * ( ReferenceType {AdditionalBound} ) expression, where AdditionalBound: & InterfaceType then all must be true + * - ReferenceType must denote a class or interface type. + * - The erasures of all the listed types must be pairwise different. + * - No two listed types may be subtypes of different parameterization of the same generic interface. + */ + void checkIntersectionInTypeCast(@NotNull PsiTypeCastExpression expression) { + PsiTypeElement castTypeElement = expression.getCastType(); + if (castTypeElement == null || !isIntersection(castTypeElement, castTypeElement.getType())) return; + myVisitor.checkFeature(expression, JavaFeature.INTERSECTION_CASTS); + if (myVisitor.hasErrorResults()) return; + + PsiTypeElement[] conjuncts = PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class); + if (conjuncts != null) { + Set erasures = new HashSet<>(conjuncts.length); + erasures.add(TypeConversionUtil.erasure(conjuncts[0].getType())); + List conjList = new ArrayList<>(Arrays.asList(conjuncts)); + for (int i = 1; i < conjuncts.length; i++) { + PsiTypeElement conjunct = conjuncts[i]; + PsiType conjType = conjunct.getType(); + if (conjType instanceof PsiClassType classType) { + PsiClass aClass = classType.resolve(); + if (aClass != null && !aClass.isInterface()) { + myVisitor.report(JavaErrorKinds.CAST_INTERSECTION_NOT_INTERFACE.create(conjunct)); + continue; + } + } + else { + myVisitor.report(JavaErrorKinds.CAST_INTERSECTION_UNEXPECTED_TYPE.create(conjunct)); + continue; + } + if (!erasures.add(TypeConversionUtil.erasure(conjType))) { + myVisitor.report(JavaErrorKinds.CAST_INTERSECTION_REPEATED_INTERFACE.create(conjunct)); + } + } + if (myVisitor.hasErrorResults()) return; + + List typeList = ContainerUtil.map(conjList, PsiTypeElement::getType); + Ref> differentArguments = new Ref<>(); + PsiClass sameGenericParameterization = + InferenceSession.findParameterizationOfTheSameGenericClass(typeList, pair -> { + if (!TypesDistinctProver.provablyDistinct(pair.first, pair.second)) { + return true; + } + differentArguments.set(pair); + return false; + }); + if (differentArguments.get() != null && sameGenericParameterization != null) { + var context = new JavaErrorKinds.InheritTypeClashContext( + sameGenericParameterization, differentArguments.get().getFirst(), differentArguments.get().getSecond()); + myVisitor.report(JavaErrorKinds.CAST_INTERSECTION_INHERITANCE_CLASH.create(expression, context)); + } + } + } + + private static boolean isIntersection(@NotNull PsiTypeElement castTypeElement, @NotNull PsiType castType) { + if (castType instanceof PsiIntersectionType) return true; + return castType instanceof PsiClassType && PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class) != null; + } + static boolean isArrayDeclaration(@NotNull PsiVariable variable) { // Java-style 'var' arrays are prohibited by the parser; for C-style ones, looking for a bracket is enough return ContainerUtil.or(variable.getChildren(), e -> PsiUtil.isJavaToken(e, JavaTokenType.LBRACKET)); @@ -1211,4 +1293,36 @@ final class ExpressionChecker { myVisitor.report(JavaErrorKinds.TYPE_RESTRICTED_IDENTIFIER.create(ref)); } } + + void checkInstanceOfApplicable(@NotNull PsiInstanceOfExpression expression) { + PsiExpression operand = expression.getOperand(); + PsiTypeElement typeElement = expression.getCheckType(); + if (typeElement == null) { + typeElement = JavaPsiPatternUtil.getPatternTypeElement(expression.getPattern()); + } + if (typeElement == null) return; + PsiType checkType = typeElement.getType(); + PsiType operandType = operand.getType(); + if (operandType == null) return; + boolean operandIsPrimitive = TypeConversionUtil.isPrimitiveAndNotNull(operandType); + boolean checkIsPrimitive = TypeConversionUtil.isPrimitiveAndNotNull(checkType); + boolean convertible = TypeConversionUtil.areTypesConvertible(operandType, checkType); + boolean primitiveInPatternsEnabled = PsiUtil.isAvailable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, expression); + if (((operandIsPrimitive || checkIsPrimitive) && !primitiveInPatternsEnabled) || !convertible) { + if (!convertible && IncompleteModelUtil.isIncompleteModel(expression) && + IncompleteModelUtil.isPotentiallyConvertible(checkType, operand)) { + return; + } + if (((operandIsPrimitive || checkIsPrimitive) && !primitiveInPatternsEnabled) && convertible) { + myVisitor.checkFeature(expression, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS); + if (myVisitor.hasErrorResults()) return; + } + myVisitor.report(JavaErrorKinds.CAST_INCONVERTIBLE.create( + expression, new JavaIncompatibleTypeErrorContext(operandType, checkType))); + } + PsiPrimaryPattern pattern = expression.getPattern(); + if (pattern instanceof PsiDeconstructionPattern deconstruction) { + myVisitor.myPatternChecker.checkDeconstructionErrors(deconstruction); + } + } } diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/GenericsChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/GenericsChecker.java index 1f5375ca2bf0..8a2c20d23660 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/GenericsChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/GenericsChecker.java @@ -9,6 +9,7 @@ import com.intellij.openapi.projectRoots.JavaVersionService; import com.intellij.openapi.roots.FileIndexFacade; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.java.JavaFeature; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.IncompleteModelUtil; @@ -484,6 +485,46 @@ final class GenericsChecker { } } + void checkInstanceOfGenericType(@NotNull PsiInstanceOfExpression expression) { + PsiTypeElement typeElement = expression.getCheckType(); + if (typeElement == null) { + typeElement = JavaPsiPatternUtil.getPatternTypeElement(expression.getPattern()); + } + if (typeElement == null) return; + PsiType checkType = typeElement.getType(); + if (myVisitor.isApplicable(JavaFeature.PATTERNS)) { + PsiPrimaryPattern pattern = expression.getPattern(); + if (pattern != null) { + myVisitor.myPatternChecker.checkUncheckedPatternConversion(pattern); + } else { + checkUnsafeCastInInstanceOf(typeElement, checkType, expression.getOperand().getType()); + } + } else { + checkIllegalForInstanceOf(checkType, typeElement); + } + } + + private void checkUnsafeCastInInstanceOf(@NotNull PsiTypeElement checkTypeElement, @NotNull PsiType checkType, @Nullable PsiType expressionType) { + if (expressionType != null && JavaGenericsUtil.isUncheckedCast(checkType, expressionType)) { + myVisitor.report(JavaErrorKinds.INSTANCEOF_UNSAFE_CAST.create( + checkTypeElement, new JavaIncompatibleTypeErrorContext(expressionType, checkType))); + } + } + + /** + * 15.20.2 Type Comparison Operator instanceof + * ReferenceType mentioned after the instanceof operator is reifiable + */ + private void checkIllegalForInstanceOf(@Nullable PsiType type, @NotNull PsiTypeElement typeElement) { + PsiClass resolved = PsiUtil.resolveClassInClassTypeOnly(type); + if (resolved instanceof PsiTypeParameter) { + myVisitor.report(JavaErrorKinds.INSTANCEOF_TYPE_PARAMETER.create(typeElement)); + } + else if (!JavaGenericsUtil.isReifiableType(type)) { + myVisitor.report(JavaErrorKinds.INSTANCEOF_ILLEGAL_GENERIC_TYPE.create(typeElement)); + } + } + private static PsiType detectExpectedType(@NotNull PsiReferenceParameterList referenceParameterList) { PsiNewExpression newExpression = requireNonNull(PsiTreeUtil.getParentOfType(referenceParameterList, PsiNewExpression.class)); PsiElement parent = newExpression.getParent(); diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java index 67f11957b740..24db9e5bc573 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/JavaErrorVisitor.java @@ -49,7 +49,7 @@ final class JavaErrorVisitor extends JavaElementVisitor { final @NotNull TypeChecker myTypeChecker = new TypeChecker(this); final @NotNull MethodChecker myMethodChecker = new MethodChecker(this); private final @NotNull ReceiverChecker myReceiverChecker = new ReceiverChecker(this); - private final @NotNull PatternChecker myPatternChecker = new PatternChecker(this); + final @NotNull PatternChecker myPatternChecker = new PatternChecker(this); final @NotNull ModifierChecker myModifierChecker = new ModifierChecker(this); final @NotNull ExpressionChecker myExpressionChecker = new ExpressionChecker(this); private final @NotNull StatementChecker myStatementChecker = new StatementChecker(this); @@ -684,6 +684,33 @@ final class JavaErrorVisitor extends JavaElementVisitor { myPatternChecker.checkDeconstructionPattern(deconstructionPattern); } + @Override + public void visitDeconstructionList(@NotNull PsiDeconstructionList deconstructionList) { + super.visitDeconstructionList(deconstructionList); + if (deconstructionList.getParent() instanceof PsiDeconstructionPattern pattern) { + myPatternChecker.checkMalformedDeconstructionPatternInCase(pattern); + } + } + + @Override + public void visitInstanceOfExpression(@NotNull PsiInstanceOfExpression expression) { + super.visitInstanceOfExpression(expression); + if (!hasErrorResults()) myExpressionChecker.checkInstanceOfApplicable(expression); + if (!hasErrorResults()) myGenericsChecker.checkInstanceOfGenericType(expression); + if (!hasErrorResults() && isApplicable(JavaFeature.PATTERNS) && + // 5.20.2 Removed restriction on pattern instanceof for unconditional patterns (JEP 432, 440) + !isApplicable(JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS)) { + myPatternChecker.checkInstanceOfPatternSupertype(expression); + } + } + + @Override + public void visitTypeCastExpression(@NotNull PsiTypeCastExpression expression) { + super.visitTypeCastExpression(expression); + if (!hasErrorResults()) myExpressionChecker.checkIntersectionInTypeCast(expression); + if (!hasErrorResults()) myExpressionChecker.checkInconvertibleTypeCast(expression); + } + @Override public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement ref) { JavaResolveResult result = ref instanceof PsiExpression ? resolveOptimised(ref, myFile) : doVisitReferenceElement(ref); diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/PatternChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/PatternChecker.java index bcb4a537820c..3bf6df49116b 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/PatternChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/PatternChecker.java @@ -85,7 +85,7 @@ final class PatternChecker { } } - private boolean checkUncheckedPatternConversion(@NotNull PsiPattern pattern) { + boolean checkUncheckedPatternConversion(@NotNull PsiPattern pattern) { PsiType patternType = JavaPsiPatternUtil.getPatternType(pattern); if (patternType == null) return false; if (pattern instanceof PsiDeconstructionPattern subPattern) { @@ -119,7 +119,7 @@ final class PatternChecker { checkDeconstructionErrors(pattern); } - private void checkDeconstructionErrors(@Nullable PsiDeconstructionPattern deconstructionPattern) { + void checkDeconstructionErrors(@Nullable PsiDeconstructionPattern deconstructionPattern) { if (deconstructionPattern == null) return; PsiTypeElement typeElement = deconstructionPattern.getTypeElement(); PsiType recordType = typeElement.getType(); @@ -186,6 +186,52 @@ final class PatternChecker { } } + void checkMalformedDeconstructionPatternInCase(@NotNull PsiDeconstructionPattern pattern) { + // We are checking the case when the pattern looks similar to method call in switch and want to show user-friendly message that here + // only constant expressions are expected. + // it is required to do it in deconstruction list because unresolved reference won't let any parents show any highlighting, + // so we need element which is not parent + PsiElement grandParent = pattern.getParent(); + if (!(grandParent instanceof PsiCaseLabelElementList)) return; + PsiTypeElement typeElement = pattern.getTypeElement(); + PsiJavaCodeReferenceElement ref = PsiTreeUtil.getChildOfType(typeElement, PsiJavaCodeReferenceElement.class); + if (ref == null) return; + if (ref.multiResolve(true).length == 0) { + PsiElementFactory elementFactory = myVisitor.factory(); + if (pattern.getPatternVariable() == null && pattern.getDeconstructionList().getDeconstructionComponents().length == 0) { + PsiClassType type = tryCast(pattern.getTypeElement().getType(), PsiClassType.class); + if (type != null && ContainerUtil.exists(type.getParameters(), PsiWildcardType.class::isInstance)) return; + PsiExpression expression = elementFactory.createExpressionFromText(pattern.getText(), grandParent); + PsiMethodCallExpression call = tryCast(expression, PsiMethodCallExpression.class); + if (call == null) return; + if (call.getMethodExpression().resolve() != null) { + myVisitor.report(JavaErrorKinds.CALL_PARSED_AS_DECONSTRUCTION_PATTERN.create(pattern)); + } + } + } + } + + void checkInstanceOfPatternSupertype(@NotNull PsiInstanceOfExpression expression) { + @Nullable PsiPattern expressionPattern = expression.getPattern(); + PsiTypeTestPattern pattern = tryCast(expressionPattern, PsiTypeTestPattern.class); + if (pattern == null) return; + PsiPatternVariable variable = pattern.getPatternVariable(); + if (variable == null) return; + PsiTypeElement typeElement = pattern.getCheckType(); + if (typeElement == null) return; + PsiType checkType = typeElement.getType(); + PsiType expressionType = expression.getOperand().getType(); + if (expressionType != null && checkType.isAssignableFrom(expressionType)) { + if (checkType.equals(expressionType)) { + myVisitor.report(JavaErrorKinds.PATTERN_INSTANCEOF_EQUALS.create(expression, checkType)); + } + else { + myVisitor.report(JavaErrorKinds.PATTERN_INSTANCEOF_SUPERTYPE.create( + expression, new JavaIncompatibleTypeErrorContext(checkType, expressionType))); + } + } + } + private static @NotNull RecordExhaustivenessResult checkRecordExhaustiveness(@NotNull List elements, @NotNull PsiType selectorType, @NotNull PsiElement context) { diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index d0c133e6c302..ce74272be09f 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -714,10 +714,33 @@ public final class JavaErrorKinds { }) .withRawDescription((list, ctx) -> message("pattern.deconstruction.count.mismatch", ctx.recordComponents().length, ctx.patternComponents().length)); + public static final Parameterized PATTERN_INSTANCEOF_SUPERTYPE = + parameterized(PsiInstanceOfExpression.class, JavaIncompatibleTypeErrorContext.class, "pattern.instanceof.supertype") + .withAnchor((expr, context) -> expr.getCheckType()) + .withRawDescription((expr, context) -> message( + "pattern.instanceof.supertype", context.lType().getPresentableText(), requireNonNull(context.rType()).getPresentableText())); + public static final Parameterized PATTERN_INSTANCEOF_EQUALS = + parameterized(PsiInstanceOfExpression.class, PsiType.class, "pattern.instanceof.equals") + .withAnchor((expr, context) -> expr.getCheckType()) + .withRawDescription((expr, context) -> message("pattern.instanceof.equals", context.getPresentableText())); + + public static final Simple INSTANCEOF_TYPE_PARAMETER = error("instanceof.type.parameter"); + public static final Simple INSTANCEOF_ILLEGAL_GENERIC_TYPE = error("instanceof.illegal.generic.type"); + public static final Parameterized INSTANCEOF_UNSAFE_CAST = + parameterized(PsiTypeElement.class, JavaIncompatibleTypeErrorContext.class, "instanceof.unsafe.cast") + .withRawDescription((expr, context) -> message( + "instanceof.unsafe.cast", context.lType().getPresentableText(), requireNonNull(context.rType()).getPresentableText())); public static final Parameterized CAST_INCONVERTIBLE = parameterized(PsiElement.class, JavaIncompatibleTypeErrorContext.class, "cast.inconvertible") .withRawDescription((psi, ctx) -> message("cast.inconvertible", formatType(ctx.lType()), formatType(ctx.rType()))); + public static final Simple CAST_INTERSECTION_NOT_INTERFACE = error("cast.intersection.not.interface"); + public static final Simple CAST_INTERSECTION_UNEXPECTED_TYPE = error("cast.intersection.unexpected.type"); + public static final Simple CAST_INTERSECTION_REPEATED_INTERFACE = error("cast.intersection.repeated.interface"); + public static final Parameterized CAST_INTERSECTION_INHERITANCE_CLASH = + parameterized(PsiTypeCastExpression.class, InheritTypeClashContext.class, "cast.intersection.inheritance.clash") + .withRawDescription((cast, ctx) -> message("cast.intersection.inheritance.clash", formatClass(ctx.superClass()), + ctx.type1().getPresentableText(), ctx.type2().getPresentableText())); public static final Simple EXPRESSION_EXPECTED = error("expression.expected"); public static final Parameterized EXPRESSION_SUPER_UNQUALIFIED_DEFAULT_METHOD = @@ -912,6 +935,8 @@ public final class JavaErrorKinds { parameterized(PsiExpression.class, PsiClass.class, "call.super.qualifier.not.inner.class") .withRawDescription((psi, cls) -> message("call.super.qualifier.not.inner.class", formatClass(cls))); public static final Simple CALL_EXPECTED = error("call.expected"); + public static final Simple CALL_PARSED_AS_DECONSTRUCTION_PATTERN = + error("call.parsed.as.deconstruction.pattern"); public static final Simple CALL_STATIC_INTERFACE_METHOD_QUALIFIER = error(PsiJavaCodeReferenceElement.class, "call.static.interface.method.qualifier") .withRange(JavaErrorFormatUtil::getRange); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java index 6ee6d729725a..b5a3932e2a38 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java @@ -26,7 +26,6 @@ import com.intellij.psi.util.*; import com.intellij.util.ArrayUtilRt; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; -import com.siyeh.ig.psiutils.InstanceOfUtils; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -554,48 +553,6 @@ public final class GenericsHighlightUtil { return enumClass != null && enumClass.isEnum() ? enumClass : null; } - static HighlightInfo.Builder checkInstanceOfGenericType(@NotNull LanguageLevel languageLevel, @NotNull PsiInstanceOfExpression expression) { - PsiTypeElement checkTypeElement = InstanceOfUtils.findCheckTypeElement(expression); - if (checkTypeElement == null) return null; - PsiType checkType = checkTypeElement.getType(); - if (JavaFeature.PATTERNS.isSufficient(languageLevel)) { - PsiPrimaryPattern pattern = expression.getPattern(); - if (pattern != null) { - return PatternHighlightingModel.getUncheckedPatternConversionError(pattern); - } - return isUnsafeCastInInstanceOf(checkTypeElement, checkType, expression.getOperand().getType()); - } - return isIllegalForInstanceOf(checkType, checkTypeElement); - } - - private static HighlightInfo.Builder isUnsafeCastInInstanceOf(@NotNull PsiTypeElement checkTypeElement, @NotNull PsiType checkType, @Nullable PsiType expressionType) { - if (expressionType != null && JavaGenericsUtil.isUncheckedCast(checkType, expressionType)) { - String description = JavaErrorBundle.message("unsafe.cast.in.instanceof", - expressionType.getPresentableText(), checkType.getPresentableText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(checkTypeElement).descriptionAndTooltip(description); - } - return null; - } - - /** - * 15.20.2 Type Comparison Operator instanceof - * ReferenceType mentioned after the instanceof operator is reifiable - */ - private static HighlightInfo.Builder isIllegalForInstanceOf(@Nullable PsiType type, @NotNull PsiTypeElement typeElement) { - PsiClass resolved = PsiUtil.resolveClassInClassTypeOnly(type); - if (resolved instanceof PsiTypeParameter) { - String description = JavaErrorBundle.message("generics.cannot.instanceof.type.parameters"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description); - } - - if (!JavaGenericsUtil.isReifiableType(type)) { - String description = JavaErrorBundle.message("illegal.generic.type.for.instanceof"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description); - } - - return null; - } - static HighlightInfo.Builder checkClassObjectAccessExpression(@NotNull PsiClassObjectAccessExpression expression) { PsiType type = expression.getOperand().getType(); if (type instanceof PsiClassType classType) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 336b4019940c..9b5af622dae9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -4,18 +4,17 @@ package com.intellij.codeInsight.daemon.impl.analysis; import com.intellij.codeInsight.ContainerProvider; import com.intellij.codeInsight.JavaModuleSystemEx; import com.intellij.codeInsight.JavaModuleSystemEx.ErrorWithFixes; -import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.JavaErrorBundle; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; -import com.intellij.codeInsight.daemon.impl.quickfix.*; +import com.intellij.codeInsight.daemon.impl.quickfix.AddTypeArgumentsConditionalFix; +import com.intellij.codeInsight.daemon.impl.quickfix.ChangeNewOperatorTypeFix; +import com.intellij.codeInsight.daemon.impl.quickfix.QualifyWithThisFix; import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation; import com.intellij.codeInsight.intention.CommonIntentionAction; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; -import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix; -import com.intellij.ide.IdeBundle; import com.intellij.modcommand.ModCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.LanguageLevelUtil; @@ -34,7 +33,6 @@ import com.intellij.pom.java.JavaFeature; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.IncompleteModelUtil; -import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession; import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil; import com.intellij.psi.scope.PatternResolveState; import com.intellij.psi.scope.processor.VariablesNotProcessor; @@ -51,16 +49,15 @@ import com.intellij.util.ui.JBUI; import com.intellij.util.ui.NamedColorUtil; import com.intellij.util.ui.UIUtil; import com.siyeh.ig.psiutils.ControlFlowUtils; -import com.siyeh.ig.psiutils.InstanceOfUtils; -import com.siyeh.ig.psiutils.VariableAccessUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; -import java.util.*; +import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -79,166 +76,11 @@ public final class HighlightUtil { } - static void checkInstanceOfApplicable(@NotNull PsiInstanceOfExpression expression, @NotNull Consumer errorSink) { - PsiExpression operand = expression.getOperand(); - PsiTypeElement typeElement = InstanceOfUtils.findCheckTypeElement(expression); - if (typeElement == null) return; - PsiType checkType = typeElement.getType(); - PsiType operandType = operand.getType(); - if (operandType == null) return; - boolean operandIsPrimitive = TypeConversionUtil.isPrimitiveAndNotNull(operandType); - boolean checkIsPrimitive = TypeConversionUtil.isPrimitiveAndNotNull(checkType); - boolean convertible = TypeConversionUtil.areTypesConvertible(operandType, checkType); - boolean primitiveInPatternsEnabled = PsiUtil.isAvailable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, expression); - if (((operandIsPrimitive || checkIsPrimitive) && !primitiveInPatternsEnabled) || !convertible) { - if (!convertible && IncompleteModelUtil.isIncompleteModel(expression) && - IncompleteModelUtil.isPotentiallyConvertible(checkType, operand)) { - return; - } - String message = JavaErrorBundle.message("inconvertible.type.cast", JavaHighlightUtil.formatType(operandType), JavaHighlightUtil - .formatType(checkType)); - HighlightInfo.Builder info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message); - if (((operandIsPrimitive || checkIsPrimitive) && !primitiveInPatternsEnabled) && convertible) { - HighlightInfo.Builder infoFeature = - checkFeature(expression, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, - PsiUtil.getLanguageLevel(expression), expression.getContainingFile()); - if (infoFeature != null) { - info = infoFeature; - } - } - if (checkIsPrimitive) { - IntentionAction action = getFixFactory().createReplacePrimitiveWithBoxedTypeAction(operandType, typeElement); - if (action != null) { - info.registerFix(action, null, null, null, null); - } - } - - errorSink.accept(info); - return; - } - PsiPrimaryPattern pattern = expression.getPattern(); - if (pattern instanceof PsiDeconstructionPattern deconstruction) { - PatternHighlightingModel.createDeconstructionErrors(deconstruction, errorSink); - } - } - - - /** - * 15.16 Cast Expressions - * ( ReferenceType {AdditionalBound} ) expression, where AdditionalBound: & InterfaceType then all must be true - * - ReferenceType must denote a class or interface type. - * - The erasures of all the listed types must be pairwise different. - * - No two listed types may be subtypes of different parameterization of the same generic interface. - */ - static HighlightInfo.Builder checkIntersectionInTypeCast(@NotNull PsiTypeCastExpression expression, - @NotNull LanguageLevel languageLevel, - @NotNull PsiFile file) { - PsiTypeElement castTypeElement = expression.getCastType(); - if (castTypeElement == null || !isIntersection(castTypeElement, castTypeElement.getType())) { - return null; - } - HighlightInfo.Builder info = checkFeature(expression, JavaFeature.INTERSECTION_CASTS, languageLevel, file); - if (info != null) return info; - - PsiTypeElement[] conjuncts = PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class); - if (conjuncts != null) { - Set erasures = new HashSet<>(conjuncts.length); - erasures.add(TypeConversionUtil.erasure(conjuncts[0].getType())); - List conjList = new ArrayList<>(Arrays.asList(conjuncts)); - for (int i = 1; i < conjuncts.length; i++) { - PsiTypeElement conjunct = conjuncts[i]; - PsiType conjType = conjunct.getType(); - if (conjType instanceof PsiClassType classType) { - PsiClass aClass = classType.resolve(); - if (aClass != null && !aClass.isInterface()) { - HighlightInfo.Builder errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(conjunct) - .descriptionAndTooltip(JavaErrorBundle.message("interface.expected")); - var action = new FlipIntersectionSidesFix(aClass.getName(), conjunct, castTypeElement); - errorResult.registerFix(action, null, HighlightDisplayKey.getDisplayNameByKey(null), null, null); - return errorResult; - } - } - else { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(conjunct) - .descriptionAndTooltip(JavaErrorBundle.message("unexpected.type.class.expected")); - } - if (!erasures.add(TypeConversionUtil.erasure(conjType))) { - HighlightInfo.Builder highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(conjunct) - .descriptionAndTooltip(JavaErrorBundle.message("repeated.interface")); - var action = new DeleteRepeatedInterfaceFix(conjunct); - highlightInfo.registerFix(action, null, HighlightDisplayKey.getDisplayNameByKey(null), null, null); - return highlightInfo; - } - } - - List typeList = ContainerUtil.map(conjList, PsiTypeElement::getType); - Ref<@Nls String> differentArgumentsMessage = new Ref<>(); - PsiClass sameGenericParameterization = - InferenceSession.findParameterizationOfTheSameGenericClass(typeList, pair -> { - if (!TypesDistinctProver.provablyDistinct(pair.first, pair.second)) { - return true; - } - differentArgumentsMessage.set(IdeBundle.message("x.and.y", pair.first.getPresentableText(), - pair.second.getPresentableText())); - return false; - }); - if (sameGenericParameterization != null) { - String message = JavaErrorBundle - .message("class.cannot.be.inherited.with.different.arguments", formatClass(sameGenericParameterization), - differentArgumentsMessage.get()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(expression) - .descriptionAndTooltip(message); - } - } - - return null; - } - - private static boolean isIntersection(@NotNull PsiTypeElement castTypeElement, @NotNull PsiType castType) { - if (castType instanceof PsiIntersectionType) return true; - return castType instanceof PsiClassType && PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class) != null; - } - - static HighlightInfo.Builder checkInconvertibleTypeCast(@NotNull PsiTypeCastExpression expression) { - PsiTypeElement castTypeElement = expression.getCastType(); - if (castTypeElement == null) return null; - PsiType castType = castTypeElement.getType(); - - PsiExpression operand = expression.getOperand(); - if (operand == null) return null; - PsiType operandType = operand.getType(); - - if (operandType != null && - !TypeConversionUtil.areTypesConvertible(operandType, castType, PsiUtil.getLanguageLevel(expression)) && - !RedundantCastUtil.isInPolymorphicCall(expression)) { - if (IncompleteModelUtil.isIncompleteModel(expression) && IncompleteModelUtil.isPotentiallyConvertible(castType, operand)) { - return null; - } - String message = JavaErrorBundle.message("inconvertible.type.cast", JavaHighlightUtil.formatType(operandType), JavaHighlightUtil - .formatType(castType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message); - } - return null; - } - static HighlightInfo.Builder checkAssignability(@Nullable PsiType lType, @Nullable PsiType rType, @Nullable PsiExpression expression, @NotNull PsiElement elementToHighlight) { TextRange textRange = elementToHighlight.getTextRange(); - return checkAssignability(lType, rType, expression, textRange, 0); - } - - private static HighlightInfo.Builder checkAssignability(@Nullable PsiType lType, - @Nullable PsiType rType, - @Nullable PsiExpression expression, - @NotNull TextRange textRange, - int navigationShift) { if (lType == rType) return null; if (expression == null) { if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) return null; @@ -256,7 +98,7 @@ public final class HighlightUtil { IncompleteModelUtil.isPotentiallyConvertible(lType, expression)) { return null; } - HighlightInfo.Builder highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange, navigationShift); + HighlightInfo.Builder highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange, 0); AddTypeArgumentsConditionalFix.register(asConsumer(highlightInfo), expression, lType); if (expression != null) { AdaptExpressionTypeFixUtil.registerExpectedTypeFixes(asConsumer(highlightInfo), expression, lType, rType); @@ -495,32 +337,6 @@ public final class HighlightUtil { } } - static HighlightInfo.Builder checkInstanceOfPatternSupertype(@NotNull PsiInstanceOfExpression expression) { - @Nullable PsiPattern expressionPattern = expression.getPattern(); - PsiTypeTestPattern pattern = tryCast(expressionPattern, PsiTypeTestPattern.class); - if (pattern == null) return null; - PsiPatternVariable variable = pattern.getPatternVariable(); - if (variable == null) return null; - PsiTypeElement typeElement = pattern.getCheckType(); - if (typeElement == null) return null; - PsiType checkType = typeElement.getType(); - PsiType expressionType = expression.getOperand().getType(); - if (expressionType != null && checkType.isAssignableFrom(expressionType)) { - String description = - checkType.equals(expressionType) ? - JavaErrorBundle.message("instanceof.pattern.equals", checkType.getPresentableText()) : - JavaErrorBundle.message("instanceof.pattern.supertype", checkType.getPresentableText(), expressionType.getPresentableText()); - HighlightInfo.Builder info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description); - if (!VariableAccessUtils.variableIsUsed(variable, variable.getDeclarationScope())) { - var action = new RedundantInstanceofFix(expression); - info.registerFix(action, null, null, null, null); - } - return info; - } - return null; - } - static @NotNull @NlsContexts.DetailedDescription String staticContextProblemDescription(@NotNull PsiElement refElement) { String type = JavaElementKind.fromElement(refElement).lessDescriptive().subject(); String name = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index ab1bb2cfb5ba..3ebaf0736e9c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -47,7 +47,6 @@ import com.intellij.psi.util.*; import com.intellij.ui.ColorUtil; import com.intellij.ui.NewUI; import com.intellij.util.ObjectUtils; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.NamedColorUtil; import com.intellij.util.ui.UIUtil; @@ -499,19 +498,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } } - @Override - public void visitInstanceOfExpression(@NotNull PsiInstanceOfExpression expression) { - super.visitInstanceOfExpression(expression); - if (!hasErrorResults()) HighlightUtil.checkInstanceOfApplicable(expression, myErrorSink); - if (!hasErrorResults()) add(GenericsHighlightUtil.checkInstanceOfGenericType(myLanguageLevel, expression)); - if (!hasErrorResults() && - JavaFeature.PATTERNS.isSufficient(myLanguageLevel) && - // 5.20.2 Removed restriction on pattern instanceof for unconditional patterns (JEP 432, 440) - !JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS.isSufficient(myLanguageLevel)) { - add(HighlightUtil.checkInstanceOfPatternSupertype(expression)); - } - } - @Override public void visitImportModuleStatement(@NotNull PsiImportModuleStatement statement) { super.visitImportModuleStatement(statement); @@ -1024,17 +1010,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } } - @Override - public void visitTypeCastExpression(@NotNull PsiTypeCastExpression typeCast) { - super.visitTypeCastExpression(typeCast); - try { - if (!hasErrorResults()) add(HighlightUtil.checkIntersectionInTypeCast(typeCast, myLanguageLevel, myFile)); - if (!hasErrorResults()) add(HighlightUtil.checkInconvertibleTypeCast(typeCast)); - } - catch (IndexNotReadyException ignored) { - } - } - @Override public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) { super.visitConditionalExpression(expression); @@ -1132,37 +1107,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } } - @Override - public void visitDeconstructionList(@NotNull PsiDeconstructionList deconstructionList) { - super.visitDeconstructionList(deconstructionList); - // We are checking the case when the pattern looks similar to method call in switch and want to show user-friendly message that here - // only constant expressions are expected. - // it is required to do it in deconstruction list because unresolved reference won't let any parents show any highlighting, - // so we need element which is not parent - PsiElement parent = deconstructionList.getParent(); - PsiDeconstructionPattern pattern = tryCast(parent, PsiDeconstructionPattern.class); - if (pattern == null) return; - PsiElement grandParent = parent.getParent(); - if (!(grandParent instanceof PsiCaseLabelElementList)) return; - PsiTypeElement typeElement = pattern.getTypeElement(); - PsiJavaCodeReferenceElement ref = PsiTreeUtil.getChildOfType(typeElement, PsiJavaCodeReferenceElement.class); - if (ref == null) return; - if (ref.multiResolve(true).length == 0) { - PsiElementFactory elementFactory = PsiElementFactory.getInstance(myFile.getProject()); - if (pattern.getPatternVariable() == null && pattern.getDeconstructionList().getDeconstructionComponents().length == 0) { - PsiClassType type = tryCast(pattern.getTypeElement().getType(), PsiClassType.class); - if (type != null && ContainerUtil.exists(type.getParameters(), PsiWildcardType.class::isInstance)) return; - PsiExpression expression = elementFactory.createExpressionFromText(pattern.getText(), grandParent); - PsiMethodCallExpression call = tryCast(expression, PsiMethodCallExpression.class); - if (call == null) return; - if (call.getMethodExpression().resolve() != null) { - add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(pattern.getTextRange()) - .descriptionAndTooltip(JavaErrorBundle.message("switch.constant.expression.required"))); - } - } - } - } - private @Nullable HighlightInfo.Builder checkFeature(@NotNull PsiElement element, @NotNull JavaFeature feature) { return HighlightUtil.checkFeature(element, feature, myLanguageLevel, myFile); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java index b9f3dcbc02eb..b4c87160c09f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java @@ -9,6 +9,7 @@ import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.codeInsight.intention.impl.BaseIntentionAction; import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper; +import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.codeserver.highlighting.JavaErrorCollector; @@ -34,7 +35,9 @@ import com.intellij.psi.util.*; import com.intellij.refactoring.util.RefactoringChangeUtil; import com.intellij.util.VisibilityUtil; import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.InstanceOfUtils; import com.siyeh.ig.psiutils.TypeUtils; +import com.siyeh.ig.psiutils.VariableAccessUtils; import com.siyeh.ig.psiutils.VariableNameGenerator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -321,6 +324,25 @@ final class JavaErrorFixProvider { sink.accept(QuickFixFactory.getInstance().createDeleteFix(elementsToDelete, text)); } }); + fix(CAST_INCONVERTIBLE, error -> { + if (error.psi() instanceof PsiInstanceOfExpression instanceOfExpression && + TypeConversionUtil.isPrimitiveAndNotNull(error.context().rType())) { + return myFactory.createReplacePrimitiveWithBoxedTypeAction( + error.context().lType(), requireNonNull(InstanceOfUtils.findCheckTypeElement(instanceOfExpression))); + } + return null; + }); + JavaFixProvider redundantInstanceOfFix = error -> { + if (error.psi().getPattern() instanceof PsiTypeTestPattern pattern) { + PsiPatternVariable variable = pattern.getPatternVariable(); + if (variable != null && !VariableAccessUtils.variableIsUsed(variable, variable.getDeclarationScope())) { + return new RedundantInstanceofFix(error.psi()); + } + } + return null; + }; + fix(PATTERN_INSTANCEOF_EQUALS, redundantInstanceOfFix); + fix(PATTERN_INSTANCEOF_SUPERTYPE, redundantInstanceOfFix); } private void createVariableFixes() { @@ -480,6 +502,12 @@ final class JavaErrorFixProvider { HighlightUtil.isCallToStaticMember(newExpression) ? new RemoveNewKeywordFix(newExpression) : null); fix(REFERENCE_QUALIFIER_PRIMITIVE, error -> error.psi() instanceof PsiReferenceExpression ref ? myFactory.createRenameWrongRefFix(ref) : null); + fix(CAST_INTERSECTION_NOT_INTERFACE, error -> { + PsiTypeElement conjunct = error.psi(); + return new FlipIntersectionSidesFix(((PsiClassType)conjunct.getType()).getClassName(), conjunct, + PsiTreeUtil.getParentOfType(conjunct, PsiTypeElement.class, true)); + }); + fix(CAST_INTERSECTION_REPEATED_INTERFACE, error -> new DeleteRepeatedInterfaceFix(error.psi())); } private void createAccessFixes() { diff --git a/java/java-analysis-impl/src/com/intellij/psi/util/RedundantCastUtil.java b/java/java-analysis-impl/src/com/intellij/psi/util/RedundantCastUtil.java index f9a9d45ac16a..5b2e76d1a8d8 100644 --- a/java/java-analysis-impl/src/com/intellij/psi/util/RedundantCastUtil.java +++ b/java/java-analysis-impl/src/com/intellij/psi/util/RedundantCastUtil.java @@ -1,7 +1,6 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.psi.util; -import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.ExceptionUtil; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; @@ -1053,7 +1052,7 @@ public final class RedundantCastUtil { PsiExpression operand = typeCast.getOperand(); if (operand == null) return false; - if (isInPolymorphicCall(typeCast)) return true; + if (PsiUtil.isInSignaturePolymorphicCall(typeCast)) return true; PsiType opType = operand.getType(); PsiTypeElement typeElement = typeCast.getCastType(); @@ -1136,28 +1135,6 @@ public final class RedundantCastUtil { } } - // see http://download.java.net/jdk7/docs/api/java/lang/invoke/MethodHandle.html#sigpoly - public static boolean isInPolymorphicCall(PsiTypeCastExpression typeCast) { - if (!PsiUtil.isLanguageLevel7OrHigher(typeCast)) return false; - - // return type - final PsiExpression operand = typeCast.getOperand(); - if (operand instanceof PsiMethodCallExpression methodCallExpression && isPolymorphicMethod(methodCallExpression)) { - return true; - } - - // argument type - final PsiElement exprList = PsiUtil.skipParenthesizedExprUp(typeCast.getParent()); - return exprList instanceof PsiExpressionList && - exprList.getParent() instanceof PsiMethodCallExpression methodCallExpression && - isPolymorphicMethod(methodCallExpression); - } - - private static boolean isPolymorphicMethod(PsiMethodCallExpression expression) { - return expression.getMethodExpression().resolve() instanceof PsiMethod method && - AnnotationUtil.isAnnotated(method, CommonClassNames.JAVA_LANG_INVOKE_MH_POLYMORPHIC, 0); - } - private static boolean areNullabilityCompatible(PsiMethod oldTargetMethod, PsiMethod newTargetMethod) { // the cast may be for the @NotNull which newTargetMethod has whereas the oldTargetMethod doesn't return NullableNotNullManager.getNullability(oldTargetMethod) == NullableNotNullManager.getNullability(newTargetMethod); diff --git a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java index deca8f9878f5..e29f4389f751 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java @@ -1,6 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.psi.util; +import com.intellij.codeInsight.AnnotationUtil; import com.intellij.core.JavaPsiBundle; import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.diagnostic.Logger; @@ -416,6 +417,34 @@ public final class PsiUtil extends PsiUtilCore { } } + /** + * @param typeCast cast to check + * @return true if the supplied cast is necessary to fulfill the signature polymorphic method contract + * @see Signature polymorphism in Java API reference + */ + // see http://download.java.net/jdk7/docs/api/java/lang/invoke/MethodHandle.html#sigpoly + public static boolean isInSignaturePolymorphicCall(@NotNull PsiTypeCastExpression typeCast) { + if (!isLanguageLevel7OrHigher(typeCast)) return false; + + // return type + final PsiExpression operand = typeCast.getOperand(); + if (operand instanceof PsiMethodCallExpression && isPolymorphicMethod((PsiMethodCallExpression)operand)) { + return true; + } + + // argument type + final PsiElement exprList = skipParenthesizedExprUp(typeCast.getParent()); + return exprList instanceof PsiExpressionList && + exprList.getParent() instanceof PsiMethodCallExpression && + isPolymorphicMethod((PsiMethodCallExpression)exprList.getParent()); + } + + private static boolean isPolymorphicMethod(PsiMethodCallExpression expression) { + PsiElement target = expression.getMethodExpression().resolve(); + return target instanceof PsiMethod && + AnnotationUtil.isAnnotated((PsiMethod)target, CommonClassNames.JAVA_LANG_INVOKE_MH_POLYMORPHIC, 0); + } + @MagicConstant(intValues = {ACCESS_LEVEL_PUBLIC, ACCESS_LEVEL_PROTECTED, ACCESS_LEVEL_PACKAGE_LOCAL, ACCESS_LEVEL_PRIVATE}) public @interface AccessLevel {}