diff --git a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties index 5b9d2ee3997f..f8c5923b2284 100644 --- a/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties +++ b/java/codeserver/highlighting/resources/messages/JavaCompilationErrorBundle.properties @@ -130,6 +130,14 @@ type.parameter.on.annotation.member=@interface members may not have type paramet type.parameter.on.annotation=@interface may not have type parameters type.parameter.duplicate=Duplicate type parameter: ''{0}'' type.parameter.incompatible.upper.bounds=Type parameter {0} has incompatible upper bounds: {1} +type.parameter.inferred.type.not.within.extend.bound=Inferred type ''{2}'' for type parameter ''{0}'' is not within its bound; should extend ''{1}'' +type.parameter.inferred.type.not.within.implement.bound=Inferred type ''{2}'' for type parameter ''{0}'' is not within its bound; should implement ''{1}'' +type.parameter.type.not.within.extend.bound=Type parameter ''{0}'' is not within its bound; should extend ''{1}'' +type.parameter.type.not.within.implement.bound=Type parameter ''{0}'' is not within its bound; should implement ''{1}'' +type.parameter.absent.class=Type ''{0}'' does not have type parameters +type.parameter.absent.method=Method ''{0}'' does not have type parameters +type.parameter.count.mismatch=Wrong number of type arguments: {0}; required: {1} +type.parameter.actual.inferred.mismatch=Actual type argument and inferred type contradict each other method.duplicate=''{0}'' is already defined in ''{1}'' method.throws.class.name.expected=Class name expected @@ -176,6 +184,9 @@ new.expression.qualified.static.class=Qualified new of static class new.expression.qualified.anonymous.implements.interface=Anonymous class implements interface; cannot have qualifier for new new.expression.qualified.qualified.class.reference=Qualified class reference is not allowed in qualified new new.expression.diamond.not.allowed=Diamond operator is not allowed here +new.expression.diamond.not.applicable=Diamond operator is not applicable for non-parameterized types +new.expression.diamond.inference.failure={0} +new.expression.diamond.anonymous.inner.non.private=Cannot use '<>' due to non-private method which doesn't override or implement a method from a supertype reference.type.argument.static.class=Type arguments are not allowed here because class ''{0}'' is static reference.type.needs.type.arguments=Improperly formed type: ''{0}'' needs type arguments because its qualifier has type arguments 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 f4bf9cbfece1..9c8fa7382812 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.JavaErrorKinds; import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext; import com.intellij.java.codeserver.highlighting.errors.JavaMismatchedCallContext; +import com.intellij.openapi.util.Pair; import com.intellij.pom.java.JavaFeature; import com.intellij.psi.*; import com.intellij.psi.impl.IncompleteModelUtil; @@ -18,10 +19,7 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static com.intellij.util.ObjectUtils.tryCast; @@ -344,16 +342,48 @@ final class ExpressionChecker { MethodCandidateInfo candidateInfo = resolveResult instanceof MethodCandidateInfo ? (MethodCandidateInfo)resolveResult : null; PsiMethod resolvedMethod = candidateInfo != null ? candidateInfo.getElement() : null; - if (!resolveResult.isAccessible() || !resolveResult.isStaticsScopeCorrect()) { - } - else if (candidateInfo != null && !candidateInfo.isApplicable()) { - if (candidateInfo.isTypeArgumentsApplicable()) { - checkIncompatibleCall(list, candidateInfo); + if (resolveResult.isAccessible() && resolveResult.isStaticsScopeCorrect()) { + if (candidateInfo != null && !candidateInfo.isApplicable()) { + if (candidateInfo.isTypeArgumentsApplicable()) { + checkIncompatibleCall(list, candidateInfo); + } + else { + PsiReferenceParameterList typeArgumentList = methodCall.getTypeArgumentList(); + PsiSubstitutor applicabilitySubstitutor = candidateInfo.getSubstitutor(false); + if (typeArgumentList.getTypeArguments().length == 0 && resolvedMethod.hasTypeParameters()) { + checkInferredTypeArguments(resolvedMethod, methodCall, applicabilitySubstitutor); + } + else { + myVisitor.myGenericsChecker.checkParameterizedReferenceTypeArguments(resolved, referenceToMethod, applicabilitySubstitutor); + } + } + } + else { + myVisitor.report(JavaErrorKinds.CALL_EXPECTED.create(methodCall)); } } - else { - myVisitor.report(JavaErrorKinds.CALL_EXPECTED.create(methodCall)); - } + } + if (!myVisitor.hasErrorResults()) { + myVisitor.myGenericsChecker.checkParameterizedReferenceTypeArguments(resolved, referenceToMethod, substitutor); + } + } + + void checkInferredTypeArguments(@NotNull PsiTypeParameterListOwner listOwner, + @NotNull PsiMethodCallExpression call, + @NotNull PsiSubstitutor substitutor) { + PsiTypeParameter[] typeParameters = listOwner.getTypeParameters(); + Pair inferredTypeArgument = GenericsUtil.findTypeParameterWithBoundError( + typeParameters, substitutor, call, false); + if (inferredTypeArgument != null) { + PsiType extendsType = inferredTypeArgument.second; + PsiTypeParameter typeParameter = inferredTypeArgument.first; + PsiClass boundClass = extendsType instanceof PsiClassType classType ? classType.resolve() : null; + + var kind = boundClass == null || typeParameter.isInterface() == boundClass.isInterface() + ? JavaErrorKinds.TYPE_PARAMETER_INFERRED_TYPE_NOT_WITHIN_EXTEND_BOUND + : JavaErrorKinds.TYPE_PARAMETER_INFERRED_TYPE_NOT_WITHIN_IMPLEMENT_BOUND; + myVisitor.report(kind.create(call, new JavaErrorKinds.TypeParameterBoundMismatchContext( + typeParameter, extendsType, Objects.requireNonNull(substitutor.substitute(typeParameter))))); } } 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 ab762f62386c..13e03cfe1526 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 @@ -4,6 +4,7 @@ package com.intellij.java.codeserver.highlighting; import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds; import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext; +import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.openapi.roots.FileIndexFacade; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vfs.VirtualFile; @@ -12,6 +13,8 @@ import com.intellij.psi.impl.IncompleteModelUtil; import com.intellij.psi.impl.PsiClassImplUtil; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.*; +import com.intellij.util.ArrayUtilRt; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -250,4 +253,167 @@ final class GenericsChecker { } } } + + void checkParameterizedReferenceTypeArguments(@Nullable PsiElement resolved, + @NotNull PsiJavaCodeReferenceElement referenceElement, + @NotNull PsiSubstitutor substitutor) { + if (!(resolved instanceof PsiTypeParameterListOwner typeParameterListOwner)) return; + checkReferenceTypeArgumentList(typeParameterListOwner, referenceElement.getParameterList(), substitutor); + } + + void checkReferenceTypeArgumentList(@NotNull PsiTypeParameterListOwner typeParameterListOwner, + @Nullable PsiReferenceParameterList referenceParameterList, + @NotNull PsiSubstitutor substitutor) { + PsiDiamondType.DiamondInferenceResult inferenceResult = null; + PsiTypeElement[] referenceElements = null; + if (referenceParameterList != null) { + referenceElements = referenceParameterList.getTypeParameterElements(); + if (referenceElements.length == 1 && referenceElements[0].getType() instanceof PsiDiamondType diamondType) { + if (!typeParameterListOwner.hasTypeParameters()) { + myVisitor.report(JavaErrorKinds.NEW_EXPRESSION_DIAMOND_NOT_APPLICABLE.create(referenceParameterList)); + return; + } + inferenceResult = diamondType.resolveInferredTypes(); + String errorMessage = inferenceResult.getErrorMessage(); + if (errorMessage != null) { + PsiType expectedType = detectExpectedType(referenceParameterList); + if (!(inferenceResult.failedToInfer() && expectedType instanceof PsiClassType classType && classType.isRaw())) { + if (inferenceResult == PsiDiamondType.DiamondInferenceResult.ANONYMOUS_INNER_RESULT || + inferenceResult == PsiDiamondType.DiamondInferenceResult.EXPLICIT_CONSTRUCTOR_TYPE_ARGS) { + myVisitor.report(JavaErrorKinds.NEW_EXPRESSION_DIAMOND_INFERENCE_FAILURE.create(referenceParameterList, inferenceResult)); + return; + } + } + } + + PsiElement parent = referenceParameterList.getParent().getParent(); + if (parent instanceof PsiAnonymousClass anonymousClass && + ContainerUtil.exists(anonymousClass.getMethods(), + method -> !method.hasModifierProperty(PsiModifier.PRIVATE) && method.findSuperMethods().length == 0)) { + myVisitor.report(JavaErrorKinds.NEW_EXPRESSION_DIAMOND_ANONYMOUS_INNER_NON_PRIVATE.create(referenceParameterList)); + return; + } + } + } + + PsiTypeParameter[] typeParameters = typeParameterListOwner.getTypeParameters(); + int targetParametersNum = typeParameters.length; + int refParametersNum = referenceParameterList == null ? 0 : referenceParameterList.getTypeArguments().length; + if (targetParametersNum != refParametersNum && refParametersNum != 0) { + if (targetParametersNum == 0) { + boolean shouldSuppress = PsiTreeUtil.getParentOfType(referenceParameterList, PsiCall.class) != null && + typeParameterListOwner instanceof PsiMethod psiMethod && + (myVisitor.sdkVersion().isAtLeast(JavaSdkVersion.JDK_1_7) || hasSuperMethodsWithTypeParams(psiMethod)); + if (!shouldSuppress) { + if (typeParameterListOwner instanceof PsiMethod psiMethod) { + myVisitor.report(JavaErrorKinds.TYPE_PARAMETER_ABSENT_METHOD.create(referenceParameterList, psiMethod)); + } + else if (typeParameterListOwner instanceof PsiClass psiClass) { + myVisitor.report(JavaErrorKinds.TYPE_PARAMETER_ABSENT_CLASS.create(referenceParameterList, psiClass)); + } + return; + } + } + else { + myVisitor.report(JavaErrorKinds.TYPE_PARAMETER_COUNT_MISMATCH.create(referenceParameterList, typeParameterListOwner)); + return; + } + } + + // bounds check + if (targetParametersNum > 0 && refParametersNum != 0) { + if (inferenceResult != null) { + PsiType[] types = inferenceResult.getTypes(); + for (int i = 0; i < typeParameters.length; i++) { + checkTypeParameterWithinItsBound(typeParameters[i], substitutor, types[i], referenceElements[0], referenceParameterList); + if (myVisitor.hasErrorResults()) return; + } + } + else { + for (int i = 0; i < typeParameters.length; i++) { + PsiTypeElement typeElement = referenceElements[i]; + checkTypeParameterWithinItsBound(typeParameters[i], substitutor, typeElement.getType(), typeElement, referenceParameterList); + if (myVisitor.hasErrorResults()) return; + } + } + } + } + + private void checkTypeParameterWithinItsBound(@NotNull PsiTypeParameter classParameter, + @NotNull PsiSubstitutor substitutor, + @NotNull PsiType type, + @NotNull PsiTypeElement typeElement2Highlight, + @Nullable PsiReferenceParameterList referenceParameterList) { + PsiClass referenceClass = type instanceof PsiClassType classType ? classType.resolve() : null; + PsiType psiType = substitutor.substitute(classParameter); + if (psiType instanceof PsiClassType && !(PsiUtil.resolveClassInType(psiType) instanceof PsiTypeParameter)) { + if (GenericsUtil.checkNotInBounds(type, psiType, referenceParameterList)) { + myVisitor.report(JavaErrorKinds.TYPE_PARAMETER_ACTUAL_INFERRED_MISMATCH.create(typeElement2Highlight)); + return; + } + } + + PsiClassType[] bounds = classParameter.getSuperTypes(); + for (PsiType bound : bounds) { + bound = substitutor.substitute(bound); + if (!bound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) && GenericsUtil.checkNotInBounds(type, bound, referenceParameterList)) { + PsiClass boundClass = bound instanceof PsiClassType classType ? classType.resolve() : null; + + boolean extend = boundClass == null || + referenceClass == null || + referenceClass.isInterface() == boundClass.isInterface() || + referenceClass instanceof PsiTypeParameter; + var kind = extend + ? JavaErrorKinds.TYPE_PARAMETER_TYPE_NOT_WITHIN_EXTEND_BOUND + : JavaErrorKinds.TYPE_PARAMETER_TYPE_NOT_WITHIN_IMPLEMENT_BOUND; + myVisitor.report(kind.create(typeElement2Highlight, new JavaErrorKinds.TypeParameterBoundMismatchContext( + classParameter, bound, type))); + } + } + } + + private static PsiType detectExpectedType(@NotNull PsiReferenceParameterList referenceParameterList) { + PsiNewExpression newExpression = Objects.requireNonNull(PsiTreeUtil.getParentOfType(referenceParameterList, PsiNewExpression.class)); + PsiElement parent = newExpression.getParent(); + PsiType expectedType = null; + if (parent instanceof PsiVariable psiVariable && newExpression.equals(psiVariable.getInitializer())) { + expectedType = psiVariable.getType(); + } + else if (parent instanceof PsiAssignmentExpression expression && newExpression.equals(expression.getRExpression())) { + expectedType = expression.getLExpression().getType(); + } + else if (parent instanceof PsiReturnStatement) { + PsiElement method = PsiTreeUtil.getParentOfType(parent, PsiMethod.class, PsiLambdaExpression.class); + if (method instanceof PsiMethod psiMethod) { + expectedType = psiMethod.getReturnType(); + } + } + else if (parent instanceof PsiExpressionList) { + PsiElement pParent = parent.getParent(); + if (pParent instanceof PsiCallExpression callExpression) { + PsiExpressionList argumentList = callExpression.getArgumentList(); + if (parent.equals(argumentList)) { + PsiMethod method = callExpression.resolveMethod(); + if (method != null) { + PsiExpression[] expressions = argumentList.getExpressions(); + int idx = ArrayUtilRt.find(expressions, newExpression); + if (idx > -1) { + PsiParameter parameter = method.getParameterList().getParameter(idx); + if (parameter != null) { + expectedType = parameter.getType(); + } + } + } + } + } + } + return expectedType; + } + + private static boolean hasSuperMethodsWithTypeParams(@NotNull PsiMethod method) { + for (PsiMethod superMethod : method.findDeepestSuperMethods()) { + if (superMethod.hasTypeParameters()) return true; + } + return false; + } } diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorFormatUtil.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorFormatUtil.java index 9df46582e87c..52c8ab0ad9a4 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorFormatUtil.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorFormatUtil.java @@ -178,4 +178,9 @@ final class JavaErrorFormatUtil { if (element instanceof PsiLabeledStatement statement) return statement.getName() + ':'; return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); } + + static @NotNull String formatClassOrType(@NotNull PsiType type) { + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); + return psiClass == null ? type.getPresentableText() : formatClass(psiClass); + } } 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 5730e1887d15..0cfd540a4252 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 @@ -385,6 +385,41 @@ public final class JavaErrorKinds { parameterized(PsiMethodCallExpression.class, IncompatibleIntersectionContext.class, "type.parameter.incompatible.upper.bounds") .withRange((call, ctx) -> getRange(call)) .withRawDescription((call, ctx) -> message("type.parameter.incompatible.upper.bounds", ctx.parameter().getName(), ctx.message())); + public static final Parameterized + TYPE_PARAMETER_INFERRED_TYPE_NOT_WITHIN_EXTEND_BOUND = + parameterized(PsiMethodCallExpression.class, TypeParameterBoundMismatchContext.class, + "type.parameter.inferred.type.not.within.extend.bound") + .withRawDescription((call, ctx) -> message("type.parameter.inferred.type.not.within.extend.bound", formatClass(ctx.parameter()), + formatType(ctx.bound()), formatType(ctx.actualType()))); + public static final Parameterized + TYPE_PARAMETER_INFERRED_TYPE_NOT_WITHIN_IMPLEMENT_BOUND = + parameterized(PsiMethodCallExpression.class, TypeParameterBoundMismatchContext.class, + "type.parameter.inferred.type.not.within.implement.bound") + .withRawDescription((call, ctx) -> message("type.parameter.inferred.type.not.within.implement.bound", formatClass(ctx.parameter()), + formatType(ctx.bound()), formatType(ctx.actualType()))); + public static final Parameterized TYPE_PARAMETER_TYPE_NOT_WITHIN_EXTEND_BOUND = + parameterized(PsiTypeElement.class, TypeParameterBoundMismatchContext.class, "type.parameter.type.not.within.extend.bound") + .withRawDescription((call, ctx) -> message("type.parameter.type.not.within.extend.bound", + formatClassOrType(ctx.actualType()), + formatType(ctx.bound()))); + public static final Parameterized TYPE_PARAMETER_TYPE_NOT_WITHIN_IMPLEMENT_BOUND = + parameterized(PsiTypeElement.class, TypeParameterBoundMismatchContext.class, "type.parameter.type.not.within.implement.bound") + .withRawDescription((call, ctx) -> message("type.parameter.type.not.within.implement.bound", + formatClassOrType(ctx.actualType()), + formatType(ctx.bound()))); + public static final Parameterized TYPE_PARAMETER_ABSENT_CLASS = + parameterized(PsiReferenceParameterList.class, PsiClass.class, "type.parameter.absent.class") + .withRawDescription((list, cls) -> message("type.parameter.absent.class", formatClass(cls))); + public static final Parameterized TYPE_PARAMETER_ABSENT_METHOD = + parameterized(PsiReferenceParameterList.class, PsiMethod.class, "type.parameter.absent.method") + .withRawDescription((list, method) -> message("type.parameter.absent.method", formatMethod(method))); + public static final Parameterized TYPE_PARAMETER_COUNT_MISMATCH = + parameterized(PsiReferenceParameterList.class, PsiTypeParameterListOwner.class, "type.parameter.count.mismatch") + .withRawDescription((list, owner) -> message("type.parameter.count.mismatch", list.getTypeArgumentCount(), + owner.getTypeParameters().length)); + public static final Simple TYPE_PARAMETER_ACTUAL_INFERRED_MISMATCH = error("type.parameter.actual.inferred.mismatch"); + public static final Simple NEW_EXPRESSION_DIAMOND_NOT_APPLICABLE = + error("new.expression.diamond.not.applicable"); public static final Simple METHOD_DUPLICATE = error(PsiMethod.class, "method.duplicate") @@ -567,6 +602,13 @@ public final class JavaErrorKinds { error("new.expression.qualified.qualified.class.reference"); public static final Simple NEW_EXPRESSION_DIAMOND_NOT_ALLOWED = error("new.expression.diamond.not.allowed"); + public static final Simple NEW_EXPRESSION_DIAMOND_ANONYMOUS_INNER_NON_PRIVATE = + error("new.expression.diamond.anonymous.inner.non.private"); + public static final Parameterized + NEW_EXPRESSION_DIAMOND_INFERENCE_FAILURE = + parameterized(PsiReferenceParameterList.class, PsiDiamondType.DiamondInferenceResult.class, "new.expression.diamond.inference.failure") + .withRawDescription( + (list, inferenceResult) -> message("new.expression.diamond.inference.failure", inferenceResult.getErrorMessage())); public static final Parameterized REFERENCE_TYPE_ARGUMENT_STATIC_CLASS = parameterized(PsiReferenceParameterList.class, PsiClass.class, "reference.type.argument.static.class") @@ -764,4 +806,10 @@ public final class JavaErrorKinds { } public record IncompatibleIntersectionContext(@NotNull PsiTypeParameter parameter, @NotNull @Nls String message) {} + + public record TypeParameterBoundMismatchContext(@NotNull PsiTypeParameter parameter, + @NotNull PsiType bound, + @NotNull PsiType actualType) { + + } } 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 3f5a7342249b..3ca9c39095d2 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 @@ -11,7 +11,6 @@ import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper; import com.intellij.core.JavaPsiBundle; import com.intellij.ide.IdeBundle; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.openapi.projectRoots.JavaVersionService; import com.intellij.openapi.roots.FileIndexFacade; @@ -26,7 +25,6 @@ import com.intellij.psi.*; import com.intellij.psi.impl.PsiClassImplUtil; import com.intellij.psi.impl.PsiImplUtil; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.PsiShortNamesCache; import com.intellij.psi.util.*; import com.intellij.util.ArrayUtilRt; import com.intellij.util.ObjectUtils; @@ -45,33 +43,6 @@ public final class GenericsHighlightUtil { private GenericsHighlightUtil() { } - static HighlightInfo.Builder checkInferredTypeArguments(@NotNull PsiTypeParameterListOwner listOwner, - @NotNull PsiElement call, - @NotNull PsiSubstitutor substitutor) { - PsiTypeParameter[] typeParameters = listOwner.getTypeParameters(); - Pair inferredTypeArgument = GenericsUtil.findTypeParameterWithBoundError(typeParameters, substitutor, - call, false); - if (inferredTypeArgument != null) { - PsiType extendsType = inferredTypeArgument.second; - PsiTypeParameter typeParameter = inferredTypeArgument.first; - PsiClass boundClass = extendsType instanceof PsiClassType classType ? classType.resolve() : null; - - @NonNls String messageKey = boundClass == null || typeParameter.isInterface() == boundClass.isInterface() - ? "generics.inferred.type.for.type.parameter.is.not.within.its.bound.extend" - : "generics.inferred.type.for.type.parameter.is.not.within.its.bound.implement"; - - String description = JavaErrorBundle.message( - messageKey, - HighlightUtil.formatClass(typeParameter), - JavaHighlightUtil.formatType(extendsType), - JavaHighlightUtil.formatType(substitutor.substitute(typeParameter)) - ); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(call).descriptionAndTooltip(description); - } - - return null; - } - static HighlightInfo.Builder checkParameterizedReferenceTypeArguments(@Nullable PsiElement resolved, @NotNull PsiJavaCodeReferenceElement referenceElement, @NotNull PsiSubstitutor substitutor, @@ -169,7 +140,7 @@ public final class GenericsHighlightUtil { .highPriority(QuickFixFactory.getInstance().createDeleteFix(referenceParameterList)); builder.registerFix(action, null, null, null, null); } - registerVariableParameterizedTypeFixes(builder, (PsiVariable)variable, referenceParameterList, javaSdkVersion); + HighlightFixUtil.registerVariableParameterizedTypeFixes(HighlightUtil.asConsumer(builder), (PsiVariable)variable, referenceParameterList); } } } @@ -333,8 +304,8 @@ public final class GenericsHighlightUtil { if (aClass instanceof PsiTypeParameter) { HighlightInfo.Builder info = HighlightMethodUtil.checkMethodIncompatibleReturnType(signature, signature.getSuperSignatures(), true, - HighlightNamesUtil.getClassDeclarationTextRange(aClass), - null); + HighlightNamesUtil.getClassDeclarationTextRange(aClass) + ); if (info != null) { overrideEquivalentMethodsErrors.put(aClass, info); } @@ -1076,42 +1047,6 @@ public final class GenericsHighlightUtil { return containingClass != null && PsiUtil.typeParametersIterator(containingClass).hasNext(); } - private static void registerVariableParameterizedTypeFixes(@Nullable HighlightInfo.Builder builder, - @NotNull PsiVariable variable, - @NotNull PsiReferenceParameterList parameterList, - @NotNull JavaSdkVersion version) { - PsiType type = variable.getType(); - if (!(type instanceof PsiClassType classType) || builder == null) return; - - if (DumbService.getInstance(variable.getProject()).isDumb()) return; - - String shortName = classType.getClassName(); - PsiManager manager = parameterList.getManager(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); - PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(parameterList.getProject()); - PsiClass[] classes = shortNamesCache.getClassesByName(shortName, GlobalSearchScope.allScope(manager.getProject())); - PsiElementFactory factory = facade.getElementFactory(); - for (PsiClass aClass : classes) { - if (aClass == null) { - LOG.error("null class returned for " + shortName); - continue; - } - if (checkReferenceTypeArgumentList(aClass, parameterList, PsiSubstitutor.EMPTY, false, version) == null) { - PsiType[] actualTypeParameters = parameterList.getTypeArguments(); - PsiTypeParameter[] classTypeParameters = aClass.getTypeParameters(); - Map map = new HashMap<>(); - for (int j = 0; j < Math.min(classTypeParameters.length, actualTypeParameters.length); j++) { - PsiTypeParameter classTypeParameter = classTypeParameters[j]; - PsiType actualTypeParameter = actualTypeParameters[j]; - map.put(classTypeParameter, actualTypeParameter); - } - PsiSubstitutor substitutor = factory.createSubstitutor(map); - PsiType suggestedType = factory.createType(aClass, substitutor); - HighlightFixUtil.registerChangeVariableTypeFixes(variable, suggestedType, variable.getInitializer(), builder); - } - } - } - static HighlightInfo.Builder checkMemberSignatureTypesAccessibility(@NotNull PsiReferenceExpression ref) { String message = null; diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java index ca3ceb4f1129..e42b10c05ede 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightFixUtil.java @@ -14,11 +14,16 @@ import com.intellij.lang.jvm.actions.JvmElementActionFactories; import com.intellij.lang.jvm.actions.MemberRequestsKt; import com.intellij.lang.jvm.util.JvmUtil; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdkVersion; +import com.intellij.openapi.projectRoots.JavaVersionService; import com.intellij.psi.*; import com.intellij.psi.controlFlow.*; import com.intellij.psi.infos.CandidateInfo; import com.intellij.psi.infos.MethodCandidateInfo; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiShortNamesCache; import com.intellij.psi.util.*; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; @@ -31,10 +36,7 @@ import com.siyeh.ig.psiutils.VariableAccessUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; public final class HighlightFixUtil { @@ -713,7 +715,7 @@ public final class HighlightFixUtil { } } - static void registerCallInferenceFixes(@NotNull PsiMethodCallExpression callExpression, @NotNull Consumer info) { + static void registerCallInferenceFixes(@NotNull PsiMethodCallExpression callExpression, @NotNull Consumer info) { JavaResolveResult result = callExpression.getMethodExpression().advancedResolve(true); if (!(result instanceof MethodCandidateInfo resolveResult)) return; PsiMethod method = resolveResult.getElement(); @@ -749,4 +751,41 @@ public final class HighlightFixUtil { } } } + + static void registerVariableParameterizedTypeFixes(@NotNull Consumer info, + @NotNull PsiVariable variable, + @NotNull PsiReferenceParameterList parameterList) { + PsiType type = variable.getType(); + if (!(type instanceof PsiClassType classType)) return; + + if (DumbService.getInstance(variable.getProject()).isDumb()) return; + + String shortName = classType.getClassName(); + PsiFile file = parameterList.getContainingFile(); + Project project = file.getProject(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(project); + PsiClass[] classes = shortNamesCache.getClassesByName(shortName, GlobalSearchScope.allScope(project)); + PsiElementFactory factory = facade.getElementFactory(); + JavaSdkVersion version = Objects.requireNonNullElse(JavaVersionService.getInstance().getJavaSdkVersion(file), + JavaSdkVersion.fromLanguageLevel(PsiUtil.getLanguageLevel(file))); + for (PsiClass aClass : classes) { + if (aClass == null) { + continue; + } + if (GenericsHighlightUtil.checkReferenceTypeArgumentList(aClass, parameterList, PsiSubstitutor.EMPTY, false, version) == null) { + PsiType[] actualTypeParameters = parameterList.getTypeArguments(); + PsiTypeParameter[] classTypeParameters = aClass.getTypeParameters(); + Map map = new HashMap<>(); + for (int j = 0; j < Math.min(classTypeParameters.length, actualTypeParameters.length); j++) { + PsiTypeParameter classTypeParameter = classTypeParameters[j]; + PsiType actualTypeParameter = actualTypeParameters[j]; + map.put(classTypeParameter, actualTypeParameter); + } + PsiSubstitutor substitutor = factory.createSubstitutor(map); + PsiType suggestedType = factory.createType(aClass, substitutor); + registerChangeVariableTypeFixes(variable, suggestedType, variable.getInitializer(), info); + } + } + } } \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index 5ed3a3b3f7c8..470a87d88c5d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -80,7 +80,7 @@ public final class HighlightMethodUtil { static HighlightInfo.Builder checkMethodIncompatibleReturnType(@NotNull MethodSignatureBackedByPsiMethod methodSignature, @NotNull List superMethodSignatures, boolean includeRealPositionInfo, - @Nullable TextRange textRange, @Nullable Ref description) { + @Nullable TextRange textRange) { PsiMethod method = methodSignature.getMethod(); PsiType returnType = methodSignature.getSubstitutor().substitute(method.getReturnType()); PsiClass aClass = method.getContainingClass(); @@ -104,7 +104,7 @@ public final class HighlightMethodUtil { } HighlightInfo.Builder info = checkSuperMethodSignature( superMethod, superMethodSignature, superReturnType, method, methodSignature, returnType, - textRange, PsiUtil.getLanguageLevel(aClass), description); + textRange, PsiUtil.getLanguageLevel(aClass)); if (info != null) { return info; } @@ -120,8 +120,7 @@ public final class HighlightMethodUtil { @NotNull MethodSignatureBackedByPsiMethod methodSignature, @NotNull PsiType returnType, @NotNull TextRange range, - @NotNull LanguageLevel languageLevel, - @Nullable Ref description) { + @NotNull LanguageLevel languageLevel) { PsiClass superContainingClass = superMethod.getContainingClass(); if (superContainingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(superContainingClass.getQualifiedName()) && @@ -155,20 +154,17 @@ public final class HighlightMethodUtil { } return createIncompatibleReturnTypeMessage(method, superMethod, substitutedSuperReturnType, returnType, - JavaErrorBundle.message("incompatible.return.type"), range, - description); + JavaErrorBundle.message("incompatible.return.type"), range + ); } - private static @NotNull HighlightInfo.Builder createIncompatibleReturnTypeMessage(@NotNull PsiMethod method, + private static HighlightInfo.@NotNull Builder createIncompatibleReturnTypeMessage(@NotNull PsiMethod method, @NotNull PsiMethod superMethod, @NotNull PsiType substitutedSuperReturnType, @NotNull PsiType returnType, @NotNull @Nls String detailMessage, - @NotNull TextRange textRange, @Nullable Ref descriptionH) { + @NotNull TextRange textRange) { String description = MessageFormat.format("{0}; {1}", createClashMethodMessage(method, superMethod, true), detailMessage); - if (descriptionH != null) { - descriptionH.set(description); - } HighlightInfo.Builder errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description); if (method instanceof LightRecordMethod recordMethod) { @@ -193,49 +189,6 @@ public final class HighlightMethodUtil { return errorResult; } - - static void checkMethodCall(@NotNull PsiMethodCallExpression methodCall, - @NotNull PsiResolveHelper resolveHelper, - @NotNull JavaSdkVersion javaSdkVersion, - @NotNull Consumer errorSink) { - PsiExpressionList list = methodCall.getArgumentList(); - PsiReferenceExpression referenceToMethod = methodCall.getMethodExpression(); - JavaResolveResult[] results = referenceToMethod.multiResolve(true); - JavaResolveResult resolveResult = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; - PsiElement resolved = resolveResult.getElement(); - - boolean isDummy = isDummyConstructorCall(methodCall, resolveHelper, list, referenceToMethod); - if (isDummy) return; - HighlightInfo.Builder builder = null; - - PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - if (!(resolved instanceof PsiMethod) || !resolveResult.isValidResult()) { - MethodCandidateInfo candidateInfo = resolveResult instanceof MethodCandidateInfo ? (MethodCandidateInfo)resolveResult : null; - PsiMethod resolvedMethod = candidateInfo != null ? candidateInfo.getElement() : null; - - if (resolveResult.isAccessible() && resolveResult.isStaticsScopeCorrect()) { - if (candidateInfo != null && !candidateInfo.isApplicable()) { - if (!candidateInfo.isTypeArgumentsApplicable()) { - PsiReferenceParameterList typeArgumentList = methodCall.getTypeArgumentList(); - PsiSubstitutor applicabilitySubstitutor = candidateInfo.getSubstitutor(false); - if (typeArgumentList.getTypeArguments().length == 0 && resolvedMethod.hasTypeParameters()) { - builder = GenericsHighlightUtil.checkInferredTypeArguments(resolvedMethod, methodCall, applicabilitySubstitutor); - } - else { - builder = GenericsHighlightUtil.checkParameterizedReferenceTypeArguments(resolved, referenceToMethod, applicabilitySubstitutor, javaSdkVersion); - } - } - } - } - } - if (builder == null) { - builder = GenericsHighlightUtil.checkParameterizedReferenceTypeArguments(resolved, referenceToMethod, substitutor, javaSdkVersion); - } - if (builder != null) { - errorSink.accept(builder); - } - } - /** * collect highlightInfos per each wrong argument; fixes would be set for the first one with fixRange: methodCall * @return highlight info for the first wrong arg expression 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 d0d23b090cc9..0ef7ee667e88 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 @@ -770,15 +770,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh @Override public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) { visitElement(expression); - if (!hasErrorResults()) { - try { - HighlightMethodUtil.checkMethodCall(expression, getResolveHelper(), myJavaSdkVersion, - myErrorSink); - } - catch (IndexNotReadyException ignored) { - } - } - if (!hasErrorResults()) add(HighlightMethodUtil.checkConstructorCallProblems(expression)); if (!hasErrorResults()) add(HighlightMethodUtil.checkSuperAbstractMethodDirectCall(expression)); 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 0a16ff8fc25d..95cf50ce3cea 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 @@ -23,6 +23,7 @@ import com.intellij.lang.jvm.actions.MemberRequestsKt; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; import com.intellij.pom.java.JavaFeature; +import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.codeStyle.VariableKind; import com.intellij.psi.impl.light.LightRecordMethod; @@ -80,7 +81,7 @@ final class JavaErrorFixProvider { CLASS_IMPLICIT_INITIALIZER, CLASS_IMPLICIT_PACKAGE, RECORD_EXTENDS, ENUM_EXTENDS, RECORD_PERMITS, ENUM_PERMITS, ANNOTATION_PERMITS, NEW_EXPRESSION_DIAMOND_NOT_ALLOWED, REFERENCE_TYPE_ARGUMENT_STATIC_CLASS, - STATEMENT_CASE_OUTSIDE_SWITCH)) { + STATEMENT_CASE_OUTSIDE_SWITCH, NEW_EXPRESSION_DIAMOND_NOT_APPLICABLE)) { fix(kind, genericRemover); } @@ -241,6 +242,40 @@ final class JavaErrorFixProvider { fix(NEW_EXPRESSION_QUALIFIED_ANONYMOUS_IMPLEMENTS_INTERFACE, error -> myFactory.createRemoveNewQualifierFix(error.psi(), null)); fix(NEW_EXPRESSION_QUALIFIED_QUALIFIED_CLASS_REFERENCE, error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("remove.qualifier.fix"))); + fix(NEW_EXPRESSION_DIAMOND_INFERENCE_FAILURE, + error -> { + if (error.context() == PsiDiamondType.DiamondInferenceResult.ANONYMOUS_INNER_RESULT && + !PsiUtil.isLanguageLevel9OrHigher(error.psi())) { + return myFactory.createIncreaseLanguageLevelFix(LanguageLevel.JDK_1_9); + } + return null; + }); + fix(TYPE_PARAMETER_ABSENT_CLASS, error -> myFactory.createChangeClassSignatureFromUsageFix(error.context(), error.psi())); + fix(TYPE_PARAMETER_COUNT_MISMATCH, + error -> error.context() instanceof PsiClass cls ? myFactory.createChangeClassSignatureFromUsageFix(cls, error.psi()) : null); + JavaFixProvider addBoundFix = error -> { + if (error.context().bound() instanceof PsiClassType bound) { + PsiClass psiClass = bound.resolve(); + if (psiClass != null) { + return myFactory.createExtendsListFix(psiClass, bound, true); + } + } + return null; + }; + fix(TYPE_PARAMETER_TYPE_NOT_WITHIN_EXTEND_BOUND, addBoundFix); + fix(TYPE_PARAMETER_TYPE_NOT_WITHIN_IMPLEMENT_BOUND, addBoundFix); + multi(TYPE_PARAMETER_ABSENT_CLASS, error -> { + PsiReferenceParameterList referenceParameterList = error.psi(); + PsiElement grandParent = referenceParameterList.getParent().getParent(); + if (!(grandParent instanceof PsiTypeElement)) return List.of(); + if (!(PsiTreeUtil.skipParentsOfType(grandParent, PsiTypeElement.class) instanceof PsiVariable variable)) return List.of(); + List registrar = new ArrayList<>(); + if (error.context().getTypeParameters().length == 0) { + registrar.add(PriorityIntentionActionWrapper.highPriority(myFactory.createDeleteFix(referenceParameterList))); + } + HighlightFixUtil.registerVariableParameterizedTypeFixes(registrar::add, variable, referenceParameterList); + return registrar; + }); fix(LITERAL_CHARACTER_TOO_LONG, error -> myFactory.createConvertToStringLiteralAction()); fix(LITERAL_CHARACTER_EMPTY, error -> myFactory.createConvertToStringLiteralAction()); fix(PATTERN_TYPE_PATTERN_EXPECTED, error -> { diff --git a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties index ed11e07f618d..8158f495d0e8 100644 --- a/java/java-psi-impl/resources/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/resources/messages/JavaErrorBundle.properties @@ -12,8 +12,6 @@ annotation.not.allowed.in.permit.list=Annotations not allowed in 'permits' list generics.holder.type=Type generics.holder.method=Method -generics.inferred.type.for.type.parameter.is.not.within.its.bound.extend=Inferred type ''{2}'' for type parameter ''{0}'' is not within its bound; should extend ''{1}'' -generics.inferred.type.for.type.parameter.is.not.within.its.bound.implement=Inferred type ''{2}'' for type parameter ''{0}'' is not within its bound; should implement ''{1}'' generics.type.parameter.is.not.within.its.bound.extend=Type parameter ''{0}'' is not within its bound; should extend ''{1}'' generics.type.parameter.is.not.within.its.bound.implement=Type parameter ''{0}'' is not within its bound; should implement ''{1}''