diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/DefaultInferredAnnotationProvider.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/DefaultInferredAnnotationProvider.java index 86eafe09a0..fc261ee9ab 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/DefaultInferredAnnotationProvider.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/DefaultInferredAnnotationProvider.java @@ -23,6 +23,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.JavaMethodContractUtil.ORG_JETBRAINS_ANNOTATIONS_CONTRACT; @@ -30,261 +31,268 @@ @ExtensionImpl public class DefaultInferredAnnotationProvider implements InferredAnnotationProvider { - private static final Set JB_INFERRED_ANNOTATIONS = - Set.of(ORG_JETBRAINS_ANNOTATIONS_CONTRACT, Mutability.UNMODIFIABLE_ANNOTATION, - Mutability.UNMODIFIABLE_VIEW_ANNOTATION); - private static final Set EXPERIMENTAL_INFERRED_ANNOTATIONS = - Set.of(Mutability.UNMODIFIABLE_ANNOTATION, Mutability.UNMODIFIABLE_VIEW_ANNOTATION); - private final Project myProject; - - // Could be added via external annotations, but there are many signatures to handle - // and we have troubles supporting external annotations for JDK 9+ - private static final CallMatcher IMMUTABLE_FACTORY = CallMatcher.anyOf( - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_LIST, "of", "copyOf"), - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_SET, "of", "copyOf"), - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_MAP, "of", "ofEntries", "copyOf", "entry") - ); - private final NullableNotNullManager myNullabilityManager; - - @Inject - public DefaultInferredAnnotationProvider(Project project, NullableNotNullManager nullabilityManager) { - myProject = project; - myNullabilityManager = nullabilityManager; - } - - @Nullable - @Override - public PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN) { - if (!JB_INFERRED_ANNOTATIONS.contains(annotationFQN) && !isDefaultNullabilityAnnotation(annotationFQN)) { - return null; + private static final Set JB_INFERRED_ANNOTATIONS = Set.of( + ORG_JETBRAINS_ANNOTATIONS_CONTRACT, + Mutability.UNMODIFIABLE_ANNOTATION, + Mutability.UNMODIFIABLE_VIEW_ANNOTATION + ); + private static final Set EXPERIMENTAL_INFERRED_ANNOTATIONS = + Set.of(Mutability.UNMODIFIABLE_ANNOTATION, Mutability.UNMODIFIABLE_VIEW_ANNOTATION); + private final Project myProject; + + // Could be added via external annotations, but there are many signatures to handle + // and we have troubles supporting external annotations for JDK 9+ + private static final CallMatcher IMMUTABLE_FACTORY = CallMatcher.anyOf( + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_LIST, "of", "copyOf"), + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_SET, "of", "copyOf"), + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_MAP, "of", "ofEntries", "copyOf", "entry") + ); + private final NullableNotNullManager myNullabilityManager; + + @Inject + public DefaultInferredAnnotationProvider(Project project, NullableNotNullManager nullabilityManager) { + myProject = project; + myNullabilityManager = nullabilityManager; } - listOwner = PsiUtil.preferCompiledElement(listOwner); + @Nullable + @Override + public PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN) { + if (!JB_INFERRED_ANNOTATIONS.contains(annotationFQN) && !isDefaultNullabilityAnnotation(annotationFQN)) { + return null; + } - if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && listOwner instanceof PsiMethod) { - PsiAnnotation anno = getHardcodedContractAnnotation((PsiMethod) listOwner); - if (anno != null) { - return anno; - } - } + listOwner = PsiUtil.preferCompiledElement(listOwner); - if (ignoreInference(listOwner, annotationFQN)) { - return null; - } - - PsiAnnotation fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotation(listOwner, annotationFQN); - if (fromBytecode != null) { - return fromBytecode; - } + if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && listOwner instanceof PsiMethod) { + PsiAnnotation anno = getHardcodedContractAnnotation((PsiMethod)listOwner); + if (anno != null) { + return anno; + } + } - if (isDefaultNullabilityAnnotation(annotationFQN)) { - PsiAnnotation anno = null; - if (listOwner instanceof PsiMethodImpl) { - anno = getInferredNullabilityAnnotation((PsiMethodImpl) listOwner); - } - if (listOwner instanceof PsiParameter) { - anno = getInferredNullabilityAnnotation((PsiParameter) listOwner); - } - return anno == null ? null : annotationFQN.equals(anno.getQualifiedName()) ? anno : null; - } + if (ignoreInference(listOwner, annotationFQN)) { + return null; + } - if (Mutability.UNMODIFIABLE_ANNOTATION.equals(annotationFQN) || Mutability.UNMODIFIABLE_VIEW_ANNOTATION.equals(annotationFQN)) { - return getInferredMutabilityAnnotation(listOwner); - } + PsiAnnotation fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotation(listOwner, annotationFQN); + if (fromBytecode != null) { + return fromBytecode; + } - if (listOwner instanceof PsiMethodImpl && ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN)) { - return getInferredContractAnnotation((PsiMethodImpl) listOwner); - } + if (isDefaultNullabilityAnnotation(annotationFQN)) { + PsiAnnotation anno = null; + if (listOwner instanceof PsiMethodImpl) { + anno = getInferredNullabilityAnnotation((PsiMethodImpl)listOwner); + } + if (listOwner instanceof PsiParameter) { + anno = getInferredNullabilityAnnotation((PsiParameter)listOwner); + } + return anno == null ? null : annotationFQN.equals(anno.getQualifiedName()) ? anno : null; + } - return null; - } + if (Mutability.UNMODIFIABLE_ANNOTATION.equals(annotationFQN) || Mutability.UNMODIFIABLE_VIEW_ANNOTATION.equals(annotationFQN)) { + return getInferredMutabilityAnnotation(listOwner); + } - private boolean isDefaultNullabilityAnnotation(String annotationFQN) { - return annotationFQN.equals(myNullabilityManager.getDefaultNullable()) || annotationFQN.equals(myNullabilityManager.getDefaultNotNull()); - } + if (listOwner instanceof PsiMethodImpl && ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN)) { + return getInferredContractAnnotation((PsiMethodImpl)listOwner); + } - @Nullable - private PsiAnnotation getHardcodedContractAnnotation(PsiMethod method) { - PsiClass aClass = method.getContainingClass(); - if (aClass != null && aClass.getQualifiedName() != null && aClass.getQualifiedName().startsWith("org.assertj.core.api.")) { - return createContractAnnotation(Collections.emptyList(), MutationSignature.pure()); - } - List contracts = HardcodedContracts.getHardcodedContracts(method, null); - return contracts.isEmpty() ? null : createContractAnnotation(contracts, HardcodedContracts.getHardcodedMutation(method)); - } - - @Nullable - private PsiAnnotation createContractAnnotation(List contracts, MutationSignature signature) { - return createContractAnnotation(myProject, signature.isPure(), - StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), - signature.isPure() || signature == MutationSignature.unknown() ? "" : signature.toString()); - } - - /** - * There is a number of well-known methods where automatic inference fails (for example, {@link Objects#requireNonNull(Object)}. - * For such methods, contracts are hardcoded, and for their parameters inferred @NotNull are suppressed.

- *

- * {@link Contract} and {@link Nonnull} annotations on methods are not necessarily applicable to the overridden implementations, so they're ignored, too.

- * - * @return whether inference is to be suppressed the given annotation on the given method or parameter - */ - private boolean ignoreInference(@Nonnull PsiModifierListOwner owner, @Nullable String annotationFQN) { - if (annotationFQN == null) - return true; - if (owner instanceof PsiMethod && PsiUtil.canBeOverridden((PsiMethod) owner)) { - return true; + return null; } - if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && HardcodedContracts.hasHardcodedContracts(owner)) { - return true; - } - if (annotationFQN.equals(myNullabilityManager.getDefaultNotNull()) && owner instanceof PsiParameter && owner.getParent() != null) { - List annotations = NullableNotNullManager.getInstance(owner.getProject()).getNullables(); - if (isAnnotated(owner, annotations, CHECK_EXTERNAL | CHECK_TYPE)) { - return true; - } - if (HardcodedContracts.hasHardcodedContracts(owner)) { - return true; - } - } - return false; - } - @Nullable - private PsiAnnotation getInferredMutabilityAnnotation(@Nonnull PsiModifierListOwner owner) { - if (owner instanceof PsiMethod && IMMUTABLE_FACTORY.methodMatches((PsiMethod) owner)) { - return Mutability.UNMODIFIABLE.asAnnotation(myProject); - } - if (!(owner instanceof PsiMethodImpl)) - return null; - PsiMethodImpl method = (PsiMethodImpl) owner; - PsiModifierList modifiers = method.getModifierList(); - if (modifiers.hasAnnotation(Mutability.UNMODIFIABLE_ANNOTATION) || - modifiers.hasAnnotation(Mutability.UNMODIFIABLE_VIEW_ANNOTATION)) { - return null; + private boolean isDefaultNullabilityAnnotation(String annotationFQN) { + return annotationFQN.equals(myNullabilityManager.getDefaultNullable()) || annotationFQN.equals(myNullabilityManager.getDefaultNotNull()); } - return JavaSourceInference.inferMutability(method).asAnnotation(myProject); - } - @Nullable - private PsiAnnotation getInferredContractAnnotation(PsiMethodImpl method) { - if (method.getModifierList().hasAnnotation(ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { - return null; + @Nullable + private PsiAnnotation getHardcodedContractAnnotation(PsiMethod method) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null && aClass.getQualifiedName() != null && aClass.getQualifiedName().startsWith("org.assertj.core.api.")) { + return createContractAnnotation(Collections.emptyList(), MutationSignature.pure()); + } + List contracts = HardcodedContracts.getHardcodedContracts(method, null); + return contracts.isEmpty() ? null : createContractAnnotation(contracts, HardcodedContracts.getHardcodedMutation(method)); } - return createContractAnnotation(JavaSourceInference.inferContracts(method), JavaSourceInference.inferPurity(method)); - } - - @Nullable - private PsiAnnotation getInferredNullabilityAnnotation(PsiMethodImpl method) { - if (hasExplicitNullability(method)) { - return null; + @Nullable + private PsiAnnotation createContractAnnotation(List contracts, MutationSignature signature) { + return createContractAnnotation(myProject, signature.isPure(), + StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), + signature.isPure() || signature == MutationSignature.unknown() ? "" : signature.toString() + ); } - Nullability nullability = JavaSourceInference.inferNullability(method); - if (nullability == Nullability.NOT_NULL) { - return ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation(); + + /** + * There is a number of well-known methods where automatic inference fails (for example, {@link Objects#requireNonNull(Object)}. + * For such methods, contracts are hardcoded, and for their parameters inferred @NotNull are suppressed.

+ *

+ * {@link Contract} and {@link Nonnull} annotations on methods are not necessarily applicable to the overridden implementations, so they're ignored, too.

+ * + * @return whether inference is to be suppressed the given annotation on the given method or parameter + */ + private boolean ignoreInference(@Nonnull PsiModifierListOwner owner, @Nullable String annotationFQN) { + if (annotationFQN == null) { + return true; + } + if (owner instanceof PsiMethod && PsiUtil.canBeOverridden((PsiMethod)owner)) { + return true; + } + if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && HardcodedContracts.hasHardcodedContracts(owner)) { + return true; + } + if (annotationFQN.equals(myNullabilityManager.getDefaultNotNull()) && owner instanceof PsiParameter && owner.getParent() != null) { + List annotations = NullableNotNullManager.getInstance(owner.getProject()).getNullables(); + if (isAnnotated(owner, annotations, CHECK_EXTERNAL | CHECK_TYPE)) { + return true; + } + if (HardcodedContracts.hasHardcodedContracts(owner)) { + return true; + } + } + return false; } - if (nullability == Nullability.NULLABLE) { - return ProjectBytecodeAnalysis.getInstance(myProject).getNullableAnnotation(); + + @Nullable + private PsiAnnotation getInferredMutabilityAnnotation(@Nonnull PsiModifierListOwner owner) { + if (owner instanceof PsiMethod && IMMUTABLE_FACTORY.methodMatches((PsiMethod)owner)) { + return Mutability.UNMODIFIABLE.asAnnotation(myProject); + } + if (!(owner instanceof PsiMethodImpl)) { + return null; + } + PsiMethodImpl method = (PsiMethodImpl)owner; + PsiModifierList modifiers = method.getModifierList(); + if (modifiers.hasAnnotation(Mutability.UNMODIFIABLE_ANNOTATION) || + modifiers.hasAnnotation(Mutability.UNMODIFIABLE_VIEW_ANNOTATION)) { + return null; + } + return JavaSourceInference.inferMutability(method).asAnnotation(myProject); } - return null; - } - private boolean hasExplicitNullability(PsiModifierListOwner owner) { - return NullableNotNullManager.getInstance(myProject).findExplicitNullability(owner) != null; - } + @Nullable + private PsiAnnotation getInferredContractAnnotation(PsiMethodImpl method) { + if (method.getModifierList().hasAnnotation(ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { + return null; + } - @Nullable - private PsiAnnotation getInferredNullabilityAnnotation(PsiParameter parameter) { - if (hasExplicitNullability(parameter)) { - return null; + return createContractAnnotation(JavaSourceInference.inferContracts(method), JavaSourceInference.inferPurity(method)); } - PsiElement parent = parameter.getParent(); - if (!(parent instanceof PsiParameterList)) - return null; - PsiElement scope = parent.getParent(); - if (scope instanceof PsiMethod) { - PsiMethod method = (PsiMethod) scope; - if (method.getName().equals("of")) { - PsiClass containingClass = method.getContainingClass(); - if (containingClass != null) { - String className = containingClass.getQualifiedName(); - if (CommonClassNames.JAVA_UTIL_LIST.equals(className) || - CommonClassNames.JAVA_UTIL_SET.equals(className) || - CommonClassNames.JAVA_UTIL_MAP.equals(className) || - "java.util.EnumSet".equals(className)) { + + @Nullable + private PsiAnnotation getInferredNullabilityAnnotation(PsiMethodImpl method) { + if (hasExplicitNullability(method)) { + return null; + } + Nullability nullability = JavaSourceInference.inferNullability(method); + if (nullability == Nullability.NOT_NULL) { return ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation(); - } } - } + if (nullability == Nullability.NULLABLE) { + return ProjectBytecodeAnalysis.getInstance(myProject).getNullableAnnotation(); + } + return null; } - Nullability nullability = JavaSourceInference.inferNullability(parameter); - return nullability == Nullability.NOT_NULL ? ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation() : null; - } - - @Nullable - private PsiAnnotation createContractAnnotation(List contracts, boolean pure) { - return createContractAnnotation(myProject, pure, StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), ""); - } - - @Nullable - public static PsiAnnotation createContractAnnotation(Project project, boolean pure, String contracts, String mutates) { - Map attrMap = new LinkedHashMap<>(); - if (!contracts.isEmpty()) { - attrMap.put("value", StringUtil.wrapWithDoubleQuote(contracts)); + + private boolean hasExplicitNullability(PsiModifierListOwner owner) { + return NullableNotNullManager.getInstance(myProject).findExplicitNullability(owner) != null; } - if (pure) { - attrMap.put("pure", "true"); - } else if (!mutates.trim().isEmpty()) { - attrMap.put("mutates", StringUtil.wrapWithDoubleQuote(mutates)); + + @Nullable + private PsiAnnotation getInferredNullabilityAnnotation(PsiParameter parameter) { + if (hasExplicitNullability(parameter)) { + return null; + } + PsiElement parent = parameter.getParent(); + if (!(parent instanceof PsiParameterList)) { + return null; + } + PsiElement scope = parent.getParent(); + if (scope instanceof PsiMethod) { + PsiMethod method = (PsiMethod)scope; + if (method.getName().equals("of")) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass != null) { + String className = containingClass.getQualifiedName(); + if (CommonClassNames.JAVA_UTIL_LIST.equals(className) || + CommonClassNames.JAVA_UTIL_SET.equals(className) || + CommonClassNames.JAVA_UTIL_MAP.equals(className) || + "java.util.EnumSet".equals(className)) { + return ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation(); + } + } + } + } + Nullability nullability = JavaSourceInference.inferNullability(parameter); + return nullability == Nullability.NOT_NULL ? ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation() : null; } - if (attrMap.isEmpty()) { - return null; + + @Nullable + private PsiAnnotation createContractAnnotation(List contracts, boolean pure) { + return createContractAnnotation(myProject, pure, StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), ""); } - String attrs = attrMap.keySet().equals(Collections.singleton("value")) ? - attrMap.get("value") : EntryStream.of(attrMap).join(" = ").joining(", "); - return ProjectBytecodeAnalysis.getInstance(project).createContractAnnotation(attrs); - } - - @Nonnull - @Override - public List findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner) { - listOwner = PsiUtil.preferCompiledElement(listOwner); - List result = new ArrayList<>(); - PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotations(listOwner); - for (PsiAnnotation annotation : fromBytecode) { - if (!ignoreInference(listOwner, annotation.getQualifiedName())) { - result.add(annotation); - } + + @Nullable + public static PsiAnnotation createContractAnnotation(Project project, boolean pure, String contracts, String mutates) { + Map attrMap = new LinkedHashMap<>(); + if (!contracts.isEmpty()) { + attrMap.put("value", StringUtil.wrapWithDoubleQuote(contracts)); + } + if (pure) { + attrMap.put("pure", "true"); + } + else if (!mutates.trim().isEmpty()) { + attrMap.put("mutates", StringUtil.wrapWithDoubleQuote(mutates)); + } + if (attrMap.isEmpty()) { + return null; + } + String attrs = attrMap.keySet().equals(Collections.singleton("value")) ? + attrMap.get("value") : EntryStream.of(attrMap).join(" = ").joining(", "); + return ProjectBytecodeAnalysis.getInstance(project).createContractAnnotation(attrs); } - if (listOwner instanceof PsiMethod) { - PsiAnnotation hardcoded = getHardcodedContractAnnotation((PsiMethod) listOwner); - ContainerUtil.addIfNotNull(result, hardcoded); - if (listOwner instanceof PsiMethodImpl) { - if (hardcoded == null && !ignoreInference(listOwner, ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { - ContainerUtil.addIfNotNull(result, getInferredContractAnnotation((PsiMethodImpl) listOwner)); + @Nonnull + @Override + public List findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner) { + listOwner = PsiUtil.preferCompiledElement(listOwner); + List result = new ArrayList<>(); + PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotations(listOwner); + for (PsiAnnotation annotation : fromBytecode) { + if (!ignoreInference(listOwner, annotation.getQualifiedName())) { + result.add(annotation); + } } - if (!ignoreInference(listOwner, myNullabilityManager.getDefaultNotNull()) || - !ignoreInference(listOwner, myNullabilityManager.getDefaultNullable())) { - PsiAnnotation annotation = getInferredNullabilityAnnotation((PsiMethodImpl) listOwner); - if (annotation != null && !ignoreInference(listOwner, annotation.getQualifiedName())) { - result.add(annotation); - } + if (listOwner instanceof PsiMethod) { + PsiAnnotation hardcoded = getHardcodedContractAnnotation((PsiMethod)listOwner); + ContainerUtil.addIfNotNull(result, hardcoded); + if (listOwner instanceof PsiMethodImpl) { + if (hardcoded == null && !ignoreInference(listOwner, ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { + ContainerUtil.addIfNotNull(result, getInferredContractAnnotation((PsiMethodImpl)listOwner)); + } + + if (!ignoreInference(listOwner, myNullabilityManager.getDefaultNotNull()) || + !ignoreInference(listOwner, myNullabilityManager.getDefaultNullable())) { + PsiAnnotation annotation = getInferredNullabilityAnnotation((PsiMethodImpl)listOwner); + if (annotation != null && !ignoreInference(listOwner, annotation.getQualifiedName())) { + result.add(annotation); + } + } + } } - } - } - if (listOwner instanceof PsiParameter && !ignoreInference(listOwner, myNullabilityManager.getDefaultNotNull())) { - ContainerUtil.addIfNotNull(result, getInferredNullabilityAnnotation((PsiParameter) listOwner)); - } + if (listOwner instanceof PsiParameter && !ignoreInference(listOwner, myNullabilityManager.getDefaultNotNull())) { + ContainerUtil.addIfNotNull(result, getInferredNullabilityAnnotation((PsiParameter)listOwner)); + } - ContainerUtil.addIfNotNull(result, getInferredMutabilityAnnotation(listOwner)); + ContainerUtil.addIfNotNull(result, getInferredMutabilityAnnotation(listOwner)); - return result; - } + return result; + } - public static boolean isExperimentalInferredAnnotation(@Nonnull PsiAnnotation annotation) { - return EXPERIMENTAL_INFERRED_ANNOTATIONS.contains(annotation.getQualifiedName()); - } + public static boolean isExperimentalInferredAnnotation(@Nonnull PsiAnnotation annotation) { + return EXPERIMENTAL_INFERRED_ANNOTATIONS.contains(annotation.getQualifiedName()); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapObjectWithOptionalOfNullableFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapObjectWithOptionalOfNullableFix.java index a9b30e7f3a..04ef281c75 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapObjectWithOptionalOfNullableFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapObjectWithOptionalOfNullableFix.java @@ -35,128 +35,143 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.Collection; /** * @author Dmitry Batkovich */ public class WrapObjectWithOptionalOfNullableFix extends MethodArgumentFix implements HighPriorityAction { - public static final ArgumentFixerActionFactory REGISTAR = new MyFixerActionFactory(); - - protected WrapObjectWithOptionalOfNullableFix(final @Nonnull PsiExpressionList list, - final int i, - final @Nonnull PsiType toType, - final @Nonnull ArgumentFixerActionFactory fixerActionFactory) { - super(list, i, toType, fixerActionFactory); - } - - @Nonnull - @Override - public String getText() { - if (myArgList.getExpressionCount() == 1) { - return JavaQuickFixBundle.message("wrap.with.optional.single.parameter.text"); - } else { - return JavaQuickFixBundle.message("wrap.with.optional.parameter.text", myIndex + 1); - } - } - - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file); - } - - public static IntentionAction createFix(@Nullable PsiType type, @Nonnull PsiExpression expression) { - class MyFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction { - protected MyFix(@Nullable PsiElement element) { - super(element); - } - - @Nls - @Nonnull - @Override - public String getFamilyName() { - return JavaQuickFixBundle.message("wrap.with.optional.single.parameter.text"); - } - - @Override - public void invoke(@Nonnull Project project, - @Nonnull PsiFile file, - @Nullable Editor editor, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { - startElement.replace(getModifiedExpression((PsiExpression) getStartElement())); - } - - @Override - public boolean isAvailable(@Nonnull Project project, - @Nonnull PsiFile file, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { - return BaseIntentionAction.canModify(startElement) && - PsiUtil.isLanguageLevel8OrHigher(startElement) && areConvertible(((PsiExpression) startElement).getType(), type); - } - - @Nonnull - @Override - public String getText() { - return getFamilyName(); - } + public static final ArgumentFixerActionFactory REGISTAR = new MyFixerActionFactory(); + + protected WrapObjectWithOptionalOfNullableFix( + final @Nonnull PsiExpressionList list, + final int i, + final @Nonnull PsiType toType, + final @Nonnull ArgumentFixerActionFactory fixerActionFactory + ) { + super(list, i, toType, fixerActionFactory); } - return new MyFix(expression); - } - public static class MyFixerActionFactory extends ArgumentFixerActionFactory { - - @Nullable + @Nonnull @Override - protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException { - return getModifiedExpression(expression); + public String getText() { + if (myArgList.getExpressionCount() == 1) { + return JavaQuickFixBundle.message("wrap.with.optional.single.parameter.text"); + } + else { + return JavaQuickFixBundle.message("wrap.with.optional.parameter.text", myIndex + 1); + } } @Override - public boolean areTypesConvertible(@Nonnull final PsiType exprType, @Nonnull final PsiType parameterType, @Nonnull final PsiElement context) { - return parameterType.isConvertibleFrom(exprType) || areConvertible(exprType, parameterType); + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file); } - @Override - public MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType toType) { - return new WrapObjectWithOptionalOfNullableFix(list, i, toType, this); - } - } - - private static boolean areConvertible(@Nullable PsiType exprType, @Nullable PsiType parameterType) { - if (exprType == null || - !exprType.isValid() || - !(parameterType instanceof PsiClassType) || - !parameterType.isValid()) { - return false; - } - final PsiClassType.ClassResolveResult resolve = ((PsiClassType) parameterType).resolveGenerics(); - final PsiClass resolvedClass = resolve.getElement(); - if (resolvedClass == null || !CommonClassNames.JAVA_UTIL_OPTIONAL.equals(resolvedClass.getQualifiedName())) { - return false; + public static IntentionAction createFix(@Nullable PsiType type, @Nonnull PsiExpression expression) { + class MyFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction { + protected MyFix(@Nullable PsiElement element) { + super(element); + } + + @Nls + @Nonnull + @Override + public String getFamilyName() { + return JavaQuickFixBundle.message("wrap.with.optional.single.parameter.text"); + } + + @Override + public void invoke( + @Nonnull Project project, + @Nonnull PsiFile file, + @Nullable Editor editor, + @Nonnull PsiElement startElement, + @Nonnull PsiElement endElement + ) { + startElement.replace(getModifiedExpression((PsiExpression)getStartElement())); + } + + @Override + public boolean isAvailable( + @Nonnull Project project, + @Nonnull PsiFile file, + @Nonnull PsiElement startElement, + @Nonnull PsiElement endElement + ) { + return BaseIntentionAction.canModify(startElement) && + PsiUtil.isLanguageLevel8OrHigher(startElement) && areConvertible(((PsiExpression)startElement).getType(), type); + } + + @Nonnull + @Override + public String getText() { + return getFamilyName(); + } + } + return new MyFix(expression); } - final Collection values = resolve.getSubstitutor().getSubstitutionMap().values(); - if (values.isEmpty()) { - return true; + public static class MyFixerActionFactory extends ArgumentFixerActionFactory { + + @Nullable + @Override + protected PsiExpression getModifiedArgument( + final PsiExpression expression, + final PsiType toType + ) throws IncorrectOperationException { + return getModifiedExpression(expression); + } + + @Override + public boolean areTypesConvertible( + @Nonnull final PsiType exprType, + @Nonnull final PsiType parameterType, + @Nonnull final PsiElement context + ) { + return parameterType.isConvertibleFrom(exprType) || areConvertible(exprType, parameterType); + } + + @Override + public MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType toType) { + return new WrapObjectWithOptionalOfNullableFix(list, i, toType, this); + } } - if (values.size() > 1) { - return false; + + private static boolean areConvertible(@Nullable PsiType exprType, @Nullable PsiType parameterType) { + if (exprType == null || + !exprType.isValid() || + !(parameterType instanceof PsiClassType) || + !parameterType.isValid()) { + return false; + } + final PsiClassType.ClassResolveResult resolve = ((PsiClassType)parameterType).resolveGenerics(); + final PsiClass resolvedClass = resolve.getElement(); + if (resolvedClass == null || !CommonClassNames.JAVA_UTIL_OPTIONAL.equals(resolvedClass.getQualifiedName())) { + return false; + } + + final Collection values = resolve.getSubstitutor().getSubstitutionMap().values(); + if (values.isEmpty()) { + return true; + } + if (values.size() > 1) { + return false; + } + final PsiType optionalTypeParameter = ContainerUtil.getFirstItem(values); + if (optionalTypeParameter == null) { + return false; + } + return TypeConversionUtil.isAssignable(optionalTypeParameter, exprType); } - final PsiType optionalTypeParameter = ContainerUtil.getFirstItem(values); - if (optionalTypeParameter == null) { - return false; + + @Nonnull + private static PsiExpression getModifiedExpression(PsiExpression expression) { + final Project project = expression.getProject(); + final Nullability nullability = NullabilityUtil.getExpressionNullability(expression, true); + String methodName = nullability == Nullability.NOT_NULL ? "of" : "ofNullable"; + final String newExpressionText = CommonClassNames.JAVA_UTIL_OPTIONAL + "." + methodName + "(" + expression.getText() + ")"; + return JavaPsiFacade.getElementFactory(project).createExpressionFromText(newExpressionText, expression); } - return TypeConversionUtil.isAssignable(optionalTypeParameter, exprType); - } - - @Nonnull - private static PsiExpression getModifiedExpression(PsiExpression expression) { - final Project project = expression.getProject(); - final Nullability nullability = NullabilityUtil.getExpressionNullability(expression, true); - String methodName = nullability == Nullability.NOT_NULL ? "of" : "ofNullable"; - final String newExpressionText = CommonClassNames.JAVA_UTIL_OPTIONAL + "." + methodName + "(" + expression.getText() + ")"; - return JavaPsiFacade.getElementFactory(project).createExpressionFromText(newExpressionText, expression); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/GuessManagerImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/GuessManagerImpl.java index e81f17038f..a034d23395 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/GuessManagerImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/GuessManagerImpl.java @@ -40,568 +40,595 @@ @Singleton @ServiceImpl public final class GuessManagerImpl extends GuessManager { - private final MethodPatternMap myMethodPatternMap = new MethodPatternMap(); - - private final Project myProject; - - @Inject - public GuessManagerImpl(Project project) { - myProject = project; - initMethodPatterns(); - } - - private void initMethodPatterns() { - // Collection - myMethodPatternMap.addPattern(new MethodPattern("add", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("contains", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("remove", 1, 0)); - - // Vector - myMethodPatternMap.addPattern(new MethodPattern("add", 2, 1)); - myMethodPatternMap.addPattern(new MethodPattern("addElement", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("elementAt", 1, -1)); - myMethodPatternMap.addPattern(new MethodPattern("firstElement", 0, -1)); - myMethodPatternMap.addPattern(new MethodPattern("lastElement", 0, -1)); - myMethodPatternMap.addPattern(new MethodPattern("get", 1, -1)); - myMethodPatternMap.addPattern(new MethodPattern("indexOf", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("indexOf", 2, 0)); - myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 2, 0)); - myMethodPatternMap.addPattern(new MethodPattern("insertElementAt", 2, 0)); - myMethodPatternMap.addPattern(new MethodPattern("removeElement", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("set", 2, 1)); - myMethodPatternMap.addPattern(new MethodPattern("setElementAt", 2, 0)); - } - - @Override - @Nonnull - public PsiType[] guessContainerElementType(PsiExpression containerExpr, TextRange rangeToIgnore) { - HashSet typesSet = new HashSet<>(); - - PsiType type = containerExpr.getType(); - PsiType elemType; - if ((elemType = getGenericElementType(type)) != null) { - return new PsiType[]{elemType}; + private final MethodPatternMap myMethodPatternMap = new MethodPatternMap(); + + private final Project myProject; + + @Inject + public GuessManagerImpl(Project project) { + myProject = project; + initMethodPatterns(); + } + + private void initMethodPatterns() { + // Collection + myMethodPatternMap.addPattern(new MethodPattern("add", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("contains", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("remove", 1, 0)); + + // Vector + myMethodPatternMap.addPattern(new MethodPattern("add", 2, 1)); + myMethodPatternMap.addPattern(new MethodPattern("addElement", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("elementAt", 1, -1)); + myMethodPatternMap.addPattern(new MethodPattern("firstElement", 0, -1)); + myMethodPatternMap.addPattern(new MethodPattern("lastElement", 0, -1)); + myMethodPatternMap.addPattern(new MethodPattern("get", 1, -1)); + myMethodPatternMap.addPattern(new MethodPattern("indexOf", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("indexOf", 2, 0)); + myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 2, 0)); + myMethodPatternMap.addPattern(new MethodPattern("insertElementAt", 2, 0)); + myMethodPatternMap.addPattern(new MethodPattern("removeElement", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("set", 2, 1)); + myMethodPatternMap.addPattern(new MethodPattern("setElementAt", 2, 0)); } - if (containerExpr instanceof PsiReferenceExpression) { - PsiElement refElement = ((PsiReferenceExpression)containerExpr).resolve(); - if (refElement instanceof PsiVariable) { + @Override + @Nonnull + public PsiType[] guessContainerElementType(PsiExpression containerExpr, TextRange rangeToIgnore) { + HashSet typesSet = new HashSet<>(); + + PsiType type = containerExpr.getType(); + PsiType elemType; + if ((elemType = getGenericElementType(type)) != null) { + return new PsiType[]{elemType}; + } - PsiFile file = refElement.getContainingFile(); - if (file == null) { - file = containerExpr.getContainingFile(); // implicit variable in jsp + if (containerExpr instanceof PsiReferenceExpression) { + PsiElement refElement = ((PsiReferenceExpression)containerExpr).resolve(); + if (refElement instanceof PsiVariable) { + + PsiFile file = refElement.getContainingFile(); + if (file == null) { + file = containerExpr.getContainingFile(); // implicit variable in jsp + } + HashSet checkedVariables = new HashSet<>(); + addTypesByVariable(typesSet, (PsiVariable)refElement, file, checkedVariables, CHECK_USAGE | CHECK_DOWN, rangeToIgnore); + checkedVariables.clear(); + addTypesByVariable(typesSet, (PsiVariable)refElement, file, checkedVariables, CHECK_UP, rangeToIgnore); + } } - HashSet checkedVariables = new HashSet<>(); - addTypesByVariable(typesSet, (PsiVariable)refElement, file, checkedVariables, CHECK_USAGE | CHECK_DOWN, rangeToIgnore); - checkedVariables.clear(); - addTypesByVariable(typesSet, (PsiVariable)refElement, file, checkedVariables, CHECK_UP, rangeToIgnore); - } + + return typesSet.toArray(PsiType.createArray(typesSet.size())); } - return typesSet.toArray(PsiType.createArray(typesSet.size())); - } - - @Nullable - private static PsiType getGenericElementType(PsiType collectionType) { - if (collectionType instanceof PsiClassType) { - PsiClassType classType = (PsiClassType)collectionType; - PsiType[] parameters = classType.getParameters(); - if (parameters.length == 1) { - return parameters[0]; - } + @Nullable + private static PsiType getGenericElementType(PsiType collectionType) { + if (collectionType instanceof PsiClassType) { + PsiClassType classType = (PsiClassType)collectionType; + PsiType[] parameters = classType.getParameters(); + if (parameters.length == 1) { + return parameters[0]; + } + } + return null; } - return null; - } - - @Override - @Nonnull - public PsiType[] guessTypeToCast(PsiExpression expr) { - LinkedHashSet types = new LinkedHashSet<>(getControlFlowExpressionTypeConjuncts(expr)); - addExprTypesWhenContainerElement(types, expr); - addExprTypesByDerivedClasses(types, expr); - return types.toArray(PsiType.createArray(types.size())); - } - - @Nonnull - @Override - public MultiMap getControlFlowExpressionTypes(@Nonnull PsiExpression forPlace, boolean honorAssignments) { - PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); - if (scope == null) { - PsiFile file = forPlace.getContainingFile(); - if (!(file instanceof PsiCodeFragment)) { - return MultiMap.empty(); - } - scope = file; + + @Override + @Nonnull + public PsiType[] guessTypeToCast(PsiExpression expr) { + LinkedHashSet types = new LinkedHashSet<>(getControlFlowExpressionTypeConjuncts(expr)); + addExprTypesWhenContainerElement(types, expr); + addExprTypesByDerivedClasses(types, expr); + return types.toArray(PsiType.createArray(types.size())); } - DataFlowRunner runner = createRunner(honorAssignments, scope); + @Nonnull + @Override + public MultiMap getControlFlowExpressionTypes(@Nonnull PsiExpression forPlace, boolean honorAssignments) { + PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); + if (scope == null) { + PsiFile file = forPlace.getContainingFile(); + if (!(file instanceof PsiCodeFragment)) { + return MultiMap.empty(); + } + scope = file; + } + + DataFlowRunner runner = createRunner(honorAssignments, scope); - final ExpressionTypeInstructionVisitor visitor = new ExpressionTypeInstructionVisitor(forPlace); - if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) { - return visitor.getResult(); + final ExpressionTypeInstructionVisitor visitor = new ExpressionTypeInstructionVisitor(forPlace); + if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) { + return visitor.getResult(); + } + return MultiMap.empty(); } - return MultiMap.empty(); - } - - @Nullable - private static PsiType getTypeFromDataflow(PsiExpression forPlace, boolean honorAssignments) { - PsiType type = forPlace.getType(); - TypeConstraint initial = type == null ? TypeConstraints.TOP : TypeConstraints.instanceOf(type); - PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); - if (scope == null) { - PsiFile file = forPlace.getContainingFile(); - if (!(file instanceof PsiCodeFragment)) { + + @Nullable + private static PsiType getTypeFromDataflow(PsiExpression forPlace, boolean honorAssignments) { + PsiType type = forPlace.getType(); + TypeConstraint initial = type == null ? TypeConstraints.TOP : TypeConstraints.instanceOf(type); + PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); + if (scope == null) { + PsiFile file = forPlace.getContainingFile(); + if (!(file instanceof PsiCodeFragment)) { + return null; + } + scope = file; + } + + DataFlowRunner runner = createRunner(honorAssignments, scope); + + class Visitor extends CastTrackingVisitor { + TypeConstraint constraint = TypeConstraints.BOTTOM; + + @Override + protected void beforeExpressionPush( + @Nonnull DfaValue value, + @Nonnull PsiExpression expression, + @Nullable TextRange range, + @Nonnull DfaMemoryState state + ) { + if (expression == forPlace && range == null) { + if (!(value instanceof DfaVariableValue) || ((DfaVariableValue)value).isFlushableByCalls()) { + value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression)); + } + constraint = constraint.join(TypeConstraint.fromDfType(state.getDfType(value))); + } + super.beforeExpressionPush(value, expression, range, state); + } + + @Override + boolean isInteresting(@Nonnull DfaValue value, @Nonnull PsiExpression expression) { + return (!(value instanceof DfaVariableValue) || ((DfaVariableValue)value).isFlushableByCalls()) && + ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression, forPlace); + } + } + final Visitor visitor = new Visitor(); + if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) { + return visitor.constraint.meet(initial).getPsiType(scope.getProject()); + } return null; - } - scope = file; } - DataFlowRunner runner = createRunner(honorAssignments, scope); - - class Visitor extends CastTrackingVisitor { - TypeConstraint constraint = TypeConstraints.BOTTOM; - - @Override - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nullable TextRange range, - @Nonnull DfaMemoryState state) { - if (expression == forPlace && range == null) { - if (!(value instanceof DfaVariableValue) || ((DfaVariableValue)value).isFlushableByCalls()) { - value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression)); - } - constraint = constraint.join(TypeConstraint.fromDfType(state.getDfType(value))); - } - super.beforeExpressionPush(value, expression, range, state); - } - - @Override - boolean isInteresting(@Nonnull DfaValue value, @Nonnull PsiExpression expression) { - return (!(value instanceof DfaVariableValue) || ((DfaVariableValue)value).isFlushableByCalls()) && - ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression, forPlace); - } - } - final Visitor visitor = new Visitor(); - if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) { - return visitor.constraint.meet(initial).getPsiType(scope.getProject()); - } - return null; - } - - @Nonnull - private static DataFlowRunner createRunner(boolean honorAssignments, PsiElement scope) { - return honorAssignments ? new DataFlowRunner(scope.getProject()) : new DataFlowRunner(scope.getProject()) { - @Nonnull - @Override - protected DfaMemoryState createMemoryState() { - return new AssignmentFilteringMemoryState(getFactory()); - } - }; - } - - private static PsiElement getTopmostBlock(PsiElement scope) { - assert scope.isValid(); - PsiElement lastScope = scope; - while (true) { - final PsiCodeBlock lastCodeBlock = PsiTreeUtil.getParentOfType(lastScope, PsiCodeBlock.class, true); - if (lastCodeBlock == null) { - break; - } - lastScope = lastCodeBlock; - } - if (lastScope == scope) { - PsiFile file = scope.getContainingFile(); - if (file instanceof PsiCodeFragment) { - return file; - } + @Nonnull + private static DataFlowRunner createRunner(boolean honorAssignments, PsiElement scope) { + return honorAssignments ? new DataFlowRunner(scope.getProject()) : new DataFlowRunner(scope.getProject()) { + @Nonnull + @Override + protected DfaMemoryState createMemoryState() { + return new AssignmentFilteringMemoryState(getFactory()); + } + }; } - return lastScope; - } - private void addExprTypesByDerivedClasses(LinkedHashSet set, PsiExpression expr) { - PsiType type = expr.getType(); - if (!(type instanceof PsiClassType)) { - return; - } - PsiClass refClass = PsiUtil.resolveClassInType(type); - if (refClass == null) { - return; + private static PsiElement getTopmostBlock(PsiElement scope) { + assert scope.isValid(); + PsiElement lastScope = scope; + while (true) { + final PsiCodeBlock lastCodeBlock = PsiTreeUtil.getParentOfType(lastScope, PsiCodeBlock.class, true); + if (lastCodeBlock == null) { + break; + } + lastScope = lastCodeBlock; + } + if (lastScope == scope) { + PsiFile file = scope.getContainingFile(); + if (file instanceof PsiCodeFragment) { + return file; + } + } + return lastScope; } - PsiManager manager = PsiManager.getInstance(myProject); - PsiElementProcessor.CollectElementsWithLimit processor = new PsiElementProcessor.CollectElementsWithLimit<>(5); - ClassInheritorsSearch.search(refClass).forEach(new PsiElementProcessorAdapter<>(processor)); - if (processor.isOverflow()) { - return; - } + private void addExprTypesByDerivedClasses(LinkedHashSet set, PsiExpression expr) { + PsiType type = expr.getType(); + if (!(type instanceof PsiClassType)) { + return; + } + PsiClass refClass = PsiUtil.resolveClassInType(type); + if (refClass == null) { + return; + } - for (PsiClass derivedClass : processor.getCollection()) { - if (derivedClass instanceof PsiAnonymousClass) { - continue; - } - PsiType derivedType = JavaPsiFacade.getElementFactory(manager.getProject()).createType(derivedClass); - set.add(derivedType); - } - } - - private void addExprTypesWhenContainerElement(LinkedHashSet set, PsiExpression expr) { - if (expr instanceof PsiMethodCallExpression) { - PsiMethodCallExpression callExpr = (PsiMethodCallExpression)expr; - PsiReferenceExpression methodExpr = callExpr.getMethodExpression(); - String methodName = methodExpr.getReferenceName(); - MethodPattern pattern = myMethodPatternMap.findPattern(methodName, callExpr.getArgumentList().getExpressionCount()); - if (pattern != null && pattern.parameterIndex < 0/* return value */) { - PsiExpression qualifier = methodExpr.getQualifierExpression(); - if (qualifier != null) { - PsiType[] types = guessContainerElementType(qualifier, null); - for (PsiType type : types) { - if (type instanceof PsiClassType) { - if (((PsiClassType)type).resolve() instanceof PsiAnonymousClass) { + PsiManager manager = PsiManager.getInstance(myProject); + PsiElementProcessor.CollectElementsWithLimit processor = new PsiElementProcessor.CollectElementsWithLimit<>(5); + ClassInheritorsSearch.search(refClass).forEach(new PsiElementProcessorAdapter<>(processor)); + if (processor.isOverflow()) { + return; + } + + for (PsiClass derivedClass : processor.getCollection()) { + if (derivedClass instanceof PsiAnonymousClass) { continue; - } } - set.add(type); - } + PsiType derivedType = JavaPsiFacade.getElementFactory(manager.getProject()).createType(derivedClass); + set.add(derivedType); } - } - } - } - - private static final int CHECK_USAGE = 0x01; - private static final int CHECK_UP = 0x02; - private static final int CHECK_DOWN = 0x04; - - private void addTypesByVariable(HashSet typesSet, - PsiVariable var, - PsiFile scopeFile, - HashSet checkedVariables, - int flags, - TextRange rangeToIgnore) { - if (!checkedVariables.add(var)) { - return; } - //System.out.println("analyzing usages of " + var + " in file " + scopeFile); - SearchScope searchScope = new LocalSearchScope(scopeFile); - if (BitUtil.isSet(flags, CHECK_USAGE) || BitUtil.isSet(flags, CHECK_DOWN)) { - for (PsiReference varRef : ReferencesSearch.search(var, searchScope, false)) { - PsiElement ref = varRef.getElement(); - - if (BitUtil.isSet(flags, CHECK_USAGE)) { - PsiType type = guessElementTypeFromReference(myMethodPatternMap, ref, rangeToIgnore); - if (type != null && !(type instanceof PsiPrimitiveType)) { - typesSet.add(type); - } + private void addExprTypesWhenContainerElement(LinkedHashSet set, PsiExpression expr) { + if (expr instanceof PsiMethodCallExpression) { + PsiMethodCallExpression callExpr = (PsiMethodCallExpression)expr; + PsiReferenceExpression methodExpr = callExpr.getMethodExpression(); + String methodName = methodExpr.getReferenceName(); + MethodPattern pattern = myMethodPatternMap.findPattern(methodName, callExpr.getArgumentList().getExpressionCount()); + if (pattern != null && pattern.parameterIndex < 0/* return value */) { + PsiExpression qualifier = methodExpr.getQualifierExpression(); + if (qualifier != null) { + PsiType[] types = guessContainerElementType(qualifier, null); + for (PsiType type : types) { + if (type instanceof PsiClassType) { + if (((PsiClassType)type).resolve() instanceof PsiAnonymousClass) { + continue; + } + } + set.add(type); + } + } + } } + } - if (BitUtil.isSet(flags, CHECK_DOWN)) { - if (ref.getParent() instanceof PsiExpressionList && ref.getParent().getParent() instanceof PsiMethodCallExpression) { //TODO : new - PsiExpressionList list = (PsiExpressionList)ref.getParent(); - int argIndex = ArrayUtil.indexOf(list.getExpressions(), ref); + private static final int CHECK_USAGE = 0x01; + private static final int CHECK_UP = 0x02; + private static final int CHECK_DOWN = 0x04; + + private void addTypesByVariable( + HashSet typesSet, + PsiVariable var, + PsiFile scopeFile, + HashSet checkedVariables, + int flags, + TextRange rangeToIgnore + ) { + if (!checkedVariables.add(var)) { + return; + } + //System.out.println("analyzing usages of " + var + " in file " + scopeFile); + SearchScope searchScope = new LocalSearchScope(scopeFile); + + if (BitUtil.isSet(flags, CHECK_USAGE) || BitUtil.isSet(flags, CHECK_DOWN)) { + for (PsiReference varRef : ReferencesSearch.search(var, searchScope, false)) { + PsiElement ref = varRef.getElement(); + + if (BitUtil.isSet(flags, CHECK_USAGE)) { + PsiType type = guessElementTypeFromReference(myMethodPatternMap, ref, rangeToIgnore); + if (type != null && !(type instanceof PsiPrimitiveType)) { + typesSet.add(type); + } + } + + if (BitUtil.isSet(flags, CHECK_DOWN)) { + if (ref.getParent() instanceof PsiExpressionList + && ref.getParent().getParent() instanceof PsiMethodCallExpression) { //TODO : new + PsiExpressionList list = (PsiExpressionList)ref.getParent(); + int argIndex = ArrayUtil.indexOf(list.getExpressions(), ref); + + PsiMethodCallExpression methodCall = (PsiMethodCallExpression)list.getParent(); + PsiMethod method = (PsiMethod)methodCall.getMethodExpression().resolve(); + if (method != null) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (argIndex < parameters.length) { + addTypesByVariable( + typesSet, + parameters[argIndex], + method.getContainingFile(), + checkedVariables, + flags | CHECK_USAGE, + rangeToIgnore + ); + } + } + } + } + } + } - PsiMethodCallExpression methodCall = (PsiMethodCallExpression)list.getParent(); - PsiMethod method = (PsiMethod)methodCall.getMethodExpression().resolve(); - if (method != null) { - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (argIndex < parameters.length) { - addTypesByVariable(typesSet, parameters[argIndex], method.getContainingFile(), checkedVariables, flags | CHECK_USAGE, - rangeToIgnore); - } + if (BitUtil.isSet(flags, CHECK_UP)) { + if (var instanceof PsiParameter && var.getParent() instanceof PsiParameterList && var.getParent() + .getParent() instanceof PsiMethod) { + PsiParameterList list = (PsiParameterList)var.getParent(); + PsiParameter[] parameters = list.getParameters(); + int argIndex = -1; + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + if (parameter.equals(var)) { + argIndex = i; + break; + } + } + + PsiMethod method = (PsiMethod)var.getParent().getParent(); + //System.out.println("analyzing usages of " + method + " in file " + scopeFile); + for (PsiReference methodRef : ReferencesSearch.search(method, searchScope, false)) { + PsiElement ref = methodRef.getElement(); + if (ref.getParent() instanceof PsiMethodCallExpression) { + PsiMethodCallExpression methodCall = (PsiMethodCallExpression)ref.getParent(); + PsiExpression[] args = methodCall.getArgumentList().getExpressions(); + if (args.length <= argIndex) { + continue; + } + PsiExpression arg = args[argIndex]; + if (arg instanceof PsiReferenceExpression) { + PsiElement refElement = ((PsiReferenceExpression)arg).resolve(); + if (refElement instanceof PsiVariable) { + addTypesByVariable( + typesSet, + (PsiVariable)refElement, + scopeFile, + checkedVariables, + flags | CHECK_USAGE, + rangeToIgnore + ); + } + } + //TODO : constructor + } + } } - } } - } } - if (BitUtil.isSet(flags, CHECK_UP)) { - if (var instanceof PsiParameter && var.getParent() instanceof PsiParameterList && var.getParent().getParent() instanceof PsiMethod) { - PsiParameterList list = (PsiParameterList)var.getParent(); - PsiParameter[] parameters = list.getParameters(); - int argIndex = -1; - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - if (parameter.equals(var)) { - argIndex = i; - break; - } - } - - PsiMethod method = (PsiMethod)var.getParent().getParent(); - //System.out.println("analyzing usages of " + method + " in file " + scopeFile); - for (PsiReference methodRef : ReferencesSearch.search(method, searchScope, false)) { - PsiElement ref = methodRef.getElement(); - if (ref.getParent() instanceof PsiMethodCallExpression) { - PsiMethodCallExpression methodCall = (PsiMethodCallExpression)ref.getParent(); - PsiExpression[] args = methodCall.getArgumentList().getExpressions(); - if (args.length <= argIndex) { - continue; - } - PsiExpression arg = args[argIndex]; - if (arg instanceof PsiReferenceExpression) { - PsiElement refElement = ((PsiReferenceExpression)arg).resolve(); - if (refElement instanceof PsiVariable) { - addTypesByVariable(typesSet, (PsiVariable)refElement, scopeFile, checkedVariables, flags | CHECK_USAGE, rangeToIgnore); - } - } - //TODO : constructor - } - } - } - } - } - - @Nullable - private static PsiType guessElementTypeFromReference(MethodPatternMap methodPatternMap, - PsiElement ref, - TextRange rangeToIgnore) { - PsiElement refParent = ref.getParent(); - if (refParent instanceof PsiReferenceExpression) { - PsiReferenceExpression parentExpr = (PsiReferenceExpression)refParent; - if (ref.equals(parentExpr.getQualifierExpression()) && parentExpr.getParent() instanceof PsiMethodCallExpression) { - String methodName = parentExpr.getReferenceName(); - PsiMethodCallExpression methodCall = (PsiMethodCallExpression)parentExpr.getParent(); - PsiExpression[] args = methodCall.getArgumentList().getExpressions(); - MethodPattern pattern = methodPatternMap.findPattern(methodName, args.length); - if (pattern != null) { - if (pattern.parameterIndex < 0) { // return value - if (methodCall.getParent() instanceof PsiTypeCastExpression && - (rangeToIgnore == null || !rangeToIgnore.contains(methodCall.getTextRange()))) { - return ((PsiTypeCastExpression)methodCall.getParent()).getType(); - } - } - else { - return args[pattern.parameterIndex].getType(); - } - } - } - } - return null; - } - - @Nonnull - @Override - public List getControlFlowExpressionTypeConjuncts(@Nonnull PsiExpression expr, boolean honorAssignments) { - if (expr.getType() instanceof PsiPrimitiveType) { - return Collections.emptyList(); - } - PsiExpression place = PsiUtil.skipParenthesizedExprDown(expr); - if (place == null) { - return Collections.emptyList(); + @Nullable + private static PsiType guessElementTypeFromReference( + MethodPatternMap methodPatternMap, + PsiElement ref, + TextRange rangeToIgnore + ) { + PsiElement refParent = ref.getParent(); + if (refParent instanceof PsiReferenceExpression) { + PsiReferenceExpression parentExpr = (PsiReferenceExpression)refParent; + if (ref.equals(parentExpr.getQualifierExpression()) && parentExpr.getParent() instanceof PsiMethodCallExpression) { + String methodName = parentExpr.getReferenceName(); + PsiMethodCallExpression methodCall = (PsiMethodCallExpression)parentExpr.getParent(); + PsiExpression[] args = methodCall.getArgumentList().getExpressions(); + MethodPattern pattern = methodPatternMap.findPattern(methodName, args.length); + if (pattern != null) { + if (pattern.parameterIndex < 0) { // return value + if (methodCall.getParent() instanceof PsiTypeCastExpression && + (rangeToIgnore == null || !rangeToIgnore.contains(methodCall.getTextRange()))) { + return ((PsiTypeCastExpression)methodCall.getParent()).getType(); + } + } + else { + return args[pattern.parameterIndex].getType(); + } + } + } + } + return null; } - List result = null; - if (!ControlFlowAnalyzer.inlinerMayInferPreciseType(place)) { - GuessTypeVisitor visitor = tryGuessingTypeWithoutDfa(place, honorAssignments); - if (!visitor.isDfaNeeded()) { - result = visitor.mySpecificType == null ? - Collections.emptyList() : Collections.singletonList(DfaPsiUtil.tryGenerify(expr, visitor.mySpecificType)); - } - } - if (result == null) { - PsiType psiType = getTypeFromDataflow(expr, honorAssignments); - if (psiType instanceof PsiIntersectionType) { - result = ContainerUtil.mapNotNull(((PsiIntersectionType)psiType).getConjuncts(), type -> DfaPsiUtil.tryGenerify(expr, type)); - } - else if (psiType != null) { - result = Collections.singletonList(DfaPsiUtil.tryGenerify(expr, psiType)); - } - else { - result = Collections.emptyList(); - } - } - result = ContainerUtil.filter(result, t -> { - PsiClass typeClass = PsiUtil.resolveClassInType(t); - return typeClass == null || PsiUtil.isAccessible(typeClass, expr, null); - }); - if (result.equals(Collections.singletonList(TypeConversionUtil.erasure(expr.getType())))) { - return Collections.emptyList(); - } - return result; - } - - @Nonnull - private static GuessTypeVisitor tryGuessingTypeWithoutDfa(PsiExpression place, boolean honorAssignments) { - List exprsAndVars = getPotentiallyAffectingElements(place); - GuessTypeVisitor visitor = new GuessTypeVisitor(place, honorAssignments); - for (PsiElement e : exprsAndVars) { - e.accept(visitor); - if (e == place || visitor.isDfaNeeded()) { - break; - } - } - return visitor; - } - - private static List getPotentiallyAffectingElements(PsiExpression place) { - PsiElement topmostBlock = getTopmostBlock(place); - return LanguageCachedValueUtil.getCachedValue(topmostBlock, () -> { - List list = - SyntaxTraverser.psiTraverser(topmostBlock).filter(e -> e instanceof PsiExpression || e instanceof PsiLocalVariable).toList(); - return new CachedValueProvider.Result<>(list, topmostBlock); - }); - } - - private static class GuessTypeVisitor extends JavaElementVisitor { - private static final CallMatcher OBJECT_GET_CLASS = - CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0); - private final @Nonnull - PsiExpression myPlace; - PsiType mySpecificType; - private boolean myNeedDfa; - private boolean myDeclared; - private final boolean myHonorAssignments; - - GuessTypeVisitor(@Nonnull PsiExpression place, boolean honorAssignments) { - myPlace = place; - myHonorAssignments = honorAssignments; - } + @Override + public List getControlFlowExpressionTypeConjuncts(@Nonnull PsiExpression expr, boolean honorAssignments) { + if (expr.getType() instanceof PsiPrimitiveType) { + return Collections.emptyList(); + } + PsiExpression place = PsiUtil.skipParenthesizedExprDown(expr); + if (place == null) { + return Collections.emptyList(); + } - protected void handleAssignment(@Nullable PsiExpression expression) { - if (!myHonorAssignments || expression == null) { - return; - } - PsiType type = expression.getType(); - if (type instanceof PsiPrimitiveType) { - type = ((PsiPrimitiveType)type).getBoxedType(expression); - } - PsiType rawType = type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type; - if (rawType == null || rawType.equals(PsiType.NULL)) { - return; - } - if (mySpecificType == null) { - mySpecificType = rawType; - } - else if (!mySpecificType.equals(rawType)) { - myNeedDfa = true; - } + List result = null; + if (!ControlFlowAnalyzer.inlinerMayInferPreciseType(place)) { + GuessTypeVisitor visitor = tryGuessingTypeWithoutDfa(place, honorAssignments); + if (!visitor.isDfaNeeded()) { + result = visitor.mySpecificType == null ? + Collections.emptyList() : Collections.singletonList(DfaPsiUtil.tryGenerify(expr, visitor.mySpecificType)); + } + } + if (result == null) { + PsiType psiType = getTypeFromDataflow(expr, honorAssignments); + if (psiType instanceof PsiIntersectionType) { + result = + ContainerUtil.mapNotNull(((PsiIntersectionType)psiType).getConjuncts(), type -> DfaPsiUtil.tryGenerify(expr, type)); + } + else if (psiType != null) { + result = Collections.singletonList(DfaPsiUtil.tryGenerify(expr, psiType)); + } + else { + result = Collections.emptyList(); + } + } + result = ContainerUtil.filter(result, t -> { + PsiClass typeClass = PsiUtil.resolveClassInType(t); + return typeClass == null || PsiUtil.isAccessible(typeClass, expr, null); + }); + if (result.equals(Collections.singletonList(TypeConversionUtil.erasure(expr.getType())))) { + return Collections.emptyList(); + } + return result; } - @Override - public void visitAssignmentExpression(PsiAssignmentExpression expression) { - if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getLExpression(), myPlace)) { - handleAssignment(expression.getRExpression()); - } - super.visitAssignmentExpression(expression); + @Nonnull + private static GuessTypeVisitor tryGuessingTypeWithoutDfa(PsiExpression place, boolean honorAssignments) { + List exprsAndVars = getPotentiallyAffectingElements(place); + GuessTypeVisitor visitor = new GuessTypeVisitor(place, honorAssignments); + for (PsiElement e : exprsAndVars) { + e.accept(visitor); + if (e == place || visitor.isDfaNeeded()) { + break; + } + } + return visitor; } - @Override - public void visitLocalVariable(PsiLocalVariable variable) { - if (ExpressionUtils.isReferenceTo(myPlace, variable)) { - myDeclared = true; - handleAssignment(variable.getInitializer()); - } - super.visitLocalVariable(variable); + private static List getPotentiallyAffectingElements(PsiExpression place) { + PsiElement topmostBlock = getTopmostBlock(place); + return LanguageCachedValueUtil.getCachedValue(topmostBlock, () -> { + List list = + SyntaxTraverser.psiTraverser(topmostBlock) + .filter(e -> e instanceof PsiExpression || e instanceof PsiLocalVariable) + .toList(); + return new CachedValueProvider.Result<>(list, topmostBlock); + }); } - @Override - public void visitTypeCastExpression(PsiTypeCastExpression expression) { - PsiExpression operand = expression.getOperand(); - if (operand != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(operand, myPlace)) { - myNeedDfa = true; - } - super.visitTypeCastExpression(expression); - } + private static class GuessTypeVisitor extends JavaElementVisitor { + private static final CallMatcher OBJECT_GET_CLASS = + CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0); + private final + @Nonnull + PsiExpression myPlace; + PsiType mySpecificType; + private boolean myNeedDfa; + private boolean myDeclared; + private final boolean myHonorAssignments; + + GuessTypeVisitor(@Nonnull PsiExpression place, boolean honorAssignments) { + myPlace = place; + myHonorAssignments = honorAssignments; + } - @Override - public void visitMethodCallExpression(PsiMethodCallExpression call) { - if (OBJECT_GET_CLASS.test(call)) { - PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression()); - if (qualifier != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(qualifier, myPlace)) { - myNeedDfa = true; - } - } - super.visitMethodCallExpression(call); - } + protected void handleAssignment(@Nullable PsiExpression expression) { + if (!myHonorAssignments || expression == null) { + return; + } + PsiType type = expression.getType(); + if (type instanceof PsiPrimitiveType) { + type = ((PsiPrimitiveType)type).getBoxedType(expression); + } + PsiType rawType = type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type; + if (rawType == null || rawType.equals(PsiType.NULL)) { + return; + } + if (mySpecificType == null) { + mySpecificType = rawType; + } + else if (!mySpecificType.equals(rawType)) { + myNeedDfa = true; + } + } - @Override - public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { - if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getOperand(), myPlace)) { - myNeedDfa = true; - } - super.visitInstanceOfExpression(expression); - } + @Override + public void visitAssignmentExpression(PsiAssignmentExpression expression) { + if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getLExpression(), myPlace)) { + handleAssignment(expression.getRExpression()); + } + super.visitAssignmentExpression(expression); + } - public boolean isDfaNeeded() { - if (myNeedDfa) { - return true; - } - if (myDeclared || mySpecificType == null) { - return false; - } - PsiType type = myPlace.getType(); - PsiType rawType = type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type; - return !mySpecificType.equals(rawType); - } - } + @Override + public void visitLocalVariable(PsiLocalVariable variable) { + if (ExpressionUtils.isReferenceTo(myPlace, variable)) { + myDeclared = true; + handleAssignment(variable.getInitializer()); + } + super.visitLocalVariable(variable); + } - abstract static class CastTrackingVisitor extends StandardInstructionVisitor { - @Override - public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - DfaValue value = memState.pop(); - memState.push(adjustValue(runner, value, instruction.getCasted())); - return super.visitTypeCast(instruction, runner, memState); - } + @Override + public void visitTypeCastExpression(PsiTypeCastExpression expression) { + PsiExpression operand = expression.getOperand(); + if (operand != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(operand, myPlace)) { + myNeedDfa = true; + } + super.visitTypeCastExpression(expression); + } - @Override - public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - DfaValue dfaRight = memState.pop(); - DfaValue dfaLeft = memState.pop(); - memState.push(adjustValue(runner, dfaLeft, instruction.getLeft())); - memState.push(dfaRight); - return super.visitInstanceof(instruction, runner, memState); - } + @Override + public void visitMethodCallExpression(PsiMethodCallExpression call) { + if (OBJECT_GET_CLASS.test(call)) { + PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression()); + if (qualifier != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(qualifier, myPlace)) { + myNeedDfa = true; + } + } + super.visitMethodCallExpression(call); + } - private DfaValue adjustValue(DataFlowRunner runner, DfaValue value, @Nullable PsiExpression expression) { - if (expression != null && isInteresting(value, expression)) { - value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression)); - } - return value; - } + @Override + public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { + if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getOperand(), myPlace)) { + myNeedDfa = true; + } + super.visitInstanceOfExpression(expression); + } - boolean isInteresting(@Nonnull DfaValue value, @Nonnull PsiExpression expression) { - return true; + public boolean isDfaNeeded() { + if (myNeedDfa) { + return true; + } + if (myDeclared || mySpecificType == null) { + return false; + } + PsiType type = myPlace.getType(); + PsiType rawType = type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type; + return !mySpecificType.equals(rawType); + } } - } - private static final class ExpressionTypeInstructionVisitor extends CastTrackingVisitor { - private final Map myResult = new HashMap<>(); - private final PsiElement myForPlace; + abstract static class CastTrackingVisitor extends StandardInstructionVisitor { + @Override + public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + DfaValue value = memState.pop(); + memState.push(adjustValue(runner, value, instruction.getCasted())); + return super.visitTypeCast(instruction, runner, memState); + } - private ExpressionTypeInstructionVisitor(@Nonnull PsiElement forPlace) { - myForPlace = PsiUtil.skipParenthesizedExprUp(forPlace); - } + @Override + public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + DfaValue dfaRight = memState.pop(); + DfaValue dfaLeft = memState.pop(); + memState.push(adjustValue(runner, dfaLeft, instruction.getLeft())); + memState.push(dfaRight); + return super.visitInstanceof(instruction, runner, memState); + } + + private DfaValue adjustValue(DataFlowRunner runner, DfaValue value, @Nullable PsiExpression expression) { + if (expression != null && isInteresting(value, expression)) { + value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression)); + } + return value; + } - MultiMap getResult() { - MultiMap result = MultiMap.createSet(Maps.newHashMap(ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY)); - Project project = myForPlace.getProject(); - myResult.forEach((value, constraint) -> { - if (value.getDescriptor() instanceof ExpressionVariableDescriptor) { - PsiExpression expression = ((ExpressionVariableDescriptor)value.getDescriptor()).getExpression(); - PsiType type = constraint.getPsiType(project); - if (type instanceof PsiIntersectionType) { - result.putValues(expression, Arrays.asList(((PsiIntersectionType)type).getConjuncts())); - } - else if (type != null) { - result.putValue(expression, type); - } - } - }); - return result; + boolean isInteresting(@Nonnull DfaValue value, @Nonnull PsiExpression expression) { + return true; + } } - @Override - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nullable TextRange range, - @Nonnull DfaMemoryState state) { - if (range == null && myForPlace == expression) { - ((DfaMemoryStateImpl)state).forRecordedVariableTypes((var, dfType) -> { - myResult.merge(var, TypeConstraint.fromDfType(dfType), TypeConstraint::join); - }); - } - super.beforeExpressionPush(value, expression, range, state); + private static final class ExpressionTypeInstructionVisitor extends CastTrackingVisitor { + private final Map myResult = new HashMap<>(); + private final PsiElement myForPlace; + + private ExpressionTypeInstructionVisitor(@Nonnull PsiElement forPlace) { + myForPlace = PsiUtil.skipParenthesizedExprUp(forPlace); + } + + MultiMap getResult() { + MultiMap result = + MultiMap.createSet(Maps.newHashMap(ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY)); + Project project = myForPlace.getProject(); + myResult.forEach((value, constraint) -> { + if (value.getDescriptor() instanceof ExpressionVariableDescriptor) { + PsiExpression expression = ((ExpressionVariableDescriptor)value.getDescriptor()).getExpression(); + PsiType type = constraint.getPsiType(project); + if (type instanceof PsiIntersectionType) { + result.putValues(expression, Arrays.asList(((PsiIntersectionType)type).getConjuncts())); + } + else if (type != null) { + result.putValue(expression, type); + } + } + }); + return result; + } + + @Override + protected void beforeExpressionPush( + @Nonnull DfaValue value, + @Nonnull PsiExpression expression, + @Nullable TextRange range, + @Nonnull DfaMemoryState state + ) { + if (range == null && myForPlace == expression) { + ((DfaMemoryStateImpl)state).forRecordedVariableTypes((var, dfType) -> { + myResult.merge(var, TypeConstraint.fromDfType(dfType), TypeConstraint::join); + }); + } + super.beforeExpressionPush(value, expression, range, state); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInstructionVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInstructionVisitor.java index ee26d4a247..e42a7fa5d1 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInstructionVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInstructionVisitor.java @@ -28,440 +28,471 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import java.util.stream.Stream; import static consulo.util.lang.ObjectUtil.tryCast; final class DataFlowInstructionVisitor extends StandardInstructionVisitor { - private static final Logger LOG = Logger.getInstance(DataFlowInstructionVisitor.class); - private final Map, StateInfo> myStateInfos = new LinkedHashMap<>(); - private final Map myClassCastProblems = new HashMap<>(); - private final Map myRealOperandTypes = new HashMap<>(); - private final Map myFailingCalls = new HashMap<>(); - private final Map myConstantExpressions = new HashMap<>(); - private final Map myOfNullableCalls = new HashMap<>(); - private final Map> myArrayStoreProblems = new HashMap<>(); - private final Map myMethodReferenceResults = new HashMap<>(); - private final Map myOutOfBoundsArrayAccesses = new HashMap<>(); - private final Set myReceiverMutabilityViolation = new HashSet<>(); - private final Set myArgumentMutabilityViolation = new HashSet<>(); - private final Map mySameValueAssigned = new HashMap<>(); - private final Map mySameArguments = new HashMap<>(); - private final Map mySwitchLabelsReachability = new HashMap<>(); - private boolean myAlwaysReturnsNotNull = true; - private final List myEndOfInitializerStates = new ArrayList<>(); - - private static final CallMatcher USELESS_SAME_ARGUMENTS = CallMatcher.anyOf( - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_MATH, "min", "max").parameterCount(2), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_INTEGER, "min", "max").parameterCount(2), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_LONG, "min", "max").parameterCount(2), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_FLOAT, "min", "max").parameterCount(2), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_DOUBLE, "min", "max").parameterCount(2), - CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "replace").parameterCount(2) - ); - - @Override - public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - PsiExpression left = instruction.getLExpression(); - if (left != null && !Boolean.FALSE.equals(mySameValueAssigned.get(left))) { - if (!left.isPhysical()) { - if (LOG.isDebugEnabled()) { - LOG.debug("Non-physical element in assignment instruction: " + left.getParent().getText(), new Throwable()); - } - } else { - DfaValue value = memState.peek(); - DfaValue target = memState.getStackValue(1); - DfType dfType = memState.getDfType(value); - if (target != null && memState.areEqual(value, target) && - !(dfType instanceof DfConstantType && isFloatingZero(((DfConstantType) dfType).getValue())) && - // Reporting strings is skipped because string reassignment might be intentionally used to deduplicate the heap objects - // (we compare strings by contents) - !(TypeUtils.isJavaLangString(left.getType()) && !memState.isNull(value)) && - !isAssignmentToDefaultValueInConstructor(instruction, runner, target)) { - mySameValueAssigned.merge(left, Boolean.TRUE, Boolean::logicalAnd); - } else { - mySameValueAssigned.put(left, Boolean.FALSE); - } - } - } - return super.visitAssign(instruction, runner, memState); - } - - @Override - protected void beforeConditionalJump(ConditionalGotoInstruction instruction, boolean isTrueBranch) { - PsiExpression anchor = instruction.getPsiAnchor(); - if (anchor != null && PsiImplUtil.getSwitchLabel(anchor) != null) { - mySwitchLabelsReachability.merge(anchor, ThreeState.fromBoolean(isTrueBranch), ThreeState::merge); - } - } - - private static boolean isAssignmentToDefaultValueInConstructor(AssignInstruction instruction, DataFlowRunner runner, DfaValue target) { - if (!(target instanceof DfaVariableValue)) { - return false; - } - DfaVariableValue var = (DfaVariableValue) target; - if (!(var.getPsiVariable() instanceof PsiField) || var.getQualifier() == null || - !(var.getQualifier().getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor)) { - return false; - } - - // chained assignment like this.a = this.b = 0; is also supported - PsiExpression rExpression = instruction.getRExpression(); - while (rExpression instanceof PsiAssignmentExpression && - ((PsiAssignmentExpression) rExpression).getOperationTokenType().equals(JavaTokenType.EQ)) { - rExpression = ((PsiAssignmentExpression) rExpression).getRExpression(); - } - DfaValue dest = runner.getFactory().createValue(rExpression); - if (dest == null) { - return false; - } - DfType dfType = dest.getDfType(); - - PsiType type = var.getType(); - boolean isDefaultValue = DfConstantType.isConst(dfType, PsiTypesUtil.getDefaultValue(type)) || - DfConstantType.isConst(dfType, 0) && TypeConversionUtil.isIntegralNumberType(type); - if (!isDefaultValue) { - return false; - } - PsiMethod method = PsiTreeUtil.getParentOfType(rExpression, PsiMethod.class); - return method != null && method.isConstructor(); - } - - // Reporting of floating zero is skipped, because this produces false-positives on the code like - // if(x == -0.0) x = 0.0; - private static boolean isFloatingZero(Object value) { - if (value instanceof Double) { - return ((Double) value).doubleValue() == 0.0; - } - if (value instanceof Float) { - return ((Float) value).floatValue() == 0.0f; - } - return false; - } - - StreamEx sameValueAssignments() { - return StreamEx.ofKeys(mySameValueAssigned, Boolean::booleanValue); - } - - EntryStream pointlessSameArguments() { - return EntryStream.of(mySameArguments).filterValues(ArgResultEquality::hasEquality); - } - - @Override - protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) { - myClassCastProblems.computeIfAbsent(castExpression, e -> new StateInfo()).update(state, castPossible); - } - - StreamEx> problems() { - return StreamEx.ofKeys(myStateInfos, StateInfo::shouldReport); - } - - public Map> getArrayStoreProblems() { - return myArrayStoreProblems; - } - - Map getOfNullableCalls() { - return myOfNullableCalls; - } - - Map getConstantExpressions() { - return EntryStream.of(myConstantExpressions).filterKeys(chunk -> chunk.myRange == null) - .mapKeys(chunk -> chunk.myExpression).toMap(); - } - - Map getConstantExpressionChunks() { - return myConstantExpressions; - } - - Map getMethodReferenceResults() { - return myMethodReferenceResults; - } - - Map getSwitchLabelsReachability() { - return mySwitchLabelsReachability; - } - - EntryStream> getFailingCastExpressions() { - return EntryStream.of(myClassCastProblems).filterValues(StateInfo::shouldReport).mapToValue( - (cast, info) -> Pair.create(info.alwaysFails(), myRealOperandTypes.getOrDefault(cast, TypeConstraints.TOP).getPsiType(cast.getProject()))); - } - - Set getMutabilityViolations(boolean receiver) { - return receiver ? myReceiverMutabilityViolation : myArgumentMutabilityViolation; - } - - public List getEndOfInitializerStates() { - return myEndOfInitializerStates; - } - - Stream outOfBoundsArrayAccesses() { - return StreamEx.ofKeys(myOutOfBoundsArrayAccesses, ThreeState.YES::equals); - } - - StreamEx alwaysFailingCalls() { - return StreamEx.ofKeys(myFailingCalls, v -> v); - } - - boolean isAlwaysReturnsNotNull(Instruction[] instructions) { - return myAlwaysReturnsNotNull && - ContainerUtil.exists(instructions, i -> i instanceof ReturnInstruction && ((ReturnInstruction) i).getAnchor() instanceof PsiReturnStatement); - } - - public boolean isInstanceofRedundant(InstanceofInstruction instruction) { - PsiExpression expression = instruction.getExpression(); - if (expression == null || myUsefulInstanceofs.contains(instruction) || !myReachable.contains(instruction)) { - return false; - } - ConstantResult result = expression instanceof PsiMethodReferenceExpression ? - myMethodReferenceResults.get(expression) : myConstantExpressions.get(new ExpressionChunk(expression, null)); - return result != ConstantResult.TRUE && result != ConstantResult.FALSE; - } - - @Override - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nullable TextRange range, - @Nonnull DfaMemoryState memState) { - if (!expression.isPhysical()) { - Application application = ApplicationManager.getApplication(); - if (application.isEAP() || application.isInternal() || application.isUnitTestMode()) { - throw new IllegalStateException("Non-physical expression is passed"); - } - } - expression.accept(new ExpressionVisitor(value, memState)); - PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); - if (parent instanceof PsiTypeCastExpression) { - TypeConstraint fact = TypeConstraint.fromDfType(memState.getDfType(value)); - myRealOperandTypes.merge((PsiTypeCastExpression) parent, fact, TypeConstraint::join); - } - reportConstantExpressionValue(value, memState, expression, range); - } - - @Override - protected void onMethodCall(@Nonnull DfaValue result, - @Nonnull PsiExpression expression, - @Nonnull DfaCallArguments arguments, - @Nonnull DfaMemoryState memState) { - PsiReferenceExpression reference = USELESS_SAME_ARGUMENTS.getReferenceIfMatched(expression); - if (reference != null) { - ArgResultEquality equality = new ArgResultEquality( - memState.areEqual(arguments.myArguments[0], arguments.myArguments[1]), - memState.areEqual(result, arguments.myArguments[0]), - memState.areEqual(result, arguments.myArguments[1])); - mySameArguments.merge(reference, equality, ArgResultEquality::merge); - } - } - - @Override - protected void beforeMethodReferenceResultPush(@Nonnull DfaValue value, - @Nonnull PsiMethodReferenceExpression methodRef, - @Nonnull DfaMemoryState state) { - if (OptionalUtil.OPTIONAL_OF_NULLABLE.methodReferenceMatches(methodRef)) { - processOfNullableResult(value, state, methodRef.getReferenceNameElement()); - } - PsiMethod method = tryCast(methodRef.resolve(), PsiMethod.class); - if (method != null && JavaMethodContractUtil.isPure(method)) { - List contracts = JavaMethodContractUtil.getMethodContracts(method); - if (contracts.isEmpty() || !contracts.get(0).isTrivial()) { - myMethodReferenceResults.compute(methodRef, (mr, curState) -> ConstantResult.mergeValue(curState, state, value)); - } - } - } - - private void processOfNullableResult(@Nonnull DfaValue value, @Nonnull DfaMemoryState memState, PsiElement anchor) { - DfaValueFactory factory = value.getFactory(); - DfaValue optionalValue = SpecialField.OPTIONAL_VALUE.createValue(factory, value); - ThreeState present; - if (memState.isNull(optionalValue)) { - present = ThreeState.NO; - } else if (memState.isNotNull(optionalValue)) { - present = ThreeState.YES; - } else { - present = ThreeState.UNSURE; - } - myOfNullableCalls.merge(anchor, present, ThreeState::merge); - } - - @Override - protected void processArrayAccess(PsiArrayAccessExpression expression, boolean alwaysOutOfBounds) { - myOutOfBoundsArrayAccesses.merge(expression, ThreeState.fromBoolean(alwaysOutOfBounds), ThreeState::merge); - } - - @Override - protected void processArrayStoreTypeMismatch(PsiAssignmentExpression assignmentExpression, PsiType fromType, PsiType toType) { - if (assignmentExpression != null) { - myArrayStoreProblems.put(assignmentExpression, Pair.create(fromType, toType)); - } - } - - @Override - public DfaInstructionState[] visitEndOfInitializer(EndOfInitializerInstruction instruction, DataFlowRunner runner, DfaMemoryState state) { - if (!instruction.isStatic()) { - myEndOfInitializerStates.add(state.createCopy()); - } - return super.visitEndOfInitializer(instruction, runner, state); - } - - private static boolean hasNonTrivialFailingContracts(PsiCallExpression call) { - List contracts = JavaMethodContractUtil.getMethodCallContracts(call); - return !contracts.isEmpty() && - contracts.stream().anyMatch(contract -> contract.getReturnValue().isFail() && !contract.isTrivial()); - } - - private void reportConstantExpressionValue(DfaValue value, DfaMemoryState memState, PsiExpression expression, TextRange range) { - if (expression instanceof PsiLiteralExpression) { - return; - } - ExpressionChunk chunk = new ExpressionChunk(expression, range); - myConstantExpressions.compute(chunk, (c, curState) -> ConstantResult.mergeValue(curState, memState, value)); - } - - @Override - protected boolean checkNotNullable(DfaMemoryState state, @Nonnull DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem problem) { - if (problem != null && problem.getKind() == NullabilityProblemKind.nullableReturn && !state.isNotNull(value)) { - myAlwaysReturnsNotNull = false; - } - - boolean ok = super.checkNotNullable(state, value, problem); - if (problem == null) { - return ok; - } - StateInfo info = myStateInfos.computeIfAbsent(problem, k -> new StateInfo()); - info.update(state, ok); - return ok; - } - - @Override - protected void reportMutabilityViolation(boolean receiver, @Nonnull PsiElement anchor) { - if (receiver) { - if (anchor instanceof PsiMethodReferenceExpression) { - anchor = ((PsiMethodReferenceExpression) anchor).getReferenceNameElement(); - } else if (anchor instanceof PsiMethodCallExpression) { - anchor = ((PsiMethodCallExpression) anchor).getMethodExpression().getReferenceNameElement(); - } - if (anchor != null) { - myReceiverMutabilityViolation.add(anchor); - } - } else { - myArgumentMutabilityViolation.add(anchor); - } - } - - private static class StateInfo { - boolean ephemeralException; - boolean normalException; - boolean normalOk; - - void update(DfaMemoryState state, boolean ok) { - if (state.isEphemeral()) { - if (!ok) { - ephemeralException = true; - } - } else { - if (ok) { - normalOk = true; - } else { - normalException = true; - } - } + private static final Logger LOG = Logger.getInstance(DataFlowInstructionVisitor.class); + private final Map, StateInfo> myStateInfos = new LinkedHashMap<>(); + private final Map myClassCastProblems = new HashMap<>(); + private final Map myRealOperandTypes = new HashMap<>(); + private final Map myFailingCalls = new HashMap<>(); + private final Map myConstantExpressions = new HashMap<>(); + private final Map myOfNullableCalls = new HashMap<>(); + private final Map> myArrayStoreProblems = new HashMap<>(); + private final Map myMethodReferenceResults = new HashMap<>(); + private final Map myOutOfBoundsArrayAccesses = new HashMap<>(); + private final Set myReceiverMutabilityViolation = new HashSet<>(); + private final Set myArgumentMutabilityViolation = new HashSet<>(); + private final Map mySameValueAssigned = new HashMap<>(); + private final Map mySameArguments = new HashMap<>(); + private final Map mySwitchLabelsReachability = new HashMap<>(); + private boolean myAlwaysReturnsNotNull = true; + private final List myEndOfInitializerStates = new ArrayList<>(); + + private static final CallMatcher USELESS_SAME_ARGUMENTS = CallMatcher.anyOf( + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_MATH, "min", "max").parameterCount(2), + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_INTEGER, "min", "max").parameterCount(2), + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_LONG, "min", "max").parameterCount(2), + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_FLOAT, "min", "max").parameterCount(2), + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_DOUBLE, "min", "max").parameterCount(2), + CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "replace").parameterCount(2) + ); + + @Override + public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + PsiExpression left = instruction.getLExpression(); + if (left != null && !Boolean.FALSE.equals(mySameValueAssigned.get(left))) { + if (!left.isPhysical()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Non-physical element in assignment instruction: " + left.getParent().getText(), new Throwable()); + } + } + else { + DfaValue value = memState.peek(); + DfaValue target = memState.getStackValue(1); + DfType dfType = memState.getDfType(value); + if (target != null && memState.areEqual(value, target) && + !(dfType instanceof DfConstantType && isFloatingZero(((DfConstantType)dfType).getValue())) && + // Reporting strings is skipped because string reassignment might be intentionally used to deduplicate the heap objects + // (we compare strings by contents) + !(TypeUtils.isJavaLangString(left.getType()) && !memState.isNull(value)) && + !isAssignmentToDefaultValueInConstructor(instruction, runner, target)) { + mySameValueAssigned.merge(left, Boolean.TRUE, Boolean::logicalAnd); + } + else { + mySameValueAssigned.put(left, Boolean.FALSE); + } + } + } + return super.visitAssign(instruction, runner, memState); + } + + @Override + protected void beforeConditionalJump(ConditionalGotoInstruction instruction, boolean isTrueBranch) { + PsiExpression anchor = instruction.getPsiAnchor(); + if (anchor != null && PsiImplUtil.getSwitchLabel(anchor) != null) { + mySwitchLabelsReachability.merge(anchor, ThreeState.fromBoolean(isTrueBranch), ThreeState::merge); + } } - boolean shouldReport() { - // non-ephemeral exceptions should be reported - // ephemeral exceptions should also be reported if only ephemeral states have reached a particular problematic instruction - // (e.g. if it's inside "if (var == null)" check after contract method invocation - return normalException || ephemeralException && !normalOk; - } + private static boolean isAssignmentToDefaultValueInConstructor(AssignInstruction instruction, DataFlowRunner runner, DfaValue target) { + if (!(target instanceof DfaVariableValue)) { + return false; + } + DfaVariableValue var = (DfaVariableValue)target; + if (!(var.getPsiVariable() instanceof PsiField) || var.getQualifier() == null || + !(var.getQualifier().getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor)) { + return false; + } - boolean alwaysFails() { - return (normalException || ephemeralException) && !normalOk; + // chained assignment like this.a = this.b = 0; is also supported + PsiExpression rExpression = instruction.getRExpression(); + while (rExpression instanceof PsiAssignmentExpression && + ((PsiAssignmentExpression)rExpression).getOperationTokenType().equals(JavaTokenType.EQ)) { + rExpression = ((PsiAssignmentExpression)rExpression).getRExpression(); + } + DfaValue dest = runner.getFactory().createValue(rExpression); + if (dest == null) { + return false; + } + DfType dfType = dest.getDfType(); + + PsiType type = var.getType(); + boolean isDefaultValue = DfConstantType.isConst(dfType, PsiTypesUtil.getDefaultValue(type)) || + DfConstantType.isConst(dfType, 0) && TypeConversionUtil.isIntegralNumberType(type); + if (!isDefaultValue) { + return false; + } + PsiMethod method = PsiTreeUtil.getParentOfType(rExpression, PsiMethod.class); + return method != null && method.isConstructor(); } - } - private class ExpressionVisitor extends JavaElementVisitor { - private final DfaValue myValue; - private final DfaMemoryState myMemState; + // Reporting of floating zero is skipped, because this produces false-positives on the code like + // if(x == -0.0) x = 0.0; + private static boolean isFloatingZero(Object value) { + if (value instanceof Double) { + return ((Double)value).doubleValue() == 0.0; + } + if (value instanceof Float) { + return ((Float)value).floatValue() == 0.0f; + } + return false; + } - ExpressionVisitor(DfaValue value, DfaMemoryState memState) { - myValue = value; - myMemState = memState; + StreamEx sameValueAssignments() { + return StreamEx.ofKeys(mySameValueAssigned, Boolean::booleanValue); + } + + EntryStream pointlessSameArguments() { + return EntryStream.of(mySameArguments).filterValues(ArgResultEquality::hasEquality); } @Override - public void visitMethodCallExpression(PsiMethodCallExpression call) { - super.visitMethodCallExpression(call); - if (OptionalUtil.OPTIONAL_OF_NULLABLE.test(call)) { - processOfNullableResult(myValue, myMemState, call.getArgumentList().getExpressions()[0]); - } + protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) { + myClassCastProblems.computeIfAbsent(castExpression, e -> new StateInfo()).update(state, castPossible); + } + + StreamEx> problems() { + return StreamEx.ofKeys(myStateInfos, StateInfo::shouldReport); + } + + public Map> getArrayStoreProblems() { + return myArrayStoreProblems; + } + + Map getOfNullableCalls() { + return myOfNullableCalls; + } + + Map getConstantExpressions() { + return EntryStream.of(myConstantExpressions).filterKeys(chunk -> chunk.myRange == null) + .mapKeys(chunk -> chunk.myExpression).toMap(); + } + + Map getConstantExpressionChunks() { + return myConstantExpressions; + } + + Map getMethodReferenceResults() { + return myMethodReferenceResults; + } + + Map getSwitchLabelsReachability() { + return mySwitchLabelsReachability; + } + + EntryStream> getFailingCastExpressions() { + return EntryStream.of(myClassCastProblems).filterValues(StateInfo::shouldReport).mapToValue( + (cast, info) -> Pair.create( + info.alwaysFails(), + myRealOperandTypes.getOrDefault(cast, TypeConstraints.TOP).getPsiType(cast.getProject()) + )); + } + + Set getMutabilityViolations(boolean receiver) { + return receiver ? myReceiverMutabilityViolation : myArgumentMutabilityViolation; + } + + public List getEndOfInitializerStates() { + return myEndOfInitializerStates; + } + + Stream outOfBoundsArrayAccesses() { + return StreamEx.ofKeys(myOutOfBoundsArrayAccesses, ThreeState.YES::equals); + } + + StreamEx alwaysFailingCalls() { + return StreamEx.ofKeys(myFailingCalls, v -> v); + } + + boolean isAlwaysReturnsNotNull(Instruction[] instructions) { + return myAlwaysReturnsNotNull && + ContainerUtil.exists( + instructions, + i -> i instanceof ReturnInstruction && ((ReturnInstruction)i).getAnchor() instanceof PsiReturnStatement + ); + } + + public boolean isInstanceofRedundant(InstanceofInstruction instruction) { + PsiExpression expression = instruction.getExpression(); + if (expression == null || myUsefulInstanceofs.contains(instruction) || !myReachable.contains(instruction)) { + return false; + } + ConstantResult result = expression instanceof PsiMethodReferenceExpression ? + myMethodReferenceResults.get(expression) : myConstantExpressions.get(new ExpressionChunk(expression, null)); + return result != ConstantResult.TRUE && result != ConstantResult.FALSE; } @Override - public void visitCallExpression(PsiCallExpression call) { - super.visitCallExpression(call); - Boolean isFailing = myFailingCalls.get(call); - if (isFailing != null || hasNonTrivialFailingContracts(call)) { - myFailingCalls.put(call, DfaTypeValue.isContractFail(myValue) && !Boolean.FALSE.equals(isFailing)); - } + protected void beforeExpressionPush( + @Nonnull DfaValue value, + @Nonnull PsiExpression expression, + @Nullable TextRange range, + @Nonnull DfaMemoryState memState + ) { + if (!expression.isPhysical()) { + Application application = ApplicationManager.getApplication(); + if (application.isEAP() || application.isInternal() || application.isUnitTestMode()) { + throw new IllegalStateException("Non-physical expression is passed"); + } + } + expression.accept(new ExpressionVisitor(value, memState)); + PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + if (parent instanceof PsiTypeCastExpression) { + TypeConstraint fact = TypeConstraint.fromDfType(memState.getDfType(value)); + myRealOperandTypes.merge((PsiTypeCastExpression)parent, fact, TypeConstraint::join); + } + reportConstantExpressionValue(value, memState, expression, range); } - } - static class ExpressionChunk { - final - @Nonnull - PsiExpression myExpression; - final - @Nullable - TextRange myRange; + @Override + protected void onMethodCall( + @Nonnull DfaValue result, + @Nonnull PsiExpression expression, + @Nonnull DfaCallArguments arguments, + @Nonnull DfaMemoryState memState + ) { + PsiReferenceExpression reference = USELESS_SAME_ARGUMENTS.getReferenceIfMatched(expression); + if (reference != null) { + ArgResultEquality equality = new ArgResultEquality( + memState.areEqual(arguments.myArguments[0], arguments.myArguments[1]), + memState.areEqual(result, arguments.myArguments[0]), + memState.areEqual(result, arguments.myArguments[1]) + ); + mySameArguments.merge(reference, equality, ArgResultEquality::merge); + } + } - ExpressionChunk(@Nonnull PsiExpression expression, @Nullable TextRange range) { - myExpression = expression; - myRange = range; + @Override + protected void beforeMethodReferenceResultPush( + @Nonnull DfaValue value, + @Nonnull PsiMethodReferenceExpression methodRef, + @Nonnull DfaMemoryState state + ) { + if (OptionalUtil.OPTIONAL_OF_NULLABLE.methodReferenceMatches(methodRef)) { + processOfNullableResult(value, state, methodRef.getReferenceNameElement()); + } + PsiMethod method = tryCast(methodRef.resolve(), PsiMethod.class); + if (method != null && JavaMethodContractUtil.isPure(method)) { + List contracts = JavaMethodContractUtil.getMethodContracts(method); + if (contracts.isEmpty() || !contracts.get(0).isTrivial()) { + myMethodReferenceResults.compute(methodRef, (mr, curState) -> ConstantResult.mergeValue(curState, state, value)); + } + } + } + + private void processOfNullableResult(@Nonnull DfaValue value, @Nonnull DfaMemoryState memState, PsiElement anchor) { + DfaValueFactory factory = value.getFactory(); + DfaValue optionalValue = SpecialField.OPTIONAL_VALUE.createValue(factory, value); + ThreeState present; + if (memState.isNull(optionalValue)) { + present = ThreeState.NO; + } + else if (memState.isNotNull(optionalValue)) { + present = ThreeState.YES; + } + else { + present = ThreeState.UNSURE; + } + myOfNullableCalls.merge(anchor, present, ThreeState::merge); } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ExpressionChunk chunk = (ExpressionChunk) o; - return myExpression.equals(chunk.myExpression) && - Objects.equals(myRange, chunk.myRange); + protected void processArrayAccess(PsiArrayAccessExpression expression, boolean alwaysOutOfBounds) { + myOutOfBoundsArrayAccesses.merge(expression, ThreeState.fromBoolean(alwaysOutOfBounds), ThreeState::merge); } @Override - public int hashCode() { - return 31 * myExpression.hashCode() + Objects.hashCode(myRange); + protected void processArrayStoreTypeMismatch(PsiAssignmentExpression assignmentExpression, PsiType fromType, PsiType toType) { + if (assignmentExpression != null) { + myArrayStoreProblems.put(assignmentExpression, Pair.create(fromType, toType)); + } } @Override - public String toString() { - String text = myExpression.getText(); - return myRange == null ? text : myRange.substring(text); + public DfaInstructionState[] visitEndOfInitializer( + EndOfInitializerInstruction instruction, + DataFlowRunner runner, + DfaMemoryState state + ) { + if (!instruction.isStatic()) { + myEndOfInitializerStates.add(state.createCopy()); + } + return super.visitEndOfInitializer(instruction, runner, state); } - } - static class ArgResultEquality { - boolean argsEqual; - boolean firstArgEqualToResult; - boolean secondArgEqualToResult; + private static boolean hasNonTrivialFailingContracts(PsiCallExpression call) { + List contracts = JavaMethodContractUtil.getMethodCallContracts(call); + return !contracts.isEmpty() && + contracts.stream().anyMatch(contract -> contract.getReturnValue().isFail() && !contract.isTrivial()); + } - ArgResultEquality(boolean argsEqual, boolean firstArgEqualToResult, boolean secondArgEqualToResult) { - this.argsEqual = argsEqual; - this.firstArgEqualToResult = firstArgEqualToResult; - this.secondArgEqualToResult = secondArgEqualToResult; + private void reportConstantExpressionValue(DfaValue value, DfaMemoryState memState, PsiExpression expression, TextRange range) { + if (expression instanceof PsiLiteralExpression) { + return; + } + ExpressionChunk chunk = new ExpressionChunk(expression, range); + myConstantExpressions.compute(chunk, (c, curState) -> ConstantResult.mergeValue(curState, memState, value)); } - ArgResultEquality merge(ArgResultEquality other) { - return new ArgResultEquality(argsEqual && other.argsEqual, firstArgEqualToResult && other.firstArgEqualToResult, - secondArgEqualToResult && other.secondArgEqualToResult); + @Override + protected boolean checkNotNullable( + DfaMemoryState state, + @Nonnull DfaValue value, + @Nullable NullabilityProblemKind.NullabilityProblem problem + ) { + if (problem != null && problem.getKind() == NullabilityProblemKind.nullableReturn && !state.isNotNull(value)) { + myAlwaysReturnsNotNull = false; + } + + boolean ok = super.checkNotNullable(state, value, problem); + if (problem == null) { + return ok; + } + StateInfo info = myStateInfos.computeIfAbsent(problem, k -> new StateInfo()); + info.update(state, ok); + return ok; } - boolean hasEquality() { - return argsEqual || firstArgEqualToResult || secondArgEqualToResult; + @Override + protected void reportMutabilityViolation(boolean receiver, @Nonnull PsiElement anchor) { + if (receiver) { + if (anchor instanceof PsiMethodReferenceExpression) { + anchor = ((PsiMethodReferenceExpression)anchor).getReferenceNameElement(); + } + else if (anchor instanceof PsiMethodCallExpression) { + anchor = ((PsiMethodCallExpression)anchor).getMethodExpression().getReferenceNameElement(); + } + if (anchor != null) { + myReceiverMutabilityViolation.add(anchor); + } + } + else { + myArgumentMutabilityViolation.add(anchor); + } + } + + private static class StateInfo { + boolean ephemeralException; + boolean normalException; + boolean normalOk; + + void update(DfaMemoryState state, boolean ok) { + if (state.isEphemeral()) { + if (!ok) { + ephemeralException = true; + } + } + else if (ok) { + normalOk = true; + } + else { + normalException = true; + } + } + + boolean shouldReport() { + // non-ephemeral exceptions should be reported + // ephemeral exceptions should also be reported if only ephemeral states have reached a particular problematic instruction + // (e.g. if it's inside "if (var == null)" check after contract method invocation + return normalException || ephemeralException && !normalOk; + } + + boolean alwaysFails() { + return (normalException || ephemeralException) && !normalOk; + } + } + + private class ExpressionVisitor extends JavaElementVisitor { + private final DfaValue myValue; + private final DfaMemoryState myMemState; + + ExpressionVisitor(DfaValue value, DfaMemoryState memState) { + myValue = value; + myMemState = memState; + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression call) { + super.visitMethodCallExpression(call); + if (OptionalUtil.OPTIONAL_OF_NULLABLE.test(call)) { + processOfNullableResult(myValue, myMemState, call.getArgumentList().getExpressions()[0]); + } + } + + @Override + public void visitCallExpression(PsiCallExpression call) { + super.visitCallExpression(call); + Boolean isFailing = myFailingCalls.get(call); + if (isFailing != null || hasNonTrivialFailingContracts(call)) { + myFailingCalls.put(call, DfaTypeValue.isContractFail(myValue) && !Boolean.FALSE.equals(isFailing)); + } + } + } + + static class ExpressionChunk { + final + @Nonnull + PsiExpression myExpression; + final + @Nullable + TextRange myRange; + + ExpressionChunk(@Nonnull PsiExpression expression, @Nullable TextRange range) { + myExpression = expression; + myRange = range; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExpressionChunk chunk = (ExpressionChunk)o; + return myExpression.equals(chunk.myExpression) && + Objects.equals(myRange, chunk.myRange); + } + + @Override + public int hashCode() { + return 31 * myExpression.hashCode() + Objects.hashCode(myRange); + } + + @Override + public String toString() { + String text = myExpression.getText(); + return myRange == null ? text : myRange.substring(text); + } + } + + static class ArgResultEquality { + boolean argsEqual; + boolean firstArgEqualToResult; + boolean secondArgEqualToResult; + + ArgResultEquality(boolean argsEqual, boolean firstArgEqualToResult, boolean secondArgEqualToResult) { + this.argsEqual = argsEqual; + this.firstArgEqualToResult = firstArgEqualToResult; + this.secondArgEqualToResult = secondArgEqualToResult; + } + + ArgResultEquality merge(ArgResultEquality other) { + return new ArgResultEquality( + argsEqual && other.argsEqual, + firstArgEqualToResult && other.firstArgEqualToResult, + secondArgEqualToResult && other.secondArgEqualToResult + ); + } + + boolean hasEquality() { + return argsEqual || firstArgEqualToResult || secondArgEqualToResult; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaOptionalSupport.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaOptionalSupport.java index 3250971174..ba6a165464 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaOptionalSupport.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaOptionalSupport.java @@ -22,92 +22,94 @@ */ public final class DfaOptionalSupport { - @Nullable - public static LocalQuickFix registerReplaceOptionalOfWithOfNullableFix(@Nonnull PsiExpression qualifier) { - final PsiMethodCallExpression call = findCallExpression(qualifier); - final PsiMethod method = call == null ? null : call.resolveMethod(); - final PsiClass containingClass = method == null ? null : method.getContainingClass(); - if (containingClass != null && "of".equals(method.getName())) { - final String qualifiedName = containingClass.getQualifiedName(); - if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qualifiedName)) { - return new ReplaceOptionalCallFix("ofNullable", false); - } - if (OptionalUtil.GUAVA_OPTIONAL.equals(qualifiedName)) { - return new ReplaceOptionalCallFix("fromNullable", false); - } + @Nullable + public static LocalQuickFix registerReplaceOptionalOfWithOfNullableFix(@Nonnull PsiExpression qualifier) { + final PsiMethodCallExpression call = findCallExpression(qualifier); + final PsiMethod method = call == null ? null : call.resolveMethod(); + final PsiClass containingClass = method == null ? null : method.getContainingClass(); + if (containingClass != null && "of".equals(method.getName())) { + final String qualifiedName = containingClass.getQualifiedName(); + if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qualifiedName)) { + return new ReplaceOptionalCallFix("ofNullable", false); + } + if (OptionalUtil.GUAVA_OPTIONAL.equals(qualifiedName)) { + return new ReplaceOptionalCallFix("fromNullable", false); + } + } + return null; } - return null; - } - private static PsiMethodCallExpression findCallExpression(@Nonnull PsiElement anchor) { - final PsiElement argList = PsiUtil.skipParenthesizedExprUp(anchor).getParent(); - if (argList instanceof PsiExpressionList) { - final PsiElement parent = argList.getParent(); - if (parent instanceof PsiMethodCallExpression) { - return (PsiMethodCallExpression) parent; - } + private static PsiMethodCallExpression findCallExpression(@Nonnull PsiElement anchor) { + final PsiElement argList = PsiUtil.skipParenthesizedExprUp(anchor).getParent(); + if (argList instanceof PsiExpressionList) { + final PsiElement parent = argList.getParent(); + if (parent instanceof PsiMethodCallExpression) { + return (PsiMethodCallExpression)parent; + } + } + return null; } - return null; - } - - @Nullable - public static LocalQuickFix createReplaceOptionalOfNullableWithEmptyFix(@Nonnull PsiElement anchor) { - final PsiMethodCallExpression parent = findCallExpression(anchor); - if (parent == null) - return null; - boolean jdkOptional = OptionalUtil.JDK_OPTIONAL_OF_NULLABLE.test(parent); - return new ReplaceOptionalCallFix(jdkOptional ? "empty" : "absent", true); - } - - @Nullable - public static LocalQuickFix createReplaceOptionalOfNullableWithOfFix(@Nonnull PsiElement anchor) { - final PsiMethodCallExpression parent = findCallExpression(anchor); - if (parent == null) - return null; - return new ReplaceOptionalCallFix("of", false); - } - /** - * Creates a DfType which represents present or absent optional (non-null) - * - * @param present whether the value should be present - * @return a DfType representing an Optional - */ - @Nonnull - public static DfType getOptionalValue(boolean present) { - DfType valueType = present ? DfTypes.NOT_NULL_OBJECT : DfTypes.NULL; - return SpecialField.OPTIONAL_VALUE.asDfType(valueType); - } - - private static class ReplaceOptionalCallFix implements LocalQuickFix { - private final String myTargetMethodName; - private final boolean myClearArguments; + @Nullable + public static LocalQuickFix createReplaceOptionalOfNullableWithEmptyFix(@Nonnull PsiElement anchor) { + final PsiMethodCallExpression parent = findCallExpression(anchor); + if (parent == null) { + return null; + } + boolean jdkOptional = OptionalUtil.JDK_OPTIONAL_OF_NULLABLE.test(parent); + return new ReplaceOptionalCallFix(jdkOptional ? "empty" : "absent", true); + } - ReplaceOptionalCallFix(final String targetMethodName, boolean clearArguments) { - myTargetMethodName = targetMethodName; - myClearArguments = clearArguments; + @Nullable + public static LocalQuickFix createReplaceOptionalOfNullableWithOfFix(@Nonnull PsiElement anchor) { + final PsiMethodCallExpression parent = findCallExpression(anchor); + if (parent == null) { + return null; + } + return new ReplaceOptionalCallFix("of", false); } + /** + * Creates a DfType which represents present or absent optional (non-null) + * + * @param present whether the value should be present + * @return a DfType representing an Optional + */ @Nonnull - @Override - public String getFamilyName() { - return CommonQuickFixBundle.message("fix.replace.with.x", "." + myTargetMethodName + "()"); + public static DfType getOptionalValue(boolean present) { + DfType valueType = present ? DfTypes.NOT_NULL_OBJECT : DfTypes.NULL; + return SpecialField.OPTIONAL_VALUE.asDfType(valueType); } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiMethodCallExpression - methodCallExpression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); - if (methodCallExpression != null) { - ExpressionUtils.bindCallTo(methodCallExpression, myTargetMethodName); - if (myClearArguments) { - PsiExpressionList argList = methodCallExpression.getArgumentList(); - PsiExpression[] args = argList.getExpressions(); - if (args.length > 0) { - argList.deleteChildRange(args[0], args[args.length - 1]); - } + private static class ReplaceOptionalCallFix implements LocalQuickFix { + private final String myTargetMethodName; + private final boolean myClearArguments; + + ReplaceOptionalCallFix(final String targetMethodName, boolean clearArguments) { + myTargetMethodName = targetMethodName; + myClearArguments = clearArguments; + } + + @Nonnull + @Override + public String getFamilyName() { + return CommonQuickFixBundle.message("fix.replace.with.x", "." + myTargetMethodName + "()"); + } + + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + final PsiMethodCallExpression + methodCallExpression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); + if (methodCallExpression != null) { + ExpressionUtils.bindCallTo(methodCallExpression, myTargetMethodName); + if (myClearArguments) { + PsiExpressionList argList = methodCallExpression.getArgumentList(); + PsiExpression[] args = argList.getExpressions(); + if (args.length > 0) { + argList.deleteChildRange(args[0], args[args.length - 1]); + } + } + } } - } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraints.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraints.java index bc8e46ac2d..3ddd4dc894 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraints.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraints.java @@ -12,603 +12,604 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; public final class TypeConstraints { - /** - * Top constraint (no restriction; any non-primitive value satisfies this) - */ - public static final TypeConstraint TOP = new TypeConstraint() { - @Nonnull - @Override - public TypeConstraint join(@Nonnull TypeConstraint other) { - return this; - } + /** + * Top constraint (no restriction; any non-primitive value satisfies this) + */ + public static final TypeConstraint TOP = new TypeConstraint() { + @Nonnull + @Override + public TypeConstraint join(@Nonnull TypeConstraint other) { + return this; + } - @Nonnull - @Override - public TypeConstraint meet(@Nonnull TypeConstraint other) { - return other; - } + @Nonnull + @Override + public TypeConstraint meet(@Nonnull TypeConstraint other) { + return other; + } - @Override - public boolean isSuperConstraintOf(@Nonnull TypeConstraint other) { - return true; - } + @Override + public boolean isSuperConstraintOf(@Nonnull TypeConstraint other) { + return true; + } - @Override - public TypeConstraint tryNegate() { - return BOTTOM; - } + @Override + public TypeConstraint tryNegate() { + return BOTTOM; + } - @Override - public String toString() { - return ""; - } - }; - /** - * Bottom constraint (no actual type satisfies this) - */ - public static final TypeConstraint BOTTOM = new TypeConstraint() { - @Nonnull - @Override - public TypeConstraint join(@Nonnull TypeConstraint other) { - return other; - } + @Override + public String toString() { + return ""; + } + }; + /** + * Bottom constraint (no actual type satisfies this) + */ + public static final TypeConstraint BOTTOM = new TypeConstraint() { + @Nonnull + @Override + public TypeConstraint join(@Nonnull TypeConstraint other) { + return other; + } - @Nonnull - @Override - public TypeConstraint meet(@Nonnull TypeConstraint other) { - return this; - } + @Nonnull + @Override + public TypeConstraint meet(@Nonnull TypeConstraint other) { + return this; + } - @Override - public boolean isSuperConstraintOf(@Nonnull TypeConstraint other) { - return other == this; - } + @Override + public boolean isSuperConstraintOf(@Nonnull TypeConstraint other) { + return other == this; + } - @Override - public TypeConstraint tryNegate() { - return TOP; - } + @Override + public TypeConstraint tryNegate() { + return TOP; + } - @Override - public String toString() { - return ""; - } - }; - - /** - * Exactly java.lang.Object class - */ - public static final TypeConstraint.Exact EXACTLY_OBJECT = new TypeConstraint.Exact() { - @Override - public StreamEx superTypes() { - return StreamEx.empty(); - } + @Override + public String toString() { + return ""; + } + }; + + /** + * Exactly java.lang.Object class + */ + public static final TypeConstraint.Exact EXACTLY_OBJECT = new TypeConstraint.Exact() { + @Override + public StreamEx superTypes() { + return StreamEx.empty(); + } - @Override - public boolean isFinal() { - return false; - } + @Override + public boolean isFinal() { + return false; + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - return true; - } + @Override + public boolean isAssignableFrom(@Nonnull Exact other) { + return true; + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - return true; - } + @Override + public boolean isConvertibleFrom(@Nonnull Exact other) { + return true; + } - @Nonnull - @Override - public TypeConstraint instanceOf() { - return TOP; - } + @Nonnull + @Override + public TypeConstraint instanceOf() { + return TOP; + } - @Nonnull - @Override - public TypeConstraint notInstanceOf() { - return BOTTOM; - } + @Nonnull + @Override + public TypeConstraint notInstanceOf() { + return BOTTOM; + } - @Override - public String toString() { - return CommonClassNames.JAVA_LANG_OBJECT; - } + @Override + public String toString() { + return CommonClassNames.JAVA_LANG_OBJECT; + } - @Override - public PsiType getPsiType(Project project) { - return JavaPsiFacade.getElementFactory(project).createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT); - } - }; - - @Nullable - private static TypeConstraint.Exact createExact(@Nonnull PsiType type) { - if (type instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType) type).getComponentType(); - if (componentType instanceof PsiPrimitiveType) { - for (PrimitiveArray p : PrimitiveArray.values()) { - if (p.getType().equals(componentType)) { - return p; - } + @Override + public PsiType getPsiType(Project project) { + return JavaPsiFacade.getElementFactory(project).createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT); + } + }; + + @Nullable + private static TypeConstraint.Exact createExact(@Nonnull PsiType type) { + if (type instanceof PsiArrayType) { + PsiType componentType = ((PsiArrayType)type).getComponentType(); + if (componentType instanceof PsiPrimitiveType) { + for (PrimitiveArray p : PrimitiveArray.values()) { + if (p.getType().equals(componentType)) { + return p; + } + } + return null; + } + TypeConstraint.Exact componentConstraint = createExact(componentType); + return componentConstraint == null ? null : new ExactArray(componentConstraint); + } + if (type instanceof PsiClassType) { + PsiClass psiClass = ((PsiClassType)type).resolve(); + if (psiClass == null) { + return new Unresolved(type.getCanonicalText()); + } + if (!(psiClass instanceof PsiTypeParameter)) { + return exactClass(psiClass); + } } return null; - } - TypeConstraint.Exact componentConstraint = createExact(componentType); - return componentConstraint == null ? null : new ExactArray(componentConstraint); - } - if (type instanceof PsiClassType) { - PsiClass psiClass = ((PsiClassType) type).resolve(); - if (psiClass == null) { - return new Unresolved(type.getCanonicalText()); - } - if (!(psiClass instanceof PsiTypeParameter)) { - return exactClass(psiClass); - } - } - return null; - } - - /** - * @param type PsiType - * @return a constraint for the object that has exactly given PsiType; - * {@link #BOTTOM} if the object of given type cannot be instantiated. - */ - @Nonnull - @Contract(pure = true) - public static TypeConstraint exact(@Nonnull PsiType type) { - type = normalizeType(type); - TypeConstraint.Exact exact = createExact(type); - if (exact != null && exact.canBeInstantiated()) { - return exact; - } - return BOTTOM; - } - - /** - * @param type PsiType - * @return a constraint for the object whose type is the supplied type or any subtype - */ - @Nonnull - @Contract(pure = true) - public static TypeConstraint instanceOf(@Nonnull PsiType type) { - if (type instanceof PsiLambdaExpressionType || type instanceof PsiMethodReferenceType) { - return TOP; } - type = normalizeType(type); - if (type instanceof PsiDisjunctionType) { - type = ((PsiDisjunctionType) type).getLeastUpperBound(); + + /** + * @param type PsiType + * @return a constraint for the object that has exactly given PsiType; + * {@link #BOTTOM} if the object of given type cannot be instantiated. + */ + @Nonnull + @Contract(pure = true) + public static TypeConstraint exact(@Nonnull PsiType type) { + type = normalizeType(type); + TypeConstraint.Exact exact = createExact(type); + if (exact != null && exact.canBeInstantiated()) { + return exact; + } + return BOTTOM; } - if (type instanceof PsiIntersectionType) { - PsiType[] conjuncts = ((PsiIntersectionType) type).getConjuncts(); - TypeConstraint result = TOP; - for (PsiType conjunct : conjuncts) { - TypeConstraint.Exact exact = createExact(conjunct); + + /** + * @param type PsiType + * @return a constraint for the object whose type is the supplied type or any subtype + */ + @Nonnull + @Contract(pure = true) + public static TypeConstraint instanceOf(@Nonnull PsiType type) { + if (type instanceof PsiLambdaExpressionType || type instanceof PsiMethodReferenceType) { + return TOP; + } + type = normalizeType(type); + if (type instanceof PsiDisjunctionType) { + type = ((PsiDisjunctionType)type).getLeastUpperBound(); + } + if (type instanceof PsiIntersectionType) { + PsiType[] conjuncts = ((PsiIntersectionType)type).getConjuncts(); + TypeConstraint result = TOP; + for (PsiType conjunct : conjuncts) { + TypeConstraint.Exact exact = createExact(conjunct); + if (exact == null) { + return new Unresolved(type.getCanonicalText()).instanceOf(); + } + result = result.meet(exact.instanceOf()); + } + return result; + } + TypeConstraint.Exact exact = createExact(type); if (exact == null) { - return new Unresolved(type.getCanonicalText()).instanceOf(); + return new Unresolved(type.getCanonicalText()).instanceOf(); } - result = result.meet(exact.instanceOf()); - } - return result; + return exact.instanceOf(); } - TypeConstraint.Exact exact = createExact(type); - if (exact == null) { - return new Unresolved(type.getCanonicalText()).instanceOf(); - } - return exact.instanceOf(); - } - @Nonnull - private static PsiType normalizeType(@Nonnull PsiType psiType) { - if (psiType instanceof PsiArrayType) { - return PsiTypesUtil.createArrayType(normalizeType(psiType.getDeepComponentType()), psiType.getArrayDimensions()); - } - if (psiType instanceof PsiWildcardType) { - return normalizeType(((PsiWildcardType) psiType).getExtendsBound()); - } - if (psiType instanceof PsiCapturedWildcardType) { - return normalizeType(((PsiCapturedWildcardType) psiType).getUpperBound()); - } - if (psiType instanceof PsiIntersectionType) { - PsiType[] types = - StreamEx.of(((PsiIntersectionType) psiType).getConjuncts()).map(TypeConstraints::normalizeType).toArray(PsiType.EMPTY_ARRAY); - if (types.length > 0) { - return PsiIntersectionType.createIntersection(true, types); - } - } - if (psiType instanceof PsiClassType) { - return normalizeClassType((PsiClassType) psiType, new HashSet<>()); - } - return psiType; - } - - @Nonnull - private static PsiType normalizeClassType(@Nonnull PsiClassType psiType, Set processed) { - PsiClass aClass = psiType.resolve(); - if (aClass instanceof PsiTypeParameter) { - PsiClassType[] types = aClass.getExtendsListTypes(); - List result = new ArrayList<>(); - for (PsiClassType type : types) { - PsiClass resolved = type.resolve(); - if (resolved != null && processed.add(resolved)) { - PsiClassType classType = JavaPsiFacade.getElementFactory(aClass.getProject()).createType(resolved); - result.add(normalizeClassType(classType, processed)); - } - } - if (!result.isEmpty()) { - return PsiIntersectionType.createIntersection(true, result.toArray(PsiType.EMPTY_ARRAY)); - } - return PsiType.getJavaLangObject(aClass.getManager(), aClass.getResolveScope()); - } - return psiType.rawType(); - } - - @Nonnull - private static TypeConstraint.Exact exactClass(@Nonnull PsiClass psiClass) { - String name = psiClass.getQualifiedName(); - if (name != null) { - switch (name) { - case CommonClassNames.JAVA_LANG_OBJECT: - return EXACTLY_OBJECT; - case CommonClassNames.JAVA_LANG_CLONEABLE: - return ArraySuperInterface.CLONEABLE; - case CommonClassNames.JAVA_IO_SERIALIZABLE: - return ArraySuperInterface.SERIALIZABLE; - } - } - return new ExactClass(psiClass); - } - - private enum PrimitiveArray implements TypeConstraint.Exact { - BOOLEAN(PsiType.BOOLEAN), - INT(PsiType.INT), - BYTE(PsiType.BYTE), - SHORT(PsiType.SHORT), - LONG(PsiType.LONG), - CHAR(PsiType.CHAR), - FLOAT(PsiType.FLOAT), - DOUBLE(PsiType.DOUBLE); - private final PsiPrimitiveType myType; - - PrimitiveArray(PsiPrimitiveType type) { - myType = type; + @Nonnull + private static PsiType normalizeType(@Nonnull PsiType psiType) { + if (psiType instanceof PsiArrayType) { + return PsiTypesUtil.createArrayType(normalizeType(psiType.getDeepComponentType()), psiType.getArrayDimensions()); + } + if (psiType instanceof PsiWildcardType) { + return normalizeType(((PsiWildcardType)psiType).getExtendsBound()); + } + if (psiType instanceof PsiCapturedWildcardType) { + return normalizeType(((PsiCapturedWildcardType)psiType).getUpperBound()); + } + if (psiType instanceof PsiIntersectionType) { + PsiType[] types = + StreamEx.of(((PsiIntersectionType)psiType).getConjuncts()).map(TypeConstraints::normalizeType).toArray(PsiType.EMPTY_ARRAY); + if (types.length > 0) { + return PsiIntersectionType.createIntersection(true, types); + } + } + if (psiType instanceof PsiClassType) { + return normalizeClassType((PsiClassType)psiType, new HashSet<>()); + } + return psiType; } @Nonnull - @Override - public PsiType getPsiType(Project project) { - return myType.createArrayType(); + private static PsiType normalizeClassType(@Nonnull PsiClassType psiType, Set processed) { + PsiClass aClass = psiType.resolve(); + if (aClass instanceof PsiTypeParameter) { + PsiClassType[] types = aClass.getExtendsListTypes(); + List result = new ArrayList<>(); + for (PsiClassType type : types) { + PsiClass resolved = type.resolve(); + if (resolved != null && processed.add(resolved)) { + PsiClassType classType = JavaPsiFacade.getElementFactory(aClass.getProject()).createType(resolved); + result.add(normalizeClassType(classType, processed)); + } + } + if (!result.isEmpty()) { + return PsiIntersectionType.createIntersection(true, result.toArray(PsiType.EMPTY_ARRAY)); + } + return PsiType.getJavaLangObject(aClass.getManager(), aClass.getResolveScope()); + } + return psiType.rawType(); } @Nonnull - @Override - public String toString() { - return myType.getCanonicalText() + "[]"; - } + private static TypeConstraint.Exact exactClass(@Nonnull PsiClass psiClass) { + String name = psiClass.getQualifiedName(); + if (name != null) { + switch (name) { + case CommonClassNames.JAVA_LANG_OBJECT: + return EXACTLY_OBJECT; + case CommonClassNames.JAVA_LANG_CLONEABLE: + return ArraySuperInterface.CLONEABLE; + case CommonClassNames.JAVA_IO_SERIALIZABLE: + return ArraySuperInterface.SERIALIZABLE; + } + } + return new ExactClass(psiClass); + } + + private enum PrimitiveArray implements TypeConstraint.Exact { + BOOLEAN(PsiType.BOOLEAN), + INT(PsiType.INT), + BYTE(PsiType.BYTE), + SHORT(PsiType.SHORT), + LONG(PsiType.LONG), + CHAR(PsiType.CHAR), + FLOAT(PsiType.FLOAT), + DOUBLE(PsiType.DOUBLE); + private final PsiPrimitiveType myType; + + PrimitiveArray(PsiPrimitiveType type) { + myType = type; + } - PsiPrimitiveType getType() { - return myType; - } + @Nonnull + @Override + public PsiType getPsiType(Project project) { + return myType.createArrayType(); + } - @Override - public boolean isFinal() { - return true; - } + @Nonnull + @Override + public String toString() { + return myType.getCanonicalText() + "[]"; + } - @Override - public StreamEx superTypes() { - return StreamEx.of(ArraySuperInterface.values()).append(EXACTLY_OBJECT); - } + PsiPrimitiveType getType() { + return myType; + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - return other.equals(this); - } + @Override + public boolean isFinal() { + return true; + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - return other.equals(this) || other.isAssignableFrom(this); - } - } + @Override + public StreamEx superTypes() { + return StreamEx.of(ArraySuperInterface.values()).append(EXACTLY_OBJECT); + } - private enum ArraySuperInterface implements TypeConstraint.Exact { - CLONEABLE(CommonClassNames.JAVA_LANG_CLONEABLE), - SERIALIZABLE(CommonClassNames.JAVA_IO_SERIALIZABLE); - private final - @Nonnull - String myReference; + @Override + public boolean isAssignableFrom(@Nonnull Exact other) { + return other.equals(this); + } - ArraySuperInterface(@Nonnull String reference) { - myReference = reference; + @Override + public boolean isConvertibleFrom(@Nonnull Exact other) { + return other.equals(this) || other.isAssignableFrom(this); + } } - @Nonnull - @Override - public PsiType getPsiType(Project project) { - return JavaPsiFacade.getElementFactory(project).createTypeByFQClassName(myReference); - } + private enum ArraySuperInterface implements TypeConstraint.Exact { + CLONEABLE(CommonClassNames.JAVA_LANG_CLONEABLE), + SERIALIZABLE(CommonClassNames.JAVA_IO_SERIALIZABLE); + private final + @Nonnull + String myReference; - @Nonnull - @Override - public String toString() { - return myReference; - } + ArraySuperInterface(@Nonnull String reference) { + myReference = reference; + } - @Override - public boolean isFinal() { - return false; - } + @Nonnull + @Override + public PsiType getPsiType(Project project) { + return JavaPsiFacade.getElementFactory(project).createTypeByFQClassName(myReference); + } - @Override - public StreamEx superTypes() { - return StreamEx.of(EXACTLY_OBJECT); - } + @Nonnull + @Override + public String toString() { + return myReference; + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - if (equals(other)) { - return true; - } - if (other instanceof PrimitiveArray || other instanceof ExactArray || other instanceof Unresolved) { - return true; - } - if (other instanceof ExactClass) { - return InheritanceUtil.isInheritor(((ExactClass) other).myClass, myReference); - } - return false; - } + @Override + public boolean isFinal() { + return false; + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - return !other.isFinal() || isAssignableFrom(other); - } + @Override + public StreamEx superTypes() { + return StreamEx.of(EXACTLY_OBJECT); + } - @Override - public boolean canBeInstantiated() { - return false; - } - } + @Override + public boolean isAssignableFrom(@Nonnull Exact other) { + if (equals(other)) { + return true; + } + if (other instanceof PrimitiveArray || other instanceof ExactArray || other instanceof Unresolved) { + return true; + } + if (other instanceof ExactClass) { + return InheritanceUtil.isInheritor(((ExactClass)other).myClass, myReference); + } + return false; + } - private static final class ExactClass implements TypeConstraint.Exact { - private final - @Nonnull - PsiClass myClass; + @Override + public boolean isConvertibleFrom(@Nonnull Exact other) { + return !other.isFinal() || isAssignableFrom(other); + } - ExactClass(@Nonnull PsiClass aClass) { - assert !(aClass instanceof PsiTypeParameter); - myClass = aClass; + @Override + public boolean canBeInstantiated() { + return false; + } } - @Override - public boolean equals(Object obj) { - return obj == this || obj instanceof ExactClass && - myClass.getManager().areElementsEquivalent(myClass, ((ExactClass) obj).myClass); - } + private static final class ExactClass implements TypeConstraint.Exact { + private final + @Nonnull + PsiClass myClass; - @Override - public int hashCode() { - return Objects.hashCode(myClass.getName()); - } + ExactClass(@Nonnull PsiClass aClass) { + assert !(aClass instanceof PsiTypeParameter); + myClass = aClass; + } - @Override - public boolean canBeInstantiated() { - // Abstract final type is incorrect. We, however, assume that final wins: it can be instantiated - // otherwise TypeConstraints.instanceOf(type) would return impossible type - return (myClass.hasModifierProperty(PsiModifier.FINAL) || !myClass.hasModifierProperty(PsiModifier.ABSTRACT)) && - !CommonClassNames.JAVA_LANG_VOID.equals(myClass.getQualifiedName()); - } + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof ExactClass && + myClass.getManager().areElementsEquivalent(myClass, ((ExactClass)obj).myClass); + } - @Override - public boolean isComparedByEquals() { - String name = myClass.getQualifiedName(); - return name != null && (CommonClassNames.JAVA_LANG_STRING.equals(name) || TypeConversionUtil.isPrimitiveWrapper(name)); - } + @Override + public int hashCode() { + return Objects.hashCode(myClass.getName()); + } - @Nonnull - @Override - public PsiType getPsiType(Project project) { - return JavaPsiFacade.getElementFactory(project).createType(myClass); - } + @Override + public boolean canBeInstantiated() { + // Abstract final type is incorrect. We, however, assume that final wins: it can be instantiated + // otherwise TypeConstraints.instanceOf(type) would return impossible type + return (myClass.hasModifierProperty(PsiModifier.FINAL) || !myClass.hasModifierProperty(PsiModifier.ABSTRACT)) && + !CommonClassNames.JAVA_LANG_VOID.equals(myClass.getQualifiedName()); + } - @Nonnull - @Override - public String toString() { - String name = myClass.getQualifiedName(); - if (name == null) { - name = myClass.getName(); - } - if (name == null && myClass instanceof PsiAnonymousClass) { - PsiClassType baseClassType = ((PsiAnonymousClass) myClass).getBaseClassType(); - name = "anonymous " + createExact(baseClassType); - } - return String.valueOf(name); - } + @Override + public boolean isComparedByEquals() { + String name = myClass.getQualifiedName(); + return name != null && (CommonClassNames.JAVA_LANG_STRING.equals(name) || TypeConversionUtil.isPrimitiveWrapper(name)); + } - @Override - public boolean isFinal() { - return myClass.hasModifierProperty(PsiModifier.FINAL); - } + @Nonnull + @Override + public PsiType getPsiType(Project project) { + return JavaPsiFacade.getElementFactory(project).createType(myClass); + } - @Override - public StreamEx superTypes() { - List superTypes = new ArrayList<>(); - InheritanceUtil.processSupers(myClass, false, t -> { - if (!t.hasModifierProperty(PsiModifier.FINAL)) { - superTypes.add(exactClass(t)); + @Nonnull + @Override + public String toString() { + String name = myClass.getQualifiedName(); + if (name == null) { + name = myClass.getName(); + } + if (name == null && myClass instanceof PsiAnonymousClass) { + PsiClassType baseClassType = ((PsiAnonymousClass)myClass).getBaseClassType(); + name = "anonymous " + createExact(baseClassType); + } + return String.valueOf(name); } - return true; - }); - return StreamEx.of(superTypes); - } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - if (equals(other) || other instanceof Unresolved) { - return true; - } - if (other instanceof ExactClass) { - return InheritanceUtil.isInheritorOrSelf(((ExactClass) other).myClass, myClass, true); - } - return false; - } + @Override + public boolean isFinal() { + return myClass.hasModifierProperty(PsiModifier.FINAL); + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - if (equals(other) || other instanceof Unresolved || other == EXACTLY_OBJECT) { - return true; - } - if (other instanceof ArraySuperInterface) { - if (myClass.isInterface()) { - return true; - } - if (!myClass.hasModifierProperty(PsiModifier.FINAL)) { - return true; - } - return InheritanceUtil.isInheritor(myClass, ((ArraySuperInterface) other).myReference); - } - if (other instanceof ExactClass) { - PsiClass otherClass = ((ExactClass) other).myClass; - if (myClass.isInterface() && otherClass.isInterface()) { - return true; - } - if (myClass.isInterface() && !otherClass.hasModifierProperty(PsiModifier.FINAL)) { - return true; - } - if (otherClass.isInterface() && !myClass.hasModifierProperty(PsiModifier.FINAL)) { - return true; - } - PsiManager manager = myClass.getManager(); - return manager.areElementsEquivalent(myClass, otherClass) || - otherClass.isInheritor(myClass, true) || - myClass.isInheritor(otherClass, true); - } - return false; - } - } + @Override + public StreamEx superTypes() { + List superTypes = new ArrayList<>(); + InheritanceUtil.processSupers(myClass, false, t -> { + if (!t.hasModifierProperty(PsiModifier.FINAL)) { + superTypes.add(exactClass(t)); + } + return true; + }); + return StreamEx.of(superTypes); + } - private static final class ExactArray implements TypeConstraint.Exact { - private final - @Nonnull - Exact myComponent; + @Override + public boolean isAssignableFrom(@Nonnull Exact other) { + if (equals(other) || other instanceof Unresolved) { + return true; + } + if (other instanceof ExactClass) { + return InheritanceUtil.isInheritorOrSelf(((ExactClass)other).myClass, myClass, true); + } + return false; + } - private ExactArray(@Nonnull Exact component) { - myComponent = component; + @Override + public boolean isConvertibleFrom(@Nonnull Exact other) { + if (equals(other) || other instanceof Unresolved || other == EXACTLY_OBJECT) { + return true; + } + if (other instanceof ArraySuperInterface) { + if (myClass.isInterface()) { + return true; + } + if (!myClass.hasModifierProperty(PsiModifier.FINAL)) { + return true; + } + return InheritanceUtil.isInheritor(myClass, ((ArraySuperInterface)other).myReference); + } + if (other instanceof ExactClass) { + PsiClass otherClass = ((ExactClass)other).myClass; + if (myClass.isInterface() && otherClass.isInterface()) { + return true; + } + if (myClass.isInterface() && !otherClass.hasModifierProperty(PsiModifier.FINAL)) { + return true; + } + if (otherClass.isInterface() && !myClass.hasModifierProperty(PsiModifier.FINAL)) { + return true; + } + PsiManager manager = myClass.getManager(); + return manager.areElementsEquivalent(myClass, otherClass) || + otherClass.isInheritor(myClass, true) || + myClass.isInheritor(otherClass, true); + } + return false; + } } - @Nullable - @Override - public PsiType getPsiType(Project project) { - PsiType componentType = myComponent.getPsiType(project); - return componentType == null ? null : componentType.createArrayType(); - } + private static final class ExactArray implements TypeConstraint.Exact { + private final + @Nonnull + Exact myComponent; - @Override - public boolean equals(Object obj) { - return obj == this || obj instanceof ExactArray && myComponent.equals(((ExactArray) obj).myComponent); - } + private ExactArray(@Nonnull Exact component) { + myComponent = component; + } - @Override - public int hashCode() { - return myComponent.hashCode() * 31 + 1; - } + @Nullable + @Override + public PsiType getPsiType(Project project) { + PsiType componentType = myComponent.getPsiType(project); + return componentType == null ? null : componentType.createArrayType(); + } - @Nonnull - @Override - public String toString() { - return myComponent + "[]"; - } + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof ExactArray && myComponent.equals(((ExactArray)obj).myComponent); + } - @Override - public boolean isFinal() { - return myComponent.isFinal(); - } + @Override + public int hashCode() { + return myComponent.hashCode() * 31 + 1; + } - @Override - public StreamEx superTypes() { - return myComponent.superTypes().map(ExactArray::new).append(ArraySuperInterface.values()).append(EXACTLY_OBJECT); - } + @Nonnull + @Override + public String toString() { + return myComponent + "[]"; + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - if (!(other instanceof ExactArray)) { - return false; - } - return myComponent.isAssignableFrom(((ExactArray) other).myComponent); - } + @Override + public boolean isFinal() { + return myComponent.isFinal(); + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - if (other instanceof ExactArray) { - return myComponent.isConvertibleFrom(((ExactArray) other).myComponent); - } - if (other instanceof ArraySuperInterface) { - return true; - } - if (other instanceof ExactClass) { - return CommonClassNames.JAVA_LANG_OBJECT.equals(((ExactClass) other).myClass.getQualifiedName()); - } - return false; - } + @Override + public StreamEx superTypes() { + return myComponent.superTypes().map(ExactArray::new).append(ArraySuperInterface.values()).append(EXACTLY_OBJECT); + } - @Override - public - @Nonnull - Exact getArrayComponent() { - return myComponent; - } - } + @Override + public boolean isAssignableFrom(@Nonnull Exact other) { + if (!(other instanceof ExactArray)) { + return false; + } + return myComponent.isAssignableFrom(((ExactArray)other).myComponent); + } - private static final class Unresolved implements TypeConstraint.Exact { - private final - @Nonnull - String myReference; + @Override + public boolean isConvertibleFrom(@Nonnull Exact other) { + if (other instanceof ExactArray) { + return myComponent.isConvertibleFrom(((ExactArray)other).myComponent); + } + if (other instanceof ArraySuperInterface) { + return true; + } + if (other instanceof ExactClass) { + return CommonClassNames.JAVA_LANG_OBJECT.equals(((ExactClass)other).myClass.getQualifiedName()); + } + return false; + } - private Unresolved(@Nonnull String reference) { - myReference = reference; + @Override + public + @Nonnull + Exact getArrayComponent() { + return myComponent; + } } - @Override - public boolean isResolved() { - return false; - } + private static final class Unresolved implements TypeConstraint.Exact { + private final + @Nonnull + String myReference; - @Override - public boolean equals(Object obj) { - return obj == this || obj instanceof Unresolved && myReference.equals(((Unresolved) obj).myReference); - } + private Unresolved(@Nonnull String reference) { + myReference = reference; + } - @Override - public int hashCode() { - return myReference.hashCode(); - } + @Override + public boolean isResolved() { + return false; + } - @Nonnull - @Override - public String toString() { - return " " + myReference; - } + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof Unresolved && myReference.equals(((Unresolved)obj).myReference); + } - @Override - public boolean isFinal() { - return false; - } + @Override + public int hashCode() { + return myReference.hashCode(); + } - @Override - public StreamEx superTypes() { - return StreamEx.of(EXACTLY_OBJECT); - } + @Nonnull + @Override + public String toString() { + return " " + myReference; + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - return other instanceof Unresolved || other instanceof ExactClass; - } + @Override + public boolean isFinal() { + return false; + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - return other instanceof Unresolved || other instanceof ExactClass || other instanceof ArraySuperInterface; + @Override + public StreamEx superTypes() { + return StreamEx.of(EXACTLY_OBJECT); + } + + @Override + public boolean isAssignableFrom(@Nonnull Exact other) { + return other instanceof Unresolved || other instanceof ExactClass; + } + + @Override + public boolean isConvertibleFrom(@Nonnull Exact other) { + return other instanceof Unresolved || other instanceof ExactClass || other instanceof ArraySuperInterface; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ParameterNullityInferenceKt.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ParameterNullityInferenceKt.java index e99353d7a1..e6abf20fa5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ParameterNullityInferenceKt.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ParameterNullityInferenceKt.java @@ -18,194 +18,233 @@ * from kotlin */ class ParameterNullityInferenceKt { - static BitSet inferNotNullParameters(LighterAST tree, LighterASTNode method, List statements) { - List parameterNames = getParameterNames(tree, method); - return inferNotNullParameters(tree, parameterNames, statements); - } - - private static BitSet inferNotNullParameters(LighterAST tree, List parameterNames, List statements) { - Set canBeNulls = new HashSet<>(ContainerUtil.mapNotNull(parameterNames, it -> it)); - if (canBeNulls.isEmpty()) { - return new BitSet(); + static BitSet inferNotNullParameters(LighterAST tree, LighterASTNode method, List statements) { + List parameterNames = getParameterNames(tree, method); + return inferNotNullParameters(tree, parameterNames, statements); } - Set notNulls = new HashSet<>(); - Deque queue = new ArrayDeque<>(statements); - while (!queue.isEmpty() && !canBeNulls.isEmpty()) { - LighterASTNode element = queue.removeFirst(); - IElementType type = element.getTokenType(); - - if (type == CONDITIONAL_EXPRESSION || type == EXPRESSION_STATEMENT) { - LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); - if (let != null) { - queue.addFirst(let); - } - } else if (type == RETURN_STATEMENT) { - queue.clear(); - LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); - if (let != null) { - queue.addFirst(let); - } - } else if (type == FOR_STATEMENT) { - LighterASTNode condition = JavaLightTreeUtil.findExpressionChild(tree, element); - queue.clear(); - if (condition != null) { - queue.addFirst(condition); - LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, ElementType.JAVA_STATEMENT_BIT_SET); - if (let != null) { - queue.addFirst(let); - } - } else { - // no condition == endless loop: we may analyze body (at least until break/return/if/etc.) - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } - } else if (type == WHILE_STATEMENT) { - queue.clear(); - LighterASTNode expression = JavaLightTreeUtil.findExpressionChild(tree, element); - if (expression != null && expression.getTokenType() == LITERAL_EXPRESSION && LightTreeUtil.firstChildOfType(tree, expression, JavaTokenType.TRUE_KEYWORD) != null) { - // while(true) == endless loop: we may analyze body (at least until break/return/if/etc.) - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } else { - dereference(tree, expression, canBeNulls, notNulls, queue); - } - } else if (type == FOREACH_STATEMENT || type == SWITCH_STATEMENT || type == IF_STATEMENT || type == THROW_STATEMENT) { - queue.clear(); - LighterASTNode expression = JavaLightTreeUtil.findExpressionChild(tree, element); - dereference(tree, expression, canBeNulls, notNulls, queue); - } else if (type == BINARY_EXPRESSION || type == POLYADIC_EXPRESSION) { - if (LightTreeUtil.firstChildOfType(tree, element, TokenSet.create(JavaTokenType.ANDAND, JavaTokenType.OROR)) != null) { - LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); - if (let != null) { - queue.addFirst(let); - } - } else { - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } - } else if (type == EMPTY_STATEMENT || type == ASSERT_STATEMENT || type == DO_WHILE_STATEMENT || type == DECLARATION_STATEMENT || type == BLOCK_STATEMENT) { - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } else if (type == SYNCHRONIZED_STATEMENT) { - LighterASTNode sync = JavaLightTreeUtil.findExpressionChild(tree, element); - dereference(tree, sync, canBeNulls, notNulls, queue); - - LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, CODE_BLOCK); - if (let != null) { - queue.addFirst(let); + private static BitSet inferNotNullParameters(LighterAST tree, List parameterNames, List statements) { + Set canBeNulls = new HashSet<>(ContainerUtil.mapNotNull(parameterNames, it -> it)); + if (canBeNulls.isEmpty()) { + return new BitSet(); } - } else if (type == FIELD || type == PARAMETER || type == LOCAL_VARIABLE) { - canBeNulls.remove(JavaLightTreeUtil.getNameIdentifierText(tree, element)); - LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); - if (let != null) { - queue.addFirst(let); + Set notNulls = new HashSet<>(); + Deque queue = new ArrayDeque<>(statements); + + while (!queue.isEmpty() && !canBeNulls.isEmpty()) { + LighterASTNode element = queue.removeFirst(); + IElementType type = element.getTokenType(); + + if (type == CONDITIONAL_EXPRESSION || type == EXPRESSION_STATEMENT) { + LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); + if (let != null) { + queue.addFirst(let); + } + } + else if (type == RETURN_STATEMENT) { + queue.clear(); + LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); + if (let != null) { + queue.addFirst(let); + } + } + else if (type == FOR_STATEMENT) { + LighterASTNode condition = JavaLightTreeUtil.findExpressionChild(tree, element); + queue.clear(); + if (condition != null) { + queue.addFirst(condition); + LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, ElementType.JAVA_STATEMENT_BIT_SET); + if (let != null) { + queue.addFirst(let); + } + } + else { + // no condition == endless loop: we may analyze body (at least until break/return/if/etc.) + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + } + else if (type == WHILE_STATEMENT) { + queue.clear(); + LighterASTNode expression = JavaLightTreeUtil.findExpressionChild(tree, element); + if (expression != null && expression.getTokenType() == LITERAL_EXPRESSION + && LightTreeUtil.firstChildOfType(tree, expression, JavaTokenType.TRUE_KEYWORD) != null) { + // while(true) == endless loop: we may analyze body (at least until break/return/if/etc.) + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + else { + dereference(tree, expression, canBeNulls, notNulls, queue); + } + } + else if (type == FOREACH_STATEMENT || type == SWITCH_STATEMENT || type == IF_STATEMENT || type == THROW_STATEMENT) { + queue.clear(); + LighterASTNode expression = JavaLightTreeUtil.findExpressionChild(tree, element); + dereference(tree, expression, canBeNulls, notNulls, queue); + } + else if (type == BINARY_EXPRESSION || type == POLYADIC_EXPRESSION) { + if (LightTreeUtil.firstChildOfType(tree, element, TokenSet.create(JavaTokenType.ANDAND, JavaTokenType.OROR)) != null) { + LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); + if (let != null) { + queue.addFirst(let); + } + } + else { + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + } + else if (type == EMPTY_STATEMENT || type == ASSERT_STATEMENT || type == DO_WHILE_STATEMENT || type == DECLARATION_STATEMENT || type == BLOCK_STATEMENT) { + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + else if (type == SYNCHRONIZED_STATEMENT) { + LighterASTNode sync = JavaLightTreeUtil.findExpressionChild(tree, element); + dereference(tree, sync, canBeNulls, notNulls, queue); + + LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, CODE_BLOCK); + if (let != null) { + queue.addFirst(let); + } + } + else if (type == FIELD || type == PARAMETER || type == LOCAL_VARIABLE) { + canBeNulls.remove(JavaLightTreeUtil.getNameIdentifierText(tree, element)); + LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); + if (let != null) { + queue.addFirst(let); + } + } + else if (type == EXPRESSION_LIST) { + List children = JavaLightTreeUtil.getExpressionChildren(tree, element); + // When parameter is passed to another method, that method may have "null -> fail" contract, + // so without knowing this we cannot continue inference for the parameter + children.forEach(it -> ignore(tree, it, canBeNulls)); + ContainerUtil.reverse(children).forEach(queue::addFirst); + } + else if (type == ASSIGNMENT_EXPRESSION) { + LighterASTNode lvalue = JavaLightTreeUtil.findExpressionChild(tree, element); + ignore(tree, lvalue, canBeNulls); + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + else if (type == ARRAY_ACCESS_EXPRESSION) { + JavaLightTreeUtil.getExpressionChildren(tree, element).forEach(it -> dereference(tree, it, canBeNulls, notNulls, queue)); + } + else if (type == METHOD_REF_EXPRESSION || type == REFERENCE_EXPRESSION) { + LighterASTNode qualifier = JavaLightTreeUtil.findExpressionChild(tree, element); + dereference(tree, qualifier, canBeNulls, notNulls, queue); + } + else if (type == CLASS || type == METHOD || type == LAMBDA_EXPRESSION) { + // Ignore classes, methods and lambda expression bodies as it's not known whether they will be instantiated/executed. + // For anonymous classes argument list, field initializers and instance initialization sections are checked. + } + else if (type == TRY_STATEMENT) { + queue.clear(); + + List params = ContainerUtil.mapNotNull( + LightTreeUtil.getChildrenOfType(tree, element, CATCH_SECTION), + it -> LightTreeUtil.firstChildOfType(tree, it, PARAMETER) + ); + + List paramTypes = + ContainerUtil.map(params, parameter -> LightTreeUtil.firstChildOfType(tree, parameter, TYPE)); + + boolean canCatchNpe = ContainerUtil.or(paramTypes, it -> canCatchNpe(tree, it)); + if (!canCatchNpe) { + LightTreeUtil.getChildrenOfType(tree, element, RESOURCE_LIST).forEach(queue::addFirst); + + LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, CODE_BLOCK); + if (let != null) { + queue.addFirst(let); + } + + // stop analysis after first try as we are not sure how execution goes further: + // whether or not it visit catch blocks, etc. + } + } + else if (ElementType.JAVA_STATEMENT_BIT_SET.contains(type)) { + // Unknown/unprocessed statement: just stop processing the rest of the method + queue.clear(); + } + else { + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } } - } else if (type == EXPRESSION_LIST) { - List children = JavaLightTreeUtil.getExpressionChildren(tree, element); - // When parameter is passed to another method, that method may have "null -> fail" contract, - // so without knowing this we cannot continue inference for the parameter - children.forEach(it -> ignore(tree, it, canBeNulls)); - ContainerUtil.reverse(children).forEach(queue::addFirst); - } else if (type == ASSIGNMENT_EXPRESSION) { - LighterASTNode lvalue = JavaLightTreeUtil.findExpressionChild(tree, element); - ignore(tree, lvalue, canBeNulls); - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } else if (type == ARRAY_ACCESS_EXPRESSION) { - JavaLightTreeUtil.getExpressionChildren(tree, element).forEach(it -> dereference(tree, it, canBeNulls, notNulls, queue)); - } else if (type == METHOD_REF_EXPRESSION || type == REFERENCE_EXPRESSION) { - LighterASTNode qualifier = JavaLightTreeUtil.findExpressionChild(tree, element); - dereference(tree, qualifier, canBeNulls, notNulls, queue); - } else if (type == CLASS || type == METHOD || type == LAMBDA_EXPRESSION) { - // Ignore classes, methods and lambda expression bodies as it's not known whether they will be instantiated/executed. - // For anonymous classes argument list, field initializers and instance initialization sections are checked. - } else if (type == TRY_STATEMENT) { - queue.clear(); - - List params = ContainerUtil.mapNotNull(LightTreeUtil.getChildrenOfType(tree, element, CATCH_SECTION), it -> LightTreeUtil.firstChildOfType(tree, it, PARAMETER)); - - List paramTypes = ContainerUtil.map(params, parameter -> LightTreeUtil.firstChildOfType(tree, parameter, TYPE)); - - boolean canCatchNpe = ContainerUtil.or(paramTypes, it -> canCatchNpe(tree, it)); - if (!canCatchNpe) { - LightTreeUtil.getChildrenOfType(tree, element, RESOURCE_LIST).forEach(queue::addFirst); - - LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, CODE_BLOCK); - if (let != null) { - queue.addFirst(let); - } - - // stop analysis after first try as we are not sure how execution goes further: - // whether or not it visit catch blocks, etc. - } - } else if (ElementType.JAVA_STATEMENT_BIT_SET.contains(type)) { - // Unknown/unprocessed statement: just stop processing the rest of the method - queue.clear(); - } else { - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } - } - - BitSet notNullParameters = new BitSet(); - for (int index = 0; index < parameterNames.size(); index++) { - String parameterName = parameterNames.get(index); - - if (notNulls.contains(parameterName)) { - notNullParameters.set(index); - } - } + BitSet notNullParameters = new BitSet(); - return notNullParameters; - } + for (int index = 0; index < parameterNames.size(); index++) { + String parameterName = parameterNames.get(index); - private static final Set NPC_CATCHES = Set.of("Throwable", "Exception", "RuntimeException", "NullPointerException", - CommonClassNames.JAVA_LANG_THROWABLE, CommonClassNames.JAVA_LANG_EXCEPTION, - CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION, CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION); + if (notNulls.contains(parameterName)) { + notNullParameters.set(index); + } + } - private static boolean canCatchNpe(LighterAST tree, @Nullable LighterASTNode type) { - if (type == null) { - return false; - } - LighterASTNode codeRef = LightTreeUtil.firstChildOfType(tree, type, JAVA_CODE_REFERENCE); - String name = JavaLightTreeUtil.getNameIdentifierText(tree, codeRef); - if (name == null) { - // Multicatch - return ContainerUtil.or(LightTreeUtil.getChildrenOfType(tree, type, TYPE), it -> canCatchNpe(tree, it)); + return notNullParameters; } - return NPC_CATCHES.contains(name); - } - private static void ignore(LighterAST tree, @Nullable LighterASTNode expression, Set canBeNulls) { - if (expression != null && expression.getTokenType() == REFERENCE_EXPRESSION && JavaLightTreeUtil.findExpressionChild(tree, expression) == null) { - canBeNulls.remove(JavaLightTreeUtil.getNameIdentifierText(tree, expression)); + private static final Set NPC_CATCHES = Set.of( + "Throwable", + "Exception", + "RuntimeException", + "NullPointerException", + CommonClassNames.JAVA_LANG_THROWABLE, + CommonClassNames.JAVA_LANG_EXCEPTION, + CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION, + CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION + ); + + private static boolean canCatchNpe(LighterAST tree, @Nullable LighterASTNode type) { + if (type == null) { + return false; + } + LighterASTNode codeRef = LightTreeUtil.firstChildOfType(tree, type, JAVA_CODE_REFERENCE); + String name = JavaLightTreeUtil.getNameIdentifierText(tree, codeRef); + if (name == null) { + // Multicatch + return ContainerUtil.or(LightTreeUtil.getChildrenOfType(tree, type, TYPE), it -> canCatchNpe(tree, it)); + } + return NPC_CATCHES.contains(name); } - } - private static void dereference(LighterAST tree, LighterASTNode expression, Set canBeNulls, Set notNulls, Deque queue) { - if (expression == null) { - return; + private static void ignore(LighterAST tree, @Nullable LighterASTNode expression, Set canBeNulls) { + if (expression != null && expression.getTokenType() == REFERENCE_EXPRESSION + && JavaLightTreeUtil.findExpressionChild(tree, expression) == null) { + canBeNulls.remove(JavaLightTreeUtil.getNameIdentifierText(tree, expression)); + } } - if (expression.getTokenType() == REFERENCE_EXPRESSION && JavaLightTreeUtil.findExpressionChild(tree, expression) == null) { - String name = JavaLightTreeUtil.getNameIdentifierText(tree, expression); - if (canBeNulls.remove(name)) { - notNulls.add(name); - } - } else { - queue.addFirst(expression); + private static void dereference( + LighterAST tree, + LighterASTNode expression, + Set canBeNulls, + Set notNulls, + Deque queue + ) { + if (expression == null) { + return; + } + + if (expression.getTokenType() == REFERENCE_EXPRESSION && JavaLightTreeUtil.findExpressionChild(tree, expression) == null) { + String name = JavaLightTreeUtil.getNameIdentifierText(tree, expression); + if (canBeNulls.remove(name)) { + notNulls.add(name); + } + } + else { + queue.addFirst(expression); + } } - } - @Nonnull - private static List getParameterNames(LighterAST tree, LighterASTNode method) { - LighterASTNode parameterList = LightTreeUtil.firstChildOfType(tree, method, PARAMETER_LIST); - if (parameterList == null) { - return Collections.emptyList(); + @Nonnull + private static List getParameterNames(LighterAST tree, LighterASTNode method) { + LighterASTNode parameterList = LightTreeUtil.firstChildOfType(tree, method, PARAMETER_LIST); + if (parameterList == null) { + return Collections.emptyList(); + } + List parameters = LightTreeUtil.getChildrenOfType(tree, parameterList, PARAMETER); + + return ContainerUtil.map(parameters, it -> { + if (LightTreeUtil.firstChildOfType(tree, it, ElementType.PRIMITIVE_TYPE_BIT_SET) != null) { + return null; + } + return JavaLightTreeUtil.getNameIdentifierText(tree, it); + }); } - List parameters = LightTreeUtil.getChildrenOfType(tree, parameterList, PARAMETER); - - return ContainerUtil.map(parameters, it -> { - if (LightTreeUtil.firstChildOfType(tree, it, ElementType.PRIMITIVE_TYPE_BIT_SET) != null) { - return null; - } - return JavaLightTreeUtil.getNameIdentifierText(tree, it); - }); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceResult.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceResult.java index 09b6003725..f1e7caa431 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceResult.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceResult.java @@ -34,121 +34,124 @@ * from kotlin */ class PurityInferenceResult { - private List mutableRefs; - @Nullable - private ExpressionRange singleCall; - - PurityInferenceResult(List mutableRefs, @Nullable ExpressionRange singleCall) { - this.mutableRefs = mutableRefs; - this.singleCall = singleCall; - } - - public boolean isPure(PsiMethod method, Supplier body) { - return !mutatesNonLocals(method, body) && callsOnlyPureMethods(method, body); - } - - public List getMutableRefs() { - return mutableRefs; - } - - @Nullable - public ExpressionRange getSingleCall() { - return singleCall; - } - - @RequiredReadAction - private boolean mutatesNonLocals(PsiMethod method, Supplier body) { - for (ExpressionRange range : mutableRefs) { - if (!isLocalVarReference(range.restoreExpression(body.get()), method)) { - return true; - } - } - return false; - } + private List mutableRefs; + @Nullable + private ExpressionRange singleCall; - @RequiredReadAction - private boolean callsOnlyPureMethods(PsiMethod currentMethod, Supplier body) { - if (singleCall == null) { - return true; + PurityInferenceResult(List mutableRefs, @Nullable ExpressionRange singleCall) { + this.mutableRefs = mutableRefs; + this.singleCall = singleCall; } - PsiCall psiCall = (PsiCall) singleCall.restoreExpression(body.get()); - assert psiCall != null; - PsiMethod method = psiCall.resolveMethod(); - if (method != null) { - return method.equals(currentMethod) || JavaMethodContractUtil.isPure(method); - } else if (psiCall instanceof PsiNewExpression && psiCall.getArgumentList() != null && psiCall.getArgumentList().getExpressionCount() == 0) { - - PsiJavaCodeReferenceElement classOrAnonymousClassReference = ((PsiNewExpression) psiCall).getClassOrAnonymousClassReference(); - PsiElement resolved = classOrAnonymousClassReference == null ? null : classOrAnonymousClassReference.resolve(); - - PsiClass psiClass = resolved instanceof PsiClass ? (PsiClass) resolved : null; - - if (psiClass != null) { - PsiClass superClass = psiClass.getSuperClass(); - return superClass == null || CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName()); - } + + public boolean isPure(PsiMethod method, Supplier body) { + return !mutatesNonLocals(method, body) && callsOnlyPureMethods(method, body); } - return false; - } - - @RequiredReadAction - private boolean isLocalVarReference(PsiExpression expression, PsiMethod scope) { - if (expression instanceof PsiReferenceExpression) { - PsiElement resolve = ((PsiReferenceExpression) expression).resolve(); - return resolve instanceof PsiLocalVariable || resolve instanceof PsiParameter; - } else if (expression instanceof PsiArrayAccessExpression) { - PsiExpression arrayExpression = ((PsiArrayAccessExpression) expression).getArrayExpression(); - if (arrayExpression instanceof PsiReferenceExpression) { - PsiElement resolve = ((PsiReferenceExpression) arrayExpression).resolve(); - return resolve instanceof PsiLocalVariable && isLocallyCreatedArray(scope, (PsiLocalVariable) resolve); - } + public List getMutableRefs() { + return mutableRefs; } - return false; - } - private boolean isLocallyCreatedArray(PsiMethod scope, PsiLocalVariable target) { - PsiExpression initializer = target.getInitializer(); - if (initializer != null & !(initializer instanceof PsiNewExpression)) { - return false; + @Nullable + public ExpressionRange getSingleCall() { + return singleCall; } - for (PsiReference ref : ReferencesSearch.search(target, new LocalSearchScope(scope)).findAll()) { - if (ref instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression) ref)) { - PsiAssignmentExpression assign = PsiTreeUtil.getParentOfType((PsiReferenceExpression) ref, PsiAssignmentExpression.class); - if (assign == null || !(assign.getRExpression() instanceof PsiNewExpression)) { - return false; + @RequiredReadAction + private boolean mutatesNonLocals(PsiMethod method, Supplier body) { + for (ExpressionRange range : mutableRefs) { + if (!isLocalVarReference(range.restoreExpression(body.get()), method)) { + return true; + } } - } + return false; } - return true; - } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + @RequiredReadAction + private boolean callsOnlyPureMethods(PsiMethod currentMethod, Supplier body) { + if (singleCall == null) { + return true; + } + PsiCall psiCall = (PsiCall)singleCall.restoreExpression(body.get()); + assert psiCall != null; + PsiMethod method = psiCall.resolveMethod(); + if (method != null) { + return method.equals(currentMethod) || JavaMethodContractUtil.isPure(method); + } + else if (psiCall instanceof PsiNewExpression && psiCall.getArgumentList() != null + && psiCall.getArgumentList().getExpressionCount() == 0) { + + PsiJavaCodeReferenceElement classOrAnonymousClassReference = ((PsiNewExpression)psiCall).getClassOrAnonymousClassReference(); + PsiElement resolved = classOrAnonymousClassReference == null ? null : classOrAnonymousClassReference.resolve(); + + PsiClass psiClass = resolved instanceof PsiClass ? (PsiClass)resolved : null; + + if (psiClass != null) { + PsiClass superClass = psiClass.getSuperClass(); + return superClass == null || CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName()); + } + } + + return false; } - if (o == null || getClass() != o.getClass()) { - return false; + + @RequiredReadAction + private boolean isLocalVarReference(PsiExpression expression, PsiMethod scope) { + if (expression instanceof PsiReferenceExpression) { + PsiElement resolve = ((PsiReferenceExpression)expression).resolve(); + return resolve instanceof PsiLocalVariable || resolve instanceof PsiParameter; + } + else if (expression instanceof PsiArrayAccessExpression) { + PsiExpression arrayExpression = ((PsiArrayAccessExpression)expression).getArrayExpression(); + if (arrayExpression instanceof PsiReferenceExpression) { + PsiElement resolve = ((PsiReferenceExpression)arrayExpression).resolve(); + return resolve instanceof PsiLocalVariable && isLocallyCreatedArray(scope, (PsiLocalVariable)resolve); + } + } + return false; } - PurityInferenceResult that = (PurityInferenceResult) o; + private boolean isLocallyCreatedArray(PsiMethod scope, PsiLocalVariable target) { + PsiExpression initializer = target.getInitializer(); + if (initializer != null & !(initializer instanceof PsiNewExpression)) { + return false; + } - if (mutableRefs != null ? !mutableRefs.equals(that.mutableRefs) : that.mutableRefs != null) { - return false; - } - if (singleCall != null ? !singleCall.equals(that.singleCall) : that.singleCall != null) { - return false; + for (PsiReference ref : ReferencesSearch.search(target, new LocalSearchScope(scope)).findAll()) { + if (ref instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression)ref)) { + PsiAssignmentExpression assign = PsiTreeUtil.getParentOfType((PsiReferenceExpression)ref, PsiAssignmentExpression.class); + if (assign == null || !(assign.getRExpression() instanceof PsiNewExpression)) { + return false; + } + } + } + return true; } - return true; - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PurityInferenceResult that = (PurityInferenceResult)o; - @Override - public int hashCode() { - int result = mutableRefs != null ? mutableRefs.hashCode() : 0; - result = 31 * result + (singleCall != null ? singleCall.hashCode() : 0); - return result; - } + if (mutableRefs != null ? !mutableRefs.equals(that.mutableRefs) : that.mutableRefs != null) { + return false; + } + if (singleCall != null ? !singleCall.equals(that.singleCall) : that.singleCall != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = mutableRefs != null ? mutableRefs.hashCode() : 0; + result = 31 * result + (singleCall != null ? singleCall.hashCode() : 0); + return result; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssertAllInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssertAllInliner.java index 6baffab6ea..5836445e2b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssertAllInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssertAllInliner.java @@ -18,49 +18,49 @@ * JUnit5 Assertions.assertAll */ public class AssertAllInliner implements CallInliner { - private static final CallMatcher ASSERT_ALL = - anyOf( - staticCall("org.junit.jupiter.api.Assertions", "assertAll").parameterTypes("org.junit.jupiter.api.function.Executable..."), - staticCall("org.junit.jupiter.api.Assertions", "assertAll") - .parameterTypes(CommonClassNames.JAVA_LANG_STRING, "org.junit.jupiter.api.function.Executable...") - ); + private static final CallMatcher ASSERT_ALL = anyOf( + staticCall("org.junit.jupiter.api.Assertions", "assertAll") + .parameterTypes("org.junit.jupiter.api.function.Executable..."), + staticCall("org.junit.jupiter.api.Assertions", "assertAll") + .parameterTypes(CommonClassNames.JAVA_LANG_STRING, "org.junit.jupiter.api.function.Executable...") + ); - @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { - if (!ASSERT_ALL.matches(call) || !MethodCallUtils.isVarArgCall(call)) { - return false; + @Override + public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + if (!ASSERT_ALL.matches(call) || !MethodCallUtils.isVarArgCall(call)) { + return false; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + for (int i = 0; i < args.length; i++) { + PsiExpression arg = args[i]; + if (i == 0 && TypeUtils.isJavaLangString(arg.getType())) { + builder.pushExpression(arg, NullabilityProblemKind.noProblem).pop(); + } + else { + builder.evaluateFunction(arg); + } + } + DfaVariableValue result = builder.createTempVariable(PsiType.BOOLEAN); + builder.assignAndPop(result, DfTypes.FALSE); + for (int i = 0; i < args.length; i++) { + PsiExpression arg = args[i]; + if (i == 0 && TypeUtils.isJavaLangString(arg.getType())) { + continue; + } + builder.doTry(call) + .invokeFunction(0, arg) + .catchAll() + .assignAndPop(result, DfTypes.TRUE) + .end(); + } + PsiType throwable = JavaPsiFacade.getElementFactory(call.getProject()) + .createTypeByFQClassName("org.opentest4j.MultipleFailuresError", call.getResolveScope()); + builder.push(result) + .ifConditionIs(true) + .doThrow(throwable) + .end() + .pushUnknown(); // void method result + return true; } - PsiExpression[] args = call.getArgumentList().getExpressions(); - for (int i = 0; i < args.length; i++) { - PsiExpression arg = args[i]; - if (i == 0 && TypeUtils.isJavaLangString(arg.getType())) { - builder.pushExpression(arg, NullabilityProblemKind.noProblem).pop(); - } else { - builder.evaluateFunction(arg); - } - } - DfaVariableValue result = builder.createTempVariable(PsiType.BOOLEAN); - builder.assignAndPop(result, DfTypes.FALSE); - for (int i = 0; i < args.length; i++) { - PsiExpression arg = args[i]; - if (i == 0 && TypeUtils.isJavaLangString(arg.getType())) { - continue; - } - builder - .doTry(call) - .invokeFunction(0, arg) - .catchAll() - .assignAndPop(result, DfTypes.TRUE) - .end(); - } - PsiType throwable = JavaPsiFacade.getElementFactory(call.getProject()) - .createTypeByFQClassName("org.opentest4j.MultipleFailuresError", call.getResolveScope()); - builder.push(result) - .ifConditionIs(true) - .doThrow(throwable) - .end() - .pushUnknown(); // void method result - return true; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ComparatorModel.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ComparatorModel.java index f381dbc4b6..bbb016039f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ComparatorModel.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ComparatorModel.java @@ -33,139 +33,144 @@ * Simplified model for comparator: does not perform actual comparison, just executes key extractors, etc. */ abstract class ComparatorModel { - private static final CallMatcher KEY_EXTRACTOR = - anyOf(staticCall(JAVA_UTIL_COMPARATOR, "comparing", "comparingInt", "comparingLong", "comparingDouble").parameterCount(1), - staticCall(JAVA_UTIL_COMPARATOR, "comparing").parameterCount(2)); - private static final CallMatcher NULL_HOSTILE = anyOf(staticCall(JAVA_UTIL_COMPARATOR, "naturalOrder", "reverseOrder").parameterCount(0), - staticCall(JAVA_UTIL_COLLECTIONS, "reverseOrder").parameterCount(0)); - private static final CallMatcher NULL_FRIENDLY = staticCall(JAVA_UTIL_COMPARATOR, "nullsFirst", "nullsLast").parameterCount(1); - private static final CallMatcher REVERSED = instanceCall(JAVA_UTIL_COMPARATOR, "reversed").parameterCount(0); - private static final CallMatcher REVERSE_ORDER = staticCall(JAVA_UTIL_COLLECTIONS, "reverseOrder").parameterCount(1); + private static final CallMatcher KEY_EXTRACTOR = anyOf( + staticCall(JAVA_UTIL_COMPARATOR, "comparing", "comparingInt", "comparingLong", "comparingDouble").parameterCount(1), + staticCall(JAVA_UTIL_COMPARATOR, "comparing").parameterCount(2) + ); + private static final CallMatcher NULL_HOSTILE = anyOf( + staticCall(JAVA_UTIL_COMPARATOR, "naturalOrder", "reverseOrder").parameterCount(0), + staticCall(JAVA_UTIL_COLLECTIONS, "reverseOrder").parameterCount(0) + ); + private static final CallMatcher NULL_FRIENDLY = staticCall(JAVA_UTIL_COMPARATOR, "nullsFirst", "nullsLast").parameterCount(1); + private static final CallMatcher REVERSED = instanceCall(JAVA_UTIL_COMPARATOR, "reversed").parameterCount(0); + private static final CallMatcher REVERSE_ORDER = staticCall(JAVA_UTIL_COLLECTIONS, "reverseOrder").parameterCount(1); - private final boolean myFailsOnNull; + private final boolean myFailsOnNull; - protected ComparatorModel(boolean failsOnNull) { - myFailsOnNull = failsOnNull; - } - - abstract void evaluate(CFGBuilder builder); + protected ComparatorModel(boolean failsOnNull) { + myFailsOnNull = failsOnNull; + } - abstract void invoke(CFGBuilder builder); + abstract void evaluate(CFGBuilder builder); - boolean failsOnNull() { - return myFailsOnNull; - } + abstract void invoke(CFGBuilder builder); - private static class NullHostile extends ComparatorModel { - NullHostile() { - super(true); + boolean failsOnNull() { + return myFailsOnNull; } - @Override - void evaluate(CFGBuilder builder) { - } + private static class NullHostile extends ComparatorModel { + NullHostile() { + super(true); + } - @Override - void invoke(CFGBuilder builder) { - builder.pop(); + @Override + void evaluate(CFGBuilder builder) { + } + + @Override + void invoke(CFGBuilder builder) { + builder.pop(); + } } - } - private static class Unknown extends ComparatorModel { - private final PsiExpression myExpression; + private static class Unknown extends ComparatorModel { + private final PsiExpression myExpression; - Unknown(PsiExpression expression) { - super(false); - myExpression = expression; - } + Unknown(PsiExpression expression) { + super(false); + myExpression = expression; + } - @Override - void evaluate(CFGBuilder builder) { - builder.evaluateFunction(myExpression); - } + @Override + void evaluate(CFGBuilder builder) { + builder.evaluateFunction(myExpression); + } - @Override - void invoke(CFGBuilder builder) { - builder.pushUnknown().invokeFunction(2, myExpression).pop(); + @Override + void invoke(CFGBuilder builder) { + builder.pushUnknown().invokeFunction(2, myExpression).pop(); + } } - } - private static class NullFriendly extends ComparatorModel { - private final ComparatorModel myDownstream; + private static class NullFriendly extends ComparatorModel { + private final ComparatorModel myDownstream; - NullFriendly(ComparatorModel downstream) { - super(false); - myDownstream = downstream; - } + NullFriendly(ComparatorModel downstream) { + super(false); + myDownstream = downstream; + } - @Override - void evaluate(CFGBuilder builder) { - myDownstream.evaluate(builder); - } + @Override + void evaluate(CFGBuilder builder) { + myDownstream.evaluate(builder); + } - @Override - void invoke(CFGBuilder builder) { - builder.dup().ifNotNull().chain(myDownstream::invoke).elseBranch().pop().end(); + @Override + void invoke(CFGBuilder builder) { + builder.dup().ifNotNull().chain(myDownstream::invoke).elseBranch().pop().end(); + } } - } - private static class KeyExtractor extends ComparatorModel { - private final PsiExpression myKeyExtractor; - private final ComparatorModel myDownstream; + private static class KeyExtractor extends ComparatorModel { + private final PsiExpression myKeyExtractor; + private final ComparatorModel myDownstream; - private KeyExtractor(PsiExpression keyExtractor, ComparatorModel downstream) { - super(false); - myKeyExtractor = keyExtractor; - myDownstream = downstream; - } + private KeyExtractor(PsiExpression keyExtractor, ComparatorModel downstream) { + super(false); + myKeyExtractor = keyExtractor; + myDownstream = downstream; + } - @Override - void evaluate(CFGBuilder builder) { - builder.evaluateFunction(myKeyExtractor); - myDownstream.evaluate(builder); - } + @Override + void evaluate(CFGBuilder builder) { + builder.evaluateFunction(myKeyExtractor); + myDownstream.evaluate(builder); + } - @Override - void invoke(CFGBuilder builder) { - builder.invokeFunction(1, myKeyExtractor, myDownstream.myFailsOnNull ? Nullability.NOT_NULL : Nullability.UNKNOWN) - .chain(myDownstream::invoke); + @Override + void invoke(CFGBuilder builder) { + builder.invokeFunction(1, myKeyExtractor, myDownstream.myFailsOnNull ? Nullability.NOT_NULL : Nullability.UNKNOWN) + .chain(myDownstream::invoke); + } } - } - @Nonnull - static ComparatorModel from(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression == null || NULL_HOSTILE.matches(expression)) { - return new NullHostile(); - } - if (expression instanceof PsiReferenceExpression) { - PsiReferenceExpression ref = (PsiReferenceExpression) expression; - if ("CASE_INSENSITIVE_ORDER".equals(ref.getReferenceName())) { - PsiField field = ObjectUtil.tryCast(ref.resolve(), PsiField.class); - if (field != null && field.getContainingClass() != null && - CommonClassNames.JAVA_LANG_STRING.equals(field.getContainingClass().getQualifiedName())) { - return new NullHostile(); - } - } - } - PsiMethodCallExpression call = ObjectUtil.tryCast(expression, PsiMethodCallExpression.class); - if (call == null) return new Unknown(expression); - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if (REVERSED.test(call) && qualifier != null) { - return from(qualifier); - } - if (REVERSE_ORDER.test(call)) { - return from(call.getArgumentList().getExpressions()[0]); - } - if (NULL_FRIENDLY.test(call) && qualifier != null) { - return new NullFriendly(from(qualifier)); - } - if (KEY_EXTRACTOR.test(call)) { - PsiExpression[] args = call.getArgumentList().getExpressions(); - PsiExpression keyExtractor = args[0]; - ComparatorModel downstream = args.length == 2 ? from(args[1]) : new NullHostile(); - return new KeyExtractor(keyExtractor, downstream); + @Nonnull + static ComparatorModel from(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression == null || NULL_HOSTILE.matches(expression)) { + return new NullHostile(); + } + if (expression instanceof PsiReferenceExpression) { + PsiReferenceExpression ref = (PsiReferenceExpression)expression; + if ("CASE_INSENSITIVE_ORDER".equals(ref.getReferenceName())) { + PsiField field = ObjectUtil.tryCast(ref.resolve(), PsiField.class); + if (field != null && field.getContainingClass() != null && + CommonClassNames.JAVA_LANG_STRING.equals(field.getContainingClass().getQualifiedName())) { + return new NullHostile(); + } + } + } + PsiMethodCallExpression call = ObjectUtil.tryCast(expression, PsiMethodCallExpression.class); + if (call == null) { + return new Unknown(expression); + } + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (REVERSED.test(call) && qualifier != null) { + return from(qualifier); + } + if (REVERSE_ORDER.test(call)) { + return from(call.getArgumentList().getExpressions()[0]); + } + if (NULL_FRIENDLY.test(call) && qualifier != null) { + return new NullFriendly(from(qualifier)); + } + if (KEY_EXTRACTOR.test(call)) { + PsiExpression[] args = call.getArgumentList().getExpressions(); + PsiExpression keyExtractor = args[0]; + ComparatorModel downstream = args.length == 2 ? from(args[1]) : new NullHostile(); + return new KeyExtractor(keyExtractor, downstream); + } + return new Unknown(expression); } - return new Unknown(expression); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/MapUpdateInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/MapUpdateInliner.java index 0aa64cacff..3095779e7f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/MapUpdateInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/MapUpdateInliner.java @@ -17,132 +17,109 @@ import java.util.Objects; -public class MapUpdateInliner implements CallInliner -{ - private static final CallMatcher MAP_COMPUTE = CallMatcher.instanceCall( - CommonClassNames.JAVA_UTIL_MAP, "computeIfAbsent", "computeIfPresent", "compute").parameterCount(2); - private static final CallMatcher MAP_MERGE = CallMatcher.instanceCall( - CommonClassNames.JAVA_UTIL_MAP, "merge").parameterCount(3); +public class MapUpdateInliner implements CallInliner { + private static final CallMatcher MAP_COMPUTE = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "computeIfAbsent", "computeIfPresent", "compute") + .parameterCount(2); + private static final CallMatcher MAP_MERGE = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "merge").parameterCount(3); - @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) - { - if(MAP_COMPUTE.test(call)) - { - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if(qualifier == null) - { - return false; - } - PsiType type = call.getType(); - if(type == null) - { - return false; - } - PsiExpression[] args = call.getArgumentList().getExpressions(); - PsiExpression key = args[0]; - PsiExpression function = args[1]; - builder - .pushExpression(qualifier) // stack: .. qualifier - .pushExpression(key) // stack: .. qualifier; key - .evaluateFunction(function); - String name = Objects.requireNonNull(call.getMethodExpression().getReferenceName()); - switch(name) - { - case "computeIfAbsent": - inlineComputeIfAbsent(builder, function, type); - break; - case "computeIfPresent": - inlineComputeIfPresent(builder, function, type); - break; - case "compute": - inlineCompute(builder, function, type); - break; - default: - throw new IllegalStateException("Unsupported name: " + name); - } - builder.resultOf(call); - return true; - } - if(MAP_MERGE.test(call)) - { - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if(qualifier == null) - { - return false; - } - PsiType type = call.getType(); - if(type == null) - { - return false; - } - PsiExpression[] args = call.getArgumentList().getExpressions(); - PsiExpression key = args[0]; - PsiExpression value = args[1]; - PsiExpression function = args[2]; - builder - .pushExpression(qualifier) // stack: .. qualifier - .pushExpression(key) // stack: .. qualifier; key - .pop() // stack: .. qualifier - .pushExpression(value) // stack: .. qualifier; value - .boxUnbox(value, ExpectedTypeUtils.findExpectedType(value, false)) - .evaluateFunction(function) - .pushUnknown() // stack: .. qualifier; value; get() result - .ifNotNull() // stack: .. qualifier; value - .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) // stack: .. qualifier; value; get() result - .swap() // stack: .. qualifier; get() result; value - .invokeFunction(2, function) // stack: .. qualifier; mapping result - .end() - .chain(b -> flushSize(b)) - .resultOf(call); - return true; - } - return false; - } + @Override + public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + if (MAP_COMPUTE.test(call)) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier == null) { + return false; + } + PsiType type = call.getType(); + if (type == null) { + return false; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + PsiExpression key = args[0]; + PsiExpression function = args[1]; + builder.pushExpression(qualifier) // stack: .. qualifier + .pushExpression(key) // stack: .. qualifier; key + .evaluateFunction(function); + String name = Objects.requireNonNull(call.getMethodExpression().getReferenceName()); + switch (name) { + case "computeIfAbsent": + inlineComputeIfAbsent(builder, function, type); + break; + case "computeIfPresent": + inlineComputeIfPresent(builder, function, type); + break; + case "compute": + inlineCompute(builder, function, type); + break; + default: + throw new IllegalStateException("Unsupported name: " + name); + } + builder.resultOf(call); + return true; + } + if (MAP_MERGE.test(call)) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier == null) { + return false; + } + PsiType type = call.getType(); + if (type == null) { + return false; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + PsiExpression key = args[0]; + PsiExpression value = args[1]; + PsiExpression function = args[2]; + builder.pushExpression(qualifier) // stack: .. qualifier + .pushExpression(key) // stack: .. qualifier; key + .pop() // stack: .. qualifier + .pushExpression(value) // stack: .. qualifier; value + .boxUnbox(value, ExpectedTypeUtils.findExpectedType(value, false)) + .evaluateFunction(function) + .pushUnknown() // stack: .. qualifier; value; get() result + .ifNotNull() // stack: .. qualifier; value + .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) // stack: .. qualifier; value; get() result + .swap() // stack: .. qualifier; get() result; value + .invokeFunction(2, function) // stack: .. qualifier; mapping result + .end() + .chain(b -> flushSize(b)) + .resultOf(call); + return true; + } + return false; + } - private static void flushSize(CFGBuilder builder) - { - builder.swap().unwrap(SpecialField.COLLECTION_SIZE).pushUnknown().assign().pop(); - } + private static void flushSize(CFGBuilder builder) { + builder.swap().unwrap(SpecialField.COLLECTION_SIZE).pushUnknown().assign().pop(); + } - private static void inlineComputeIfAbsent(@Nonnull CFGBuilder builder, - PsiExpression function, - PsiType type) - { - builder - .pushUnknown() // stack: .. qualifier; key; get() result - .ifNull() // stack: .. qualifier; key - .invokeFunction(1, function) // stack: .. qualifier; mapping_result - .chain(MapUpdateInliner::flushSize) - .elseBranch() - .splice(2) - .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) - .end(); - } + private static void inlineComputeIfAbsent(@Nonnull CFGBuilder builder, PsiExpression function, PsiType type) { + builder.pushUnknown() // stack: .. qualifier; key; get() result + .ifNull() // stack: .. qualifier; key + .invokeFunction(1, function) // stack: .. qualifier; mapping_result + .chain(MapUpdateInliner::flushSize) + .elseBranch() + .splice(2) + .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) + .end(); + } - private static void inlineComputeIfPresent(@Nonnull CFGBuilder builder, - PsiExpression function, - PsiType type) - { - builder - .pushUnknown() // stack: .. qualifier; key; get() result - .ifNotNull() // stack: .. qualifier; key - .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) - .invokeFunction(2, function) // stack: .. qualifier; mapping result - .chain(MapUpdateInliner::flushSize) - .elseBranch() - .splice(2) - .pushNull() - .end(); - } + private static void inlineComputeIfPresent(@Nonnull CFGBuilder builder, PsiExpression function, PsiType type) { + builder.pushUnknown() // stack: .. qualifier; key; get() result + .ifNotNull() // stack: .. qualifier; key + .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) + .invokeFunction(2, function) // stack: .. qualifier; mapping result + .chain(MapUpdateInliner::flushSize) + .elseBranch() + .splice(2) + .pushNull() + .end(); + } - private static void inlineCompute(@Nonnull CFGBuilder builder, - PsiExpression function, - PsiType type) - { - builder - .push(DfTypes.typedObject(type, Nullability.NULLABLE)) - .invokeFunction(2, function) // stack: .. qualifier; mapping result - .chain(MapUpdateInliner::flushSize); - } + private static void inlineCompute(@Nonnull CFGBuilder builder, PsiExpression function, PsiType type) { + builder.push(DfTypes.typedObject(type, Nullability.NULLABLE)) + .invokeFunction(2, function) // stack: .. qualifier; mapping result + .chain(MapUpdateInliner::flushSize); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/TransformInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/TransformInliner.java index e19adcdc38..efbcda15b8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/TransformInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/TransformInliner.java @@ -8,42 +8,38 @@ import com.siyeh.ig.callMatcher.CallMatcher; import jakarta.annotation.Nonnull; -public class TransformInliner implements CallInliner -{ - private static final CallMatcher TRANSFORM_METHODS = CallMatcher.anyOf( - CallMatcher.instanceCall("reactor.core.publisher.Mono", "as").parameterCount(1), - CallMatcher.instanceCall("reactor.core.publisher.Flux", "as").parameterCount(1), - CallMatcher.instanceCall("reactor.core.publisher.ParallelFlux", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Completable", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Flowable", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Maybe", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Observable", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Single", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Completable", "to").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Flowable", "to").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Maybe", "to").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Observable", "to").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Single", "to").parameterCount(1), - CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "transform").parameterCount(1), - CallMatcher.instanceCall("one.util.streamex.BaseStreamEx", "chain").parameterCount(1) - ); +public class TransformInliner implements CallInliner { + private static final CallMatcher TRANSFORM_METHODS = CallMatcher.anyOf( + CallMatcher.instanceCall("reactor.core.publisher.Mono", "as").parameterCount(1), + CallMatcher.instanceCall("reactor.core.publisher.Flux", "as").parameterCount(1), + CallMatcher.instanceCall("reactor.core.publisher.ParallelFlux", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Completable", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Flowable", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Maybe", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Observable", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Single", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Completable", "to").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Flowable", "to").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Maybe", "to").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Observable", "to").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Single", "to").parameterCount(1), + CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "transform").parameterCount(1), + CallMatcher.instanceCall("one.util.streamex.BaseStreamEx", "chain").parameterCount(1) + ); - @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) - { - if(TRANSFORM_METHODS.test(call)) - { - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if(qualifier != null) - { - PsiExpression fn = call.getArgumentList().getExpressions()[0]; - builder.pushExpression(qualifier) - .evaluateFunction(fn) - .invokeFunction(1, fn) - .resultOf(call); - return true; - } - } - return false; - } + @Override + public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + if (TRANSFORM_METHODS.test(call)) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier != null) { + PsiExpression fn = call.getArgumentList().getExpressions()[0]; + builder.pushExpression(qualifier) + .evaluateFunction(fn) + .invokeFunction(1, fn) + .resultOf(call); + return true; + } + } + return false; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaExpressionFactory.java index 657a049e31..b7df248287 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaExpressionFactory.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaExpressionFactory.java @@ -26,6 +26,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -38,542 +39,563 @@ */ public class DfaExpressionFactory { - private final DfaValueFactory myFactory; - private final Map myArrayIndices = new HashMap<>(); - - DfaExpressionFactory(DfaValueFactory factory) { - myFactory = factory; - } + private final DfaValueFactory myFactory; + private final Map myArrayIndices = new HashMap<>(); - @Nullable - @Contract("null -> null") - DfaValue getExpressionDfaValue(@Nullable PsiExpression expression) { - if (expression == null) { - return null; + DfaExpressionFactory(DfaValueFactory factory) { + myFactory = factory; } - if (expression instanceof PsiParenthesizedExpression) { - return getExpressionDfaValue(((PsiParenthesizedExpression) expression).getExpression()); - } + @Nullable + @Contract("null -> null") + DfaValue getExpressionDfaValue(@Nullable PsiExpression expression) { + if (expression == null) { + return null; + } - if (expression instanceof PsiArrayAccessExpression) { - PsiExpression arrayExpression = ((PsiArrayAccessExpression) expression).getArrayExpression(); - DfaValue qualifier = getQualifierValue(arrayExpression); - if (qualifier != null) { - Object index = ExpressionUtils.computeConstantExpression(((PsiArrayAccessExpression) expression).getIndexExpression()); - if (index instanceof Integer) { - DfaValue arrayElementValue = getArrayElementValue(qualifier, (Integer) index); - if (arrayElementValue != null) { - return arrayElementValue; - } - } - } - PsiType type = expression.getType(); - if (type != null) { - return myFactory.getObjectType(type, DfaPsiUtil.getElementNullability(type, null)); - } - } + if (expression instanceof PsiParenthesizedExpression) { + return getExpressionDfaValue(((PsiParenthesizedExpression)expression).getExpression()); + } - if (expression instanceof PsiMethodCallExpression) { - return createReferenceValue(((PsiMethodCallExpression) expression).getMethodExpression()); - } + if (expression instanceof PsiArrayAccessExpression) { + PsiExpression arrayExpression = ((PsiArrayAccessExpression)expression).getArrayExpression(); + DfaValue qualifier = getQualifierValue(arrayExpression); + if (qualifier != null) { + Object index = ExpressionUtils.computeConstantExpression(((PsiArrayAccessExpression)expression).getIndexExpression()); + if (index instanceof Integer) { + DfaValue arrayElementValue = getArrayElementValue(qualifier, (Integer)index); + if (arrayElementValue != null) { + return arrayElementValue; + } + } + } + PsiType type = expression.getType(); + if (type != null) { + return myFactory.getObjectType(type, DfaPsiUtil.getElementNullability(type, null)); + } + } - if (expression instanceof PsiReferenceExpression) { - return createReferenceValue((PsiReferenceExpression) expression); - } + if (expression instanceof PsiMethodCallExpression) { + return createReferenceValue(((PsiMethodCallExpression)expression).getMethodExpression()); + } - if (expression instanceof PsiLiteralExpression) { - return myFactory.fromDfType(DfaPsiUtil.fromLiteral((PsiLiteralExpression) expression)); - } + if (expression instanceof PsiReferenceExpression) { + return createReferenceValue((PsiReferenceExpression)expression); + } - if (expression instanceof PsiNewExpression || expression instanceof PsiLambdaExpression) { - return myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL); - } + if (expression instanceof PsiLiteralExpression) { + return myFactory.fromDfType(DfaPsiUtil.fromLiteral((PsiLiteralExpression)expression)); + } - final Object value = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false); - if (value != null) { - PsiType type = expression.getType(); - if (type != null) { - return myFactory.getConstant(value, type); - } - } + if (expression instanceof PsiNewExpression || expression instanceof PsiLambdaExpression) { + return myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL); + } - if (expression instanceof PsiThisExpression || expression instanceof PsiSuperExpression) { - PsiJavaCodeReferenceElement qualifier = ((PsiQualifiedExpression) expression).getQualifier(); - PsiClass target; - if (qualifier != null) { - target = ObjectUtil.tryCast(qualifier.resolve(), PsiClass.class); - } else { - target = ClassUtils.getContainingClass(expression); - } - return target == null - ? myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL) - : myFactory.getVarFactory().createThisValue(target); - } - return null; - } - - private DfaValue createReferenceValue(@Nonnull PsiReferenceExpression refExpr) { - PsiElement target = refExpr.resolve(); - if (target instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) target; - if (!PsiUtil.isAccessedForWriting(refExpr)) { - DfaValue constValue = myFactory.getConstantFromVariable(variable); - if (constValue != null && !maybeUninitializedConstant(constValue, refExpr, variable)) { - return constValue; - } - } - } - VariableDescriptor var = getAccessedVariableOrGetter(target); - if (var == null) { - return null; - } + final Object value = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false); + if (value != null) { + PsiType type = expression.getType(); + if (type != null) { + return myFactory.getConstant(value, type); + } + } - DfaValue qualifier = getQualifierOrThisValue(refExpr); - DfaValue result = var.createValue(myFactory, qualifier, true); - if (var instanceof SpecialField) { - PsiType wantedType = refExpr.getType(); - result = DfaUtil.boxUnbox(result, wantedType); - } - return result; - } - - /** - * Returns a DFA variable which represents the qualifier for given reference if possible. For unqualified reference - * to a non-static member, a variable which represents the corresponding {@code this} may be returned - * - * @param refExpr reference to create a qualifier variable for - * @return a qualifier variable or null if qualifier is unnecessary or cannot be represented as a variable - */ - @Nullable - public DfaValue getQualifierOrThisValue(PsiReferenceExpression refExpr) { - PsiExpression qualifierExpression = refExpr.getQualifierExpression(); - if (qualifierExpression == null) { - PsiElement element = refExpr.resolve(); - if (element instanceof PsiMember && !((PsiMember) element).hasModifierProperty(PsiModifier.STATIC)) { - PsiClass currentClass; - currentClass = ClassUtils.getContainingClass(refExpr); - PsiClass memberClass = ((PsiMember) element).getContainingClass(); - if (memberClass != null && currentClass != null) { - PsiClass target; - if (currentClass == memberClass || InheritanceUtil.isInheritorOrSelf(currentClass, memberClass, true)) { - target = currentClass; - } else { - target = memberClass; - } - return myFactory.getVarFactory().createThisValue(target); - } - } - } - return getQualifierValue(qualifierExpression); - } - - @Nullable - private DfaValue getQualifierValue(PsiExpression qualifierExpression) { - DfaValue qualifierValue = getExpressionDfaValue(qualifierExpression); - if (qualifierValue == null) { - return null; - } - PsiVariable constVar = DfConstantType.getConstantOfType(qualifierValue.getDfType(), PsiVariable.class); - if (constVar != null) { - return myFactory.getVarFactory().createVariableValue(constVar); - } - return qualifierValue; - } - - private static boolean maybeUninitializedConstant(DfaValue constValue, - @Nonnull PsiReferenceExpression refExpr, - PsiModifierListOwner var) { - // If static final field is referred from the same or inner/nested class, - // we consider that it might be uninitialized yet as some class initializers may call its methods or - // even instantiate objects of this class and call their methods - if (!DfConstantType.isConst(constValue.getDfType(), var)) { - return false; - } - if (!(var instanceof PsiField) || var instanceof PsiEnumConstant) { - return false; - } - return PsiTreeUtil.getTopmostParentOfType(refExpr, PsiClass.class) == PsiTreeUtil.getTopmostParentOfType(var, PsiClass.class); - } - - @Contract("null -> null") - @Nullable - public static VariableDescriptor getAccessedVariableOrGetter(final PsiElement target) { - SpecialField sf = SpecialField.findSpecialField(target); - if (sf != null) { - return sf; - } - if (target instanceof PsiVariable) { - return new PlainDescriptor((PsiVariable) target); - } - if (target instanceof PsiMethod) { - PsiMethod method = (PsiMethod) target; - if (method.getParameterList().isEmpty() && - (PropertyUtilBase.isSimplePropertyGetter(method) || JavaMethodContractUtil.isPure(method) || isClassAnnotatedImmutable(method)) && - isContractAllowedForGetter(method)) { - return new GetterDescriptor(method); - } - } - return null; - } - - private static boolean isClassAnnotatedImmutable(PsiMethod method) { - List annotations = ConcurrencyAnnotationsManager.getInstance(method.getProject()).getImmutableAnnotations(); - return AnnotationUtil.findAnnotation(method.getContainingClass(), annotations) != null; - } - - private static boolean isContractAllowedForGetter(PsiMethod method) { - List contracts = JavaMethodContractUtil.getMethodCallContracts(method, null); - if (contracts.size() == 1) { - MethodContract contract = contracts.get(0); - return contract.isTrivial() && contract.getReturnValue().equals(ContractReturnValue.returnNew()); + if (expression instanceof PsiThisExpression || expression instanceof PsiSuperExpression) { + PsiJavaCodeReferenceElement qualifier = ((PsiQualifiedExpression)expression).getQualifier(); + PsiClass target; + if (qualifier != null) { + target = ObjectUtil.tryCast(qualifier.resolve(), PsiClass.class); + } + else { + target = ClassUtils.getContainingClass(expression); + } + return target == null + ? myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL) + : myFactory.getVarFactory().createThisValue(target); + } + return null; } - return contracts.isEmpty(); - } - @Nonnull - private DfaValue getAdvancedExpressionDfaValue(@Nullable PsiExpression expression, @Nullable PsiType targetType) { - if (expression == null) { - return myFactory.getUnknown(); - } - DfaValue value = getExpressionDfaValue(expression); - if (value != null) { - return DfaUtil.boxUnbox(value, targetType); - } - if (expression instanceof PsiConditionalExpression) { - return getAdvancedExpressionDfaValue(((PsiConditionalExpression) expression).getThenExpression(), targetType).unite( - getAdvancedExpressionDfaValue(((PsiConditionalExpression) expression).getElseExpression(), targetType)); - } - PsiType type = expression.getType(); - if (expression instanceof PsiArrayInitializerExpression) { - int length = ((PsiArrayInitializerExpression) expression).getInitializers().length; - return myFactory.fromDfType(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(length), type)); - } - DfType dfType = DfTypes.typedObject(type, NullabilityUtil.getExpressionNullability(expression)); - if (type instanceof PsiPrimitiveType && targetType instanceof PsiPrimitiveType && !type.equals(targetType)) { - if (TypeConversionUtil.isIntegralNumberType(targetType)) { - LongRangeSet range = DfLongType.extractRange(dfType); - return myFactory.fromDfType(rangeClamped(range.castTo((PsiPrimitiveType) targetType), PsiType.LONG.equals(targetType))); - } - return myFactory.fromDfType(DfTypes.typedObject(targetType, Nullability.UNKNOWN)); - } - return DfaUtil.boxUnbox(myFactory.fromDfType(dfType), targetType); - } + private DfaValue createReferenceValue(@Nonnull PsiReferenceExpression refExpr) { + PsiElement target = refExpr.resolve(); + if (target instanceof PsiVariable) { + PsiVariable variable = (PsiVariable)target; + if (!PsiUtil.isAccessedForWriting(refExpr)) { + DfaValue constValue = myFactory.getConstantFromVariable(variable); + if (constValue != null && !maybeUninitializedConstant(constValue, refExpr, variable)) { + return constValue; + } + } + } + VariableDescriptor var = getAccessedVariableOrGetter(target); + if (var == null) { + return null; + } - @Nonnull - public DfaValue getArrayElementValue(DfaValue array, LongRangeSet indexSet) { - if (!(array instanceof DfaVariableValue)) { - return myFactory.getUnknown(); + DfaValue qualifier = getQualifierOrThisValue(refExpr); + DfaValue result = var.createValue(myFactory, qualifier, true); + if (var instanceof SpecialField) { + PsiType wantedType = refExpr.getType(); + result = DfaUtil.boxUnbox(result, wantedType); + } + return result; } - if (indexSet.isEmpty()) { - return myFactory.getUnknown(); + + /** + * Returns a DFA variable which represents the qualifier for given reference if possible. For unqualified reference + * to a non-static member, a variable which represents the corresponding {@code this} may be returned + * + * @param refExpr reference to create a qualifier variable for + * @return a qualifier variable or null if qualifier is unnecessary or cannot be represented as a variable + */ + @Nullable + public DfaValue getQualifierOrThisValue(PsiReferenceExpression refExpr) { + PsiExpression qualifierExpression = refExpr.getQualifierExpression(); + if (qualifierExpression == null) { + PsiElement element = refExpr.resolve(); + if (element instanceof PsiMember && !((PsiMember)element).hasModifierProperty(PsiModifier.STATIC)) { + PsiClass currentClass; + currentClass = ClassUtils.getContainingClass(refExpr); + PsiClass memberClass = ((PsiMember)element).getContainingClass(); + if (memberClass != null && currentClass != null) { + PsiClass target; + if (currentClass == memberClass || InheritanceUtil.isInheritorOrSelf(currentClass, memberClass, true)) { + target = currentClass; + } + else { + target = memberClass; + } + return myFactory.getVarFactory().createThisValue(target); + } + } + } + return getQualifierValue(qualifierExpression); } - long min = indexSet.min(); - long max = indexSet.max(); - if (min == max && min >= 0 && min < Integer.MAX_VALUE) { - DfaValue value = getArrayElementValue(array, (int) min); - return value == null ? myFactory.getUnknown() : value; + + @Nullable + private DfaValue getQualifierValue(PsiExpression qualifierExpression) { + DfaValue qualifierValue = getExpressionDfaValue(qualifierExpression); + if (qualifierValue == null) { + return null; + } + PsiVariable constVar = DfConstantType.getConstantOfType(qualifierValue.getDfType(), PsiVariable.class); + if (constVar != null) { + return myFactory.getVarFactory().createVariableValue(constVar); + } + return qualifierValue; + } + + private static boolean maybeUninitializedConstant( + DfaValue constValue, + @Nonnull PsiReferenceExpression refExpr, + PsiModifierListOwner var + ) { + // If static final field is referred from the same or inner/nested class, + // we consider that it might be uninitialized yet as some class initializers may call its methods or + // even instantiate objects of this class and call their methods + if (!DfConstantType.isConst(constValue.getDfType(), var)) { + return false; + } + if (!(var instanceof PsiField) || var instanceof PsiEnumConstant) { + return false; + } + return PsiTreeUtil.getTopmostParentOfType(refExpr, PsiClass.class) == PsiTreeUtil.getTopmostParentOfType(var, PsiClass.class); } - DfaVariableValue arrayDfaVar = (DfaVariableValue) array; - PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable(); - if (!(arrayPsiVar instanceof PsiVariable)) { - return myFactory.getUnknown(); + + @Contract("null -> null") + @Nullable + public static VariableDescriptor getAccessedVariableOrGetter(final PsiElement target) { + SpecialField sf = SpecialField.findSpecialField(target); + if (sf != null) { + return sf; + } + if (target instanceof PsiVariable) { + return new PlainDescriptor((PsiVariable)target); + } + if (target instanceof PsiMethod) { + PsiMethod method = (PsiMethod)target; + if (method.getParameterList().isEmpty() + && ( + PropertyUtilBase.isSimplePropertyGetter(method) + || JavaMethodContractUtil.isPure(method) + || isClassAnnotatedImmutable(method) + ) + && isContractAllowedForGetter(method)) { + return new GetterDescriptor(method); + } + } + return null; } - PsiType arrayType = ((PsiVariable) arrayPsiVar).getType(); - PsiType targetType = arrayType instanceof PsiArrayType ? ((PsiArrayType) arrayType).getComponentType() : null; - PsiExpression[] elements = ExpressionUtils.getConstantArrayElements((PsiVariable) arrayPsiVar); - if (elements == null || elements.length == 0) { - return myFactory.getUnknown(); + + private static boolean isClassAnnotatedImmutable(PsiMethod method) { + List annotations = ConcurrencyAnnotationsManager.getInstance(method.getProject()).getImmutableAnnotations(); + return AnnotationUtil.findAnnotation(method.getContainingClass(), annotations) != null; } - indexSet = indexSet.intersect(LongRangeSet.range(0, elements.length - 1)); - if (indexSet.isEmpty() || indexSet.isCardinalityBigger(100)) { - return myFactory.getUnknown(); + + private static boolean isContractAllowedForGetter(PsiMethod method) { + List contracts = JavaMethodContractUtil.getMethodCallContracts(method, null); + if (contracts.size() == 1) { + MethodContract contract = contracts.get(0); + return contract.isTrivial() && contract.getReturnValue().equals(ContractReturnValue.returnNew()); + } + return contracts.isEmpty(); } - return LongStreamEx.of(indexSet.stream()) - .mapToObj(idx -> getAdvancedExpressionDfaValue(elements[(int) idx], targetType)) - .prefix(DfaValue::unite) - .takeWhileInclusive(value -> !DfaTypeValue.isUnknown(value)) - .reduce((a, b) -> b) - .orElseGet(myFactory::getUnknown); - } - - @Contract("null, _ -> null") - @Nullable - public DfaValue getArrayElementValue(DfaValue array, int index) { - if (!(array instanceof DfaVariableValue)) { - return null; + + @Nonnull + private DfaValue getAdvancedExpressionDfaValue(@Nullable PsiExpression expression, @Nullable PsiType targetType) { + if (expression == null) { + return myFactory.getUnknown(); + } + DfaValue value = getExpressionDfaValue(expression); + if (value != null) { + return DfaUtil.boxUnbox(value, targetType); + } + if (expression instanceof PsiConditionalExpression) { + return getAdvancedExpressionDfaValue(((PsiConditionalExpression)expression).getThenExpression(), targetType).unite( + getAdvancedExpressionDfaValue(((PsiConditionalExpression)expression).getElseExpression(), targetType)); + } + PsiType type = expression.getType(); + if (expression instanceof PsiArrayInitializerExpression) { + int length = ((PsiArrayInitializerExpression)expression).getInitializers().length; + return myFactory.fromDfType(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(length), type)); + } + DfType dfType = DfTypes.typedObject(type, NullabilityUtil.getExpressionNullability(expression)); + if (type instanceof PsiPrimitiveType && targetType instanceof PsiPrimitiveType && !type.equals(targetType)) { + if (TypeConversionUtil.isIntegralNumberType(targetType)) { + LongRangeSet range = DfLongType.extractRange(dfType); + return myFactory.fromDfType(rangeClamped(range.castTo((PsiPrimitiveType)targetType), PsiType.LONG.equals(targetType))); + } + return myFactory.fromDfType(DfTypes.typedObject(targetType, Nullability.UNKNOWN)); + } + return DfaUtil.boxUnbox(myFactory.fromDfType(dfType), targetType); } - DfaVariableValue arrayDfaVar = (DfaVariableValue) array; - PsiType type = arrayDfaVar.getType(); - if (!(type instanceof PsiArrayType)) { - return null; + + @Nonnull + public DfaValue getArrayElementValue(DfaValue array, LongRangeSet indexSet) { + if (!(array instanceof DfaVariableValue)) { + return myFactory.getUnknown(); + } + if (indexSet.isEmpty()) { + return myFactory.getUnknown(); + } + long min = indexSet.min(); + long max = indexSet.max(); + if (min == max && min >= 0 && min < Integer.MAX_VALUE) { + DfaValue value = getArrayElementValue(array, (int)min); + return value == null ? myFactory.getUnknown() : value; + } + DfaVariableValue arrayDfaVar = (DfaVariableValue)array; + PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable(); + if (!(arrayPsiVar instanceof PsiVariable)) { + return myFactory.getUnknown(); + } + PsiType arrayType = ((PsiVariable)arrayPsiVar).getType(); + PsiType targetType = arrayType instanceof PsiArrayType ? ((PsiArrayType)arrayType).getComponentType() : null; + PsiExpression[] elements = ExpressionUtils.getConstantArrayElements((PsiVariable)arrayPsiVar); + if (elements == null || elements.length == 0) { + return myFactory.getUnknown(); + } + indexSet = indexSet.intersect(LongRangeSet.range(0, elements.length - 1)); + if (indexSet.isEmpty() || indexSet.isCardinalityBigger(100)) { + return myFactory.getUnknown(); + } + return LongStreamEx.of(indexSet.stream()) + .mapToObj(idx -> getAdvancedExpressionDfaValue(elements[(int)idx], targetType)) + .prefix(DfaValue::unite) + .takeWhileInclusive(value -> !DfaTypeValue.isUnknown(value)) + .reduce((a, b) -> b) + .orElseGet(myFactory::getUnknown); } - PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable(); - if (arrayPsiVar instanceof PsiVariable) { - PsiExpression constantArrayElement = ExpressionUtils.getConstantArrayElement((PsiVariable) arrayPsiVar, index); - if (constantArrayElement != null) { - return getAdvancedExpressionDfaValue(constantArrayElement, ((PsiArrayType) type).getComponentType()); - } + + @Contract("null, _ -> null") + @Nullable + public DfaValue getArrayElementValue(DfaValue array, int index) { + if (!(array instanceof DfaVariableValue)) { + return null; + } + DfaVariableValue arrayDfaVar = (DfaVariableValue)array; + PsiType type = arrayDfaVar.getType(); + if (!(type instanceof PsiArrayType)) { + return null; + } + PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable(); + if (arrayPsiVar instanceof PsiVariable) { + PsiExpression constantArrayElement = ExpressionUtils.getConstantArrayElement((PsiVariable)arrayPsiVar, index); + if (constantArrayElement != null) { + return getAdvancedExpressionDfaValue(constantArrayElement, ((PsiArrayType)type).getComponentType()); + } + } + ArrayElementDescriptor indexVariable = getArrayIndexVariable(index); + if (indexVariable == null) { + return null; + } + return indexVariable.createValue(myFactory, arrayDfaVar); } - ArrayElementDescriptor indexVariable = getArrayIndexVariable(index); - if (indexVariable == null) { - return null; + + @Nullable + private ArrayElementDescriptor getArrayIndexVariable(int index) { + if (index >= 0) { + return myArrayIndices.computeIfAbsent(index, ArrayElementDescriptor::new); + } + return null; } - return indexVariable.createValue(myFactory, arrayDfaVar); - } - @Nullable - private ArrayElementDescriptor getArrayIndexVariable(int index) { - if (index >= 0) { - return myArrayIndices.computeIfAbsent(index, ArrayElementDescriptor::new); + @Nonnull + private static PsiSubstitutor getSubstitutor(PsiElement member, @Nullable DfaVariableValue qualifier) { + if (member instanceof PsiMember && qualifier != null) { + PsiClass fieldClass = ((PsiMember)member).getContainingClass(); + PsiClassType classType = ObjectUtil.tryCast(qualifier.getType(), PsiClassType.class); + if (classType != null && InheritanceUtil.isInheritorOrSelf(classType.resolve(), fieldClass, true)) { + return TypeConversionUtil.getSuperClassSubstitutor(fieldClass, classType); + } + } + return PsiSubstitutor.EMPTY; } - return null; - } - - @Nonnull - private static PsiSubstitutor getSubstitutor(PsiElement member, @Nullable DfaVariableValue qualifier) { - if (member instanceof PsiMember && qualifier != null) { - PsiClass fieldClass = ((PsiMember) member).getContainingClass(); - PsiClassType classType = ObjectUtil.tryCast(qualifier.getType(), PsiClassType.class); - if (classType != null && InheritanceUtil.isInheritorOrSelf(classType.resolve(), fieldClass, true)) { - return TypeConversionUtil.getSuperClassSubstitutor(fieldClass, classType); - } + + public DfaVariableValue getAssertionsDisabledVariable() { + return myFactory.getVarFactory().createVariableValue(AssertionDisabledDescriptor.INSTANCE); } - return PsiSubstitutor.EMPTY; - } - public DfaVariableValue getAssertionsDisabledVariable() { - return myFactory.getVarFactory().createVariableValue(AssertionDisabledDescriptor.INSTANCE); - } + public static final class AssertionDisabledDescriptor implements VariableDescriptor { + static final AssertionDisabledDescriptor INSTANCE = new AssertionDisabledDescriptor(); - public static final class AssertionDisabledDescriptor implements VariableDescriptor { - static final AssertionDisabledDescriptor INSTANCE = new AssertionDisabledDescriptor(); + private AssertionDisabledDescriptor() { + } - private AssertionDisabledDescriptor() { - } + @Override + public boolean isStable() { + return true; + } - @Override - public boolean isStable() { - return true; - } + @Nonnull + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + return PsiType.BOOLEAN; + } - @Nonnull - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - return PsiType.BOOLEAN; + @Override + public String toString() { + return "$assertionsDisabled"; + } } - @Override - public String toString() { - return "$assertionsDisabled"; - } - } + static final class PlainDescriptor implements VariableDescriptor { + private final + @Nonnull + PsiVariable myVariable; - static final class PlainDescriptor implements VariableDescriptor { - private final - @Nonnull - PsiVariable myVariable; + PlainDescriptor(@Nonnull PsiVariable variable) { + myVariable = variable; + } - PlainDescriptor(@Nonnull PsiVariable variable) { - myVariable = variable; - } + @Nonnull + @Override + public String toString() { + return String.valueOf(myVariable.getName()); + } - @Nonnull - @Override - public String toString() { - return String.valueOf(myVariable.getName()); - } + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + PsiType type = myVariable.getType(); + if (type instanceof PsiEllipsisType) { + type = ((PsiEllipsisType)type).toArrayType(); + } + return getSubstitutor(myVariable, qualifier).substitute(type); + } - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - PsiType type = myVariable.getType(); - if (type instanceof PsiEllipsisType) { - type = ((PsiEllipsisType) type).toArrayType(); - } - return getSubstitutor(myVariable, qualifier).substitute(type); - } + @Override + public PsiVariable getPsiElement() { + return myVariable; + } - @Override - public PsiVariable getPsiElement() { - return myVariable; - } + @Override + public boolean isStable() { + return PsiUtil.isJvmLocalVariable(myVariable) || myVariable.hasModifierProperty(PsiModifier.FINAL); + } - @Override - public boolean isStable() { - return PsiUtil.isJvmLocalVariable(myVariable) || myVariable.hasModifierProperty(PsiModifier.FINAL); - } + @Nonnull + @Override + public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { + if (myVariable.hasModifierProperty(PsiModifier.VOLATILE)) { + PsiType type = getType(ObjectUtil.tryCast(qualifier, DfaVariableValue.class)); + return factory.getObjectType(type, DfaPsiUtil.getElementNullability(type, myVariable)); + } + if (PsiUtil.isJvmLocalVariable(myVariable) || + (myVariable instanceof PsiField && myVariable.hasModifierProperty(PsiModifier.STATIC) && + (!myVariable.hasModifierProperty(PsiModifier.FINAL) || !DfaUtil.hasInitializationHacks((PsiField)myVariable)))) { + return factory.getVarFactory().createVariableValue(this); + } + return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); + } - @Nonnull - @Override - public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { - if (myVariable.hasModifierProperty(PsiModifier.VOLATILE)) { - PsiType type = getType(ObjectUtil.tryCast(qualifier, DfaVariableValue.class)); - return factory.getObjectType(type, DfaPsiUtil.getElementNullability(type, myVariable)); - } - if (PsiUtil.isJvmLocalVariable(myVariable) || - (myVariable instanceof PsiField && myVariable.hasModifierProperty(PsiModifier.STATIC) && - (!myVariable.hasModifierProperty(PsiModifier.FINAL) || !DfaUtil.hasInitializationHacks((PsiField) myVariable)))) { - return factory.getVarFactory().createVariableValue(this); - } - return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); - } + @Override + public int hashCode() { + return Objects.hashCode(myVariable.getName()); + } - @Override - public int hashCode() { - return Objects.hashCode(myVariable.getName()); + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof PlainDescriptor && ((PlainDescriptor)obj).myVariable == myVariable; + } } - @Override - public boolean equals(Object obj) { - return obj == this || obj instanceof PlainDescriptor && ((PlainDescriptor) obj).myVariable == myVariable; - } - } - - public static final class GetterDescriptor implements VariableDescriptor { - private static final CallMatcher STABLE_METHODS = CallMatcher.anyOf( - CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0), - CallMatcher.instanceCall("java.lang.reflect.Member", "getName", "getModifiers", "getDeclaringClass", "isSynthetic"), - CallMatcher.instanceCall("java.lang.reflect.Executable", "getParameterCount", "isVarArgs"), - CallMatcher.instanceCall("java.lang.reflect.Field", "getType"), - CallMatcher.instanceCall("java.lang.reflect.Method", "getReturnType"), - CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_CLASS, "getName", "isInterface", "isArray", "isPrimitive", "isSynthetic", - "isAnonymousClass", "isLocalClass", "isMemberClass", "getDeclaringClass", "getEnclosingClass", - "getSimpleName", "getCanonicalName") - ); - private final - @Nonnull - PsiMethod myGetter; - private final boolean myStable; - - public GetterDescriptor(@Nonnull PsiMethod getter) { - myGetter = getter; - if (STABLE_METHODS.methodMatches(getter) || getter instanceof LightRecordMethod) { - myStable = true; - } else { - PsiField field = PsiUtil.canBeOverridden(getter) ? null : PropertyUtil.getFieldOfGetter(getter); - myStable = field != null && field.hasModifierProperty(PsiModifier.FINAL); - } - } + public static final class GetterDescriptor implements VariableDescriptor { + private static final CallMatcher STABLE_METHODS = CallMatcher.anyOf( + CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0), + CallMatcher.instanceCall("java.lang.reflect.Member", "getName", "getModifiers", "getDeclaringClass", "isSynthetic"), + CallMatcher.instanceCall("java.lang.reflect.Executable", "getParameterCount", "isVarArgs"), + CallMatcher.instanceCall("java.lang.reflect.Field", "getType"), + CallMatcher.instanceCall("java.lang.reflect.Method", "getReturnType"), + CallMatcher.instanceCall( + CommonClassNames.JAVA_LANG_CLASS, + "getName", + "isInterface", + "isArray", + "isPrimitive", + "isSynthetic", + "isAnonymousClass", + "isLocalClass", + "isMemberClass", + "getDeclaringClass", + "getEnclosingClass", + "getSimpleName", + "getCanonicalName" + ) + ); + private final + @Nonnull + PsiMethod myGetter; + private final boolean myStable; + + public GetterDescriptor(@Nonnull PsiMethod getter) { + myGetter = getter; + if (STABLE_METHODS.methodMatches(getter) || getter instanceof LightRecordMethod) { + myStable = true; + } + else { + PsiField field = PsiUtil.canBeOverridden(getter) ? null : PropertyUtil.getFieldOfGetter(getter); + myStable = field != null && field.hasModifierProperty(PsiModifier.FINAL); + } + } - @Nonnull - @Override - public String toString() { - return myGetter.getName(); - } + @Nonnull + @Override + public String toString() { + return myGetter.getName(); + } - @Nullable - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - return getSubstitutor(myGetter, qualifier).substitute(myGetter.getReturnType()); - } + @Nullable + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + return getSubstitutor(myGetter, qualifier).substitute(myGetter.getReturnType()); + } - @Nonnull - @Override - public PsiMethod getPsiElement() { - return myGetter; - } + @Nonnull + @Override + public PsiMethod getPsiElement() { + return myGetter; + } - @Override - public boolean isStable() { - return myStable; - } + @Override + public boolean isStable() { + return myStable; + } - @Override - public boolean isCall() { - return true; - } + @Override + public boolean isCall() { + return true; + } - @Nonnull - @Override - public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { - if (myGetter.hasModifierProperty(PsiModifier.STATIC)) { - return factory.getVarFactory().createVariableValue(this); - } - return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); - } + @Nonnull + @Override + public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { + if (myGetter.hasModifierProperty(PsiModifier.STATIC)) { + return factory.getVarFactory().createVariableValue(this); + } + return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); + } - @Override - public int hashCode() { - return Objects.hashCode(myGetter.getName()); - } + @Override + public int hashCode() { + return Objects.hashCode(myGetter.getName()); + } - @Override - public boolean equals(Object obj) { - return obj == this || (obj instanceof GetterDescriptor && ((GetterDescriptor) obj).myGetter == myGetter); + @Override + public boolean equals(Object obj) { + return obj == this || (obj instanceof GetterDescriptor && ((GetterDescriptor)obj).myGetter == myGetter); + } } - } - public static final class ArrayElementDescriptor implements VariableDescriptor { - private final int myIndex; + public static final class ArrayElementDescriptor implements VariableDescriptor { + private final int myIndex; - ArrayElementDescriptor(int index) { - myIndex = index; - } + ArrayElementDescriptor(int index) { + myIndex = index; + } - public int getIndex() { - return myIndex; - } + public int getIndex() { + return myIndex; + } - @Nullable - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - if (qualifier == null) { - return null; - } - PsiType qualifierType = qualifier.getType(); - return qualifierType instanceof PsiArrayType ? ((PsiArrayType) qualifierType).getComponentType() : null; - } + @Nullable + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + if (qualifier == null) { + return null; + } + PsiType qualifierType = qualifier.getType(); + return qualifierType instanceof PsiArrayType ? ((PsiArrayType)qualifierType).getComponentType() : null; + } - @Nonnull - @Override - public String toString() { - return "[" + myIndex + "]"; - } + @Nonnull + @Override + public String toString() { + return "[" + myIndex + "]"; + } - @Override - public boolean isStable() { - return false; + @Override + public boolean isStable() { + return false; + } } - } - public static final class ThisDescriptor implements VariableDescriptor { - @Nonnull - private final PsiClass myQualifier; + public static final class ThisDescriptor implements VariableDescriptor { + @Nonnull + private final PsiClass myQualifier; - ThisDescriptor(@Nonnull PsiClass qualifier) { - myQualifier = qualifier; - } + ThisDescriptor(@Nonnull PsiClass qualifier) { + myQualifier = qualifier; + } - @Nonnull - @Override - public String toString() { - return myQualifier.getName() + ".this"; - } + @Nonnull + @Override + public String toString() { + return myQualifier.getName() + ".this"; + } - @Nonnull - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - return new PsiImmediateClassType(myQualifier, PsiSubstitutor.EMPTY); - } + @Nonnull + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + return new PsiImmediateClassType(myQualifier, PsiSubstitutor.EMPTY); + } - @Override - public PsiClass getPsiElement() { - return myQualifier; - } + @Override + public PsiClass getPsiElement() { + return myQualifier; + } - @Override - public boolean isStable() { - return true; - } + @Override + public boolean isStable() { + return true; + } - @Override - public int hashCode() { - return Objects.hashCode(myQualifier.getQualifiedName()); - } + @Override + public int hashCode() { + return Objects.hashCode(myQualifier.getQualifiedName()); + } - @Override - public boolean equals(Object obj) { - return this == obj || obj instanceof ThisDescriptor && ((ThisDescriptor) obj).myQualifier == myQualifier; + @Override + public boolean equals(Object obj) { + return this == obj || obj instanceof ThisDescriptor && ((ThisDescriptor)obj).myQualifier == myQualifier; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValueFactory.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValueFactory.java index b91aa795bd..953b063a34 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValueFactory.java @@ -31,6 +31,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import static com.intellij.java.language.patterns.PsiJavaPatterns.psiMember; @@ -38,369 +39,369 @@ import static consulo.language.pattern.StandardPatterns.or; public class DfaValueFactory { - private final - @Nonnull - List myValues = new ArrayList<>(); - private final boolean myUnknownMembersAreNullable; - private final - @Nonnull - FieldChecker myFieldChecker; - private final - @Nonnull - Project myProject; - private - @Nullable - DfaVariableValue myAssertionDisabled; - - /** - * @param project a project in which context the analysis is performed - * @param context an item to analyze (code-block, expression, class) - * @param unknownMembersAreNullable if true, unknown (non-annotated members) are assumed to be nullable - */ - public DfaValueFactory(@Nonnull Project project, @Nullable PsiElement context, boolean unknownMembersAreNullable) { - myProject = project; - myFieldChecker = new FieldChecker(context); - myUnknownMembersAreNullable = unknownMembersAreNullable; - myValues.add(null); - myVarFactory = new DfaVariableValue.Factory(this); - myBoxedFactory = new DfaBoxedValue.Factory(this); - myExpressionFactory = new DfaExpressionFactory(this); - myBinOpFactory = new DfaBinOpValue.Factory(this); - myTypeValueFactory = new DfaTypeValue.Factory(this); - } - - public boolean canTrustFieldInitializer(PsiField field) { - return myFieldChecker.canTrustFieldInitializer(field); - } - - private static final ElementPattern MEMBER_OR_METHOD_PARAMETER = - or(psiMember(), psiParameter().withSuperParent(2, psiMember())); - - - @Nonnull - public Nullability suggestNullabilityForNonAnnotatedMember(@Nonnull PsiModifierListOwner member) { - if (myUnknownMembersAreNullable && - MEMBER_OR_METHOD_PARAMETER.accepts(member) && - AnnotationUtil.getSuperAnnotationOwners(member).isEmpty()) { - return Nullability.NULLABLE; + private final + @Nonnull + List myValues = new ArrayList<>(); + private final boolean myUnknownMembersAreNullable; + private final + @Nonnull + FieldChecker myFieldChecker; + private final + @Nonnull + Project myProject; + private + @Nullable + DfaVariableValue myAssertionDisabled; + + /** + * @param project a project in which context the analysis is performed + * @param context an item to analyze (code-block, expression, class) + * @param unknownMembersAreNullable if true, unknown (non-annotated members) are assumed to be nullable + */ + public DfaValueFactory(@Nonnull Project project, @Nullable PsiElement context, boolean unknownMembersAreNullable) { + myProject = project; + myFieldChecker = new FieldChecker(context); + myUnknownMembersAreNullable = unknownMembersAreNullable; + myValues.add(null); + myVarFactory = new DfaVariableValue.Factory(this); + myBoxedFactory = new DfaBoxedValue.Factory(this); + myExpressionFactory = new DfaExpressionFactory(this); + myBinOpFactory = new DfaBinOpValue.Factory(this); + myTypeValueFactory = new DfaTypeValue.Factory(this); + } + + public boolean canTrustFieldInitializer(PsiField field) { + return myFieldChecker.canTrustFieldInitializer(field); + } + + private static final ElementPattern MEMBER_OR_METHOD_PARAMETER = + or(psiMember(), psiParameter().withSuperParent(2, psiMember())); + + + @Nonnull + public Nullability suggestNullabilityForNonAnnotatedMember(@Nonnull PsiModifierListOwner member) { + if (myUnknownMembersAreNullable && + MEMBER_OR_METHOD_PARAMETER.accepts(member) && + AnnotationUtil.getSuperAnnotationOwners(member).isEmpty()) { + return Nullability.NULLABLE; + } + + return Nullability.UNKNOWN; + } + + @Nonnull + public DfaTypeValue getObjectType(@Nullable PsiType type, @Nonnull Nullability nullability) { + return fromDfType(DfTypes.typedObject(type, nullability)); + } + + public + @Nullable + DfaVariableValue getAssertionDisabled() { + return myAssertionDisabled; } - return Nullability.UNKNOWN; - } - - @Nonnull - public DfaTypeValue getObjectType(@Nullable PsiType type, @Nonnull Nullability nullability) { - return fromDfType(DfTypes.typedObject(type, nullability)); - } - - public - @Nullable - DfaVariableValue getAssertionDisabled() { - return myAssertionDisabled; - } - - void setAssertionDisabled(@Nonnull DfaVariableValue value) { - assert myAssertionDisabled == null; - myAssertionDisabled = value; - } - - int registerValue(DfaValue value) { - myValues.add(value); - return myValues.size() - 1; - } - - public DfaValue getValue(int id) { - return myValues.get(id); - } - - @Nullable - @Contract("null -> null") - public DfaValue createValue(PsiExpression psiExpression) { - return myExpressionFactory.getExpressionDfaValue(psiExpression); - } - - @Nonnull - public DfaTypeValue getInt(int value) { - return fromDfType(DfTypes.intValue(value)); - } - - @Nonnull - public DfaTypeValue getUnknown() { - return fromDfType(DfTypes.TOP); - } - - /** - * @return a special sentinel value that never equals to anything else (even unknown value) and - * sometimes pushed on the stack as control flow implementation detail. - * It's never assigned to the variable or merged with any other value. - */ - @Nonnull - public DfaValue getSentinel() { - return mySentinelValue; - } - - @Nonnull - public DfaTypeValue getBoolean(boolean value) { - return fromDfType(DfTypes.booleanValue(value)); - } - - /** - * @return a null value - */ - @Nonnull - public DfaTypeValue getNull() { - return fromDfType(DfTypes.NULL); - } - - /** - * Creates a constant of given type and given value. Constants are always unique - * (two different constants are not equal to each other). - *

- * The following types of the objects are supported: - *

    - *
  • Integer/Long/Double/Float/Boolean (will be unboxed)
  • - *
  • Character/Byte/Short (will be unboxed and widened to int)
  • - *
  • String
  • - *
  • {@link PsiEnumConstant} (enum constant value, type must be the corresponding enum type)
  • - *
  • {@link PsiField} (a final field that contains a unique value, type must be a type of that field)
  • - *
  • {@link PsiType} (java.lang.Class object value, type must be java.lang.Class)
  • - *
- * - * @param type type of the constant - * @return a DfaTypeValue whose type is DfConstantType that corresponds to given constant. - */ - public DfaTypeValue getConstant(Object value, @Nonnull PsiType type) { - return fromDfType(DfTypes.constant(value, type)); - } - - /** - * @param variable variable to create a constant based on its value - * @return a value that represents a constant created from variable; null if variable cannot be represented as a constant - */ - @Nullable - public DfaValue getConstantFromVariable(PsiVariable variable) { - if (!variable.hasModifierProperty(PsiModifier.FINAL) || DfaUtil.ignoreInitializer(variable)) { - return null; + void setAssertionDisabled(@Nonnull DfaVariableValue value) { + assert myAssertionDisabled == null; + myAssertionDisabled = value; } - Object value = variable.computeConstantValue(); - PsiType type = variable.getType(); - if (value == null) { - Boolean boo = computeJavaLangBooleanFieldReference(variable); - if (boo != null) { - DfaValue unboxed = getConstant(boo, PsiType.BOOLEAN); - return getBoxedFactory().createBoxed(unboxed, PsiType.BOOLEAN.getBoxedType(variable)); - } - if (DfaUtil.isEmptyCollectionConstantField(variable)) { - return getConstant(variable, type); - } - PsiExpression initializer = PsiFieldImpl.getDetachedInitializer(variable); - initializer = PsiUtil.skipParenthesizedExprDown(initializer); - if (initializer instanceof PsiLiteralExpression && initializer.textMatches(PsiKeyword.NULL)) { - return getNull(); - } - if (variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC) && ExpressionUtils.isNewObject(initializer)) { - return getConstant(variable, type); - } - return null; + + int registerValue(DfaValue value) { + myValues.add(value); + return myValues.size() - 1; } - return getConstant(value, type); - } - - @Nonnull - public Project getProject() { - return myProject; - } - - @Nullable - private static Boolean computeJavaLangBooleanFieldReference(final PsiVariable variable) { - if (!(variable instanceof PsiField)) { - return null; + + public DfaValue getValue(int id) { + return myValues.get(id); } - PsiClass psiClass = ((PsiField) variable).getContainingClass(); - if (psiClass == null || !CommonClassNames.JAVA_LANG_BOOLEAN.equals(psiClass.getQualifiedName())) { - return null; + + @Nullable + @Contract("null -> null") + public DfaValue createValue(PsiExpression psiExpression) { + return myExpressionFactory.getExpressionDfaValue(psiExpression); } - @NonNls String name = variable.getName(); - return "TRUE".equals(name) ? Boolean.TRUE : "FALSE".equals(name) ? Boolean.FALSE : null; - } - - @Nonnull - public DfaTypeValue fromDfType(@Nonnull DfType dfType) { - return myTypeValueFactory.create(dfType); - } - - public Collection getValues() { - return Collections.unmodifiableCollection(myValues); - } - - @Nonnull - public DfaControlTransferValue controlTransfer(TransferTarget kind, FList traps) { - return myControlTransfers.get(Pair.create(kind, traps)); - } - - private final Map>, DfaControlTransferValue> myControlTransfers = - FactoryMap.create(p -> new DfaControlTransferValue(this, p.first, p.second)); - - private final DfaVariableValue.Factory myVarFactory; - private final DfaBoxedValue.Factory myBoxedFactory; - private final DfaBinOpValue.Factory myBinOpFactory; - private final DfaExpressionFactory myExpressionFactory; - private final DfaTypeValue.Factory myTypeValueFactory; - private final DfaValue mySentinelValue = new DfaValue(this) { - @Override - public String toString() { - return "SENTINEL"; + + @Nonnull + public DfaTypeValue getInt(int value) { + return fromDfType(DfTypes.intValue(value)); } - }; - - @Nonnull - public DfaVariableValue.Factory getVarFactory() { - return myVarFactory; - } - - @Nonnull - public DfaBoxedValue.Factory getBoxedFactory() { - return myBoxedFactory; - } - - @Nonnull - public DfaExpressionFactory getExpressionFactory() { - return myExpressionFactory; - } - - @Nonnull - public DfaBinOpValue.Factory getBinOpFactory() { - return myBinOpFactory; - } - - @Nonnull - public DfaValue createCommonValue(@Nonnull PsiExpression[] expressions, PsiType targetType) { - DfaValue loopElement = null; - for (PsiExpression expression : expressions) { - DfaValue expressionValue = createValue(expression); - if (expressionValue == null) { - expressionValue = getObjectType(expression.getType(), NullabilityUtil.getExpressionNullability(expression)); - } - loopElement = loopElement == null ? expressionValue : loopElement.unite(expressionValue); - if (DfaTypeValue.isUnknown(loopElement)) { - break; - } + + @Nonnull + public DfaTypeValue getUnknown() { + return fromDfType(DfTypes.TOP); + } + + /** + * @return a special sentinel value that never equals to anything else (even unknown value) and + * sometimes pushed on the stack as control flow implementation detail. + * It's never assigned to the variable or merged with any other value. + */ + @Nonnull + public DfaValue getSentinel() { + return mySentinelValue; + } + + @Nonnull + public DfaTypeValue getBoolean(boolean value) { + return fromDfType(DfTypes.booleanValue(value)); + } + + /** + * @return a null value + */ + @Nonnull + public DfaTypeValue getNull() { + return fromDfType(DfTypes.NULL); + } + + /** + * Creates a constant of given type and given value. Constants are always unique + * (two different constants are not equal to each other). + *

+ * The following types of the objects are supported: + *

    + *
  • Integer/Long/Double/Float/Boolean (will be unboxed)
  • + *
  • Character/Byte/Short (will be unboxed and widened to int)
  • + *
  • String
  • + *
  • {@link PsiEnumConstant} (enum constant value, type must be the corresponding enum type)
  • + *
  • {@link PsiField} (a final field that contains a unique value, type must be a type of that field)
  • + *
  • {@link PsiType} (java.lang.Class object value, type must be java.lang.Class)
  • + *
+ * + * @param type type of the constant + * @return a DfaTypeValue whose type is DfConstantType that corresponds to given constant. + */ + public DfaTypeValue getConstant(Object value, @Nonnull PsiType type) { + return fromDfType(DfTypes.constant(value, type)); } - return loopElement == null ? getUnknown() : DfaUtil.boxUnbox(loopElement, targetType); - } - - private static class ClassInitializationInfo { - private static final CallMatcher SAFE_CALLS = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OBJECTS, "requireNonNull"); - - final boolean myCanInstantiateItself; - final boolean myCtorsCallMethods; - final boolean mySuperCtorsCallMethods; - - ClassInitializationInfo(@Nonnull PsiClass psiClass) { - // Indirect instantiation via other class is still possible, but hopefully unlikely - boolean canInstantiateItself = false; - for (PsiElement child = psiClass.getFirstChild(); child != null; child = child.getNextSibling()) { - if (child instanceof PsiMember && ((PsiMember) child).hasModifierProperty(PsiModifier.STATIC) && - SyntaxTraverser.psiTraverser(child).filter(PsiNewExpression.class) - .filterMap(PsiNewExpression::getClassReference) - .find(classRef -> classRef.isReferenceTo(psiClass)) != null) { - canInstantiateItself = true; - break; + + /** + * @param variable variable to create a constant based on its value + * @return a value that represents a constant created from variable; null if variable cannot be represented as a constant + */ + @Nullable + public DfaValue getConstantFromVariable(PsiVariable variable) { + if (!variable.hasModifierProperty(PsiModifier.FINAL) || DfaUtil.ignoreInitializer(variable)) { + return null; + } + Object value = variable.computeConstantValue(); + PsiType type = variable.getType(); + if (value == null) { + Boolean boo = computeJavaLangBooleanFieldReference(variable); + if (boo != null) { + DfaValue unboxed = getConstant(boo, PsiType.BOOLEAN); + return getBoxedFactory().createBoxed(unboxed, PsiType.BOOLEAN.getBoxedType(variable)); + } + if (DfaUtil.isEmptyCollectionConstantField(variable)) { + return getConstant(variable, type); + } + PsiExpression initializer = PsiFieldImpl.getDetachedInitializer(variable); + initializer = PsiUtil.skipParenthesizedExprDown(initializer); + if (initializer instanceof PsiLiteralExpression && initializer.textMatches(PsiKeyword.NULL)) { + return getNull(); + } + if (variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC) && ExpressionUtils.isNewObject(initializer)) { + return getConstant(variable, type); + } + return null; } - } - myCanInstantiateItself = canInstantiateItself; - mySuperCtorsCallMethods = - !InheritanceUtil.processSupers(psiClass, false, superClass -> !canCallMethodsInConstructors(superClass, true)); - myCtorsCallMethods = canCallMethodsInConstructors(psiClass, false); + return getConstant(value, type); + } + + @Nonnull + public Project getProject() { + return myProject; } - private static boolean canCallMethodsInConstructors(@Nonnull PsiClass aClass, boolean virtual) { - boolean inByteCode = false; - if (aClass instanceof PsiCompiledElement) { - inByteCode = true; - PsiElement navigationElement = aClass.getNavigationElement(); - if (navigationElement instanceof PsiClass) { - aClass = (PsiClass) navigationElement; + @Nullable + private static Boolean computeJavaLangBooleanFieldReference(final PsiVariable variable) { + if (!(variable instanceof PsiField)) { + return null; } - } - for (PsiMethod constructor : aClass.getConstructors()) { - if (inByteCode && JavaMethodContractUtil.isPure(constructor) && - !JavaMethodContractUtil.hasExplicitContractAnnotation(constructor)) { - // While pure constructor may call pure overridable method, our current implementation - // of bytecode inference will not infer the constructor purity in this case. - // So if we inferred a constructor purity from bytecode we can currently rely that - // no overridable methods are called there. - continue; + PsiClass psiClass = ((PsiField)variable).getContainingClass(); + if (psiClass == null || !CommonClassNames.JAVA_LANG_BOOLEAN.equals(psiClass.getQualifiedName())) { + return null; } - if (!constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { - return true; + @NonNls String name = variable.getName(); + return "TRUE".equals(name) ? Boolean.TRUE : "FALSE".equals(name) ? Boolean.FALSE : null; + } + + @Nonnull + public DfaTypeValue fromDfType(@Nonnull DfType dfType) { + return myTypeValueFactory.create(dfType); + } + + public Collection getValues() { + return Collections.unmodifiableCollection(myValues); + } + + @Nonnull + public DfaControlTransferValue controlTransfer(TransferTarget kind, FList traps) { + return myControlTransfers.get(Pair.create(kind, traps)); + } + + private final Map>, DfaControlTransferValue> myControlTransfers = + FactoryMap.create(p -> new DfaControlTransferValue(this, p.first, p.second)); + + private final DfaVariableValue.Factory myVarFactory; + private final DfaBoxedValue.Factory myBoxedFactory; + private final DfaBinOpValue.Factory myBinOpFactory; + private final DfaExpressionFactory myExpressionFactory; + private final DfaTypeValue.Factory myTypeValueFactory; + private final DfaValue mySentinelValue = new DfaValue(this) { + @Override + public String toString() { + return "SENTINEL"; } + }; - PsiCodeBlock body = constructor.getBody(); - if (body == null) { - continue; + @Nonnull + public DfaVariableValue.Factory getVarFactory() { + return myVarFactory; + } + + @Nonnull + public DfaBoxedValue.Factory getBoxedFactory() { + return myBoxedFactory; + } + + @Nonnull + public DfaExpressionFactory getExpressionFactory() { + return myExpressionFactory; + } + + @Nonnull + public DfaBinOpValue.Factory getBinOpFactory() { + return myBinOpFactory; + } + + @Nonnull + public DfaValue createCommonValue(@Nonnull PsiExpression[] expressions, PsiType targetType) { + DfaValue loopElement = null; + for (PsiExpression expression : expressions) { + DfaValue expressionValue = createValue(expression); + if (expressionValue == null) { + expressionValue = getObjectType(expression.getType(), NullabilityUtil.getExpressionNullability(expression)); + } + loopElement = loopElement == null ? expressionValue : loopElement.unite(expressionValue); + if (DfaTypeValue.isUnknown(loopElement)) { + break; + } } + return loopElement == null ? getUnknown() : DfaUtil.boxUnbox(loopElement, targetType); + } - for (PsiMethodCallExpression call : SyntaxTraverser.psiTraverser().withRoot(body).filter(PsiMethodCallExpression.class)) { - PsiReferenceExpression methodExpression = call.getMethodExpression(); - if (methodExpression.textMatches(PsiKeyword.THIS) || methodExpression.textMatches(PsiKeyword.SUPER)) { - continue; - } - if (SAFE_CALLS.test(call)) { - continue; - } - if (!virtual) { - return true; - } - - PsiMethod target = call.resolveMethod(); - if (target != null && PsiUtil.canBeOverridden(target)) { - return true; - } + private static class ClassInitializationInfo { + private static final CallMatcher SAFE_CALLS = + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OBJECTS, "requireNonNull"); + + final boolean myCanInstantiateItself; + final boolean myCtorsCallMethods; + final boolean mySuperCtorsCallMethods; + + ClassInitializationInfo(@Nonnull PsiClass psiClass) { + // Indirect instantiation via other class is still possible, but hopefully unlikely + boolean canInstantiateItself = false; + for (PsiElement child = psiClass.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child instanceof PsiMember && ((PsiMember)child).hasModifierProperty(PsiModifier.STATIC) && + SyntaxTraverser.psiTraverser(child).filter(PsiNewExpression.class) + .filterMap(PsiNewExpression::getClassReference) + .find(classRef -> classRef.isReferenceTo(psiClass)) != null) { + canInstantiateItself = true; + break; + } + } + myCanInstantiateItself = canInstantiateItself; + mySuperCtorsCallMethods = + !InheritanceUtil.processSupers(psiClass, false, superClass -> !canCallMethodsInConstructors(superClass, true)); + myCtorsCallMethods = canCallMethodsInConstructors(psiClass, false); } - } - return false; - } - } - - private static class FieldChecker { - private final boolean myTrustDirectFieldInitializers; - private final boolean myTrustFieldInitializersInConstructors; - private final boolean myCanInstantiateItself; - private final PsiClass myClass; - - FieldChecker(PsiElement context) { - PsiMethod method = context instanceof PsiClass ? null : PsiTreeUtil.getParentOfType(context, PsiMethod.class); - PsiClass contextClass = method != null ? method.getContainingClass() : context instanceof PsiClass ? (PsiClass) context : null; - myClass = contextClass; - if (method == null || myClass == null) { - myTrustDirectFieldInitializers = myTrustFieldInitializersInConstructors = myCanInstantiateItself = false; - return; - } - // Indirect instantiation via other class is still possible, but hopefully unlikely - ClassInitializationInfo info = LanguageCachedValueUtil.getCachedValue(contextClass, () -> CachedValueProvider.Result - .create(new ClassInitializationInfo(contextClass), PsiModificationTracker.MODIFICATION_COUNT)); - myCanInstantiateItself = info.myCanInstantiateItself; - if (method.hasModifierProperty(PsiModifier.STATIC) || method.isConstructor()) { - myTrustDirectFieldInitializers = true; - myTrustFieldInitializersInConstructors = false; - return; - } - myTrustFieldInitializersInConstructors = !info.mySuperCtorsCallMethods && !info.myCtorsCallMethods; - myTrustDirectFieldInitializers = !info.mySuperCtorsCallMethods; + private static boolean canCallMethodsInConstructors(@Nonnull PsiClass aClass, boolean virtual) { + boolean inByteCode = false; + if (aClass instanceof PsiCompiledElement) { + inByteCode = true; + PsiElement navigationElement = aClass.getNavigationElement(); + if (navigationElement instanceof PsiClass) { + aClass = (PsiClass)navigationElement; + } + } + for (PsiMethod constructor : aClass.getConstructors()) { + if (inByteCode && JavaMethodContractUtil.isPure(constructor) && + !JavaMethodContractUtil.hasExplicitContractAnnotation(constructor)) { + // While pure constructor may call pure overridable method, our current implementation + // of bytecode inference will not infer the constructor purity in this case. + // So if we inferred a constructor purity from bytecode we can currently rely that + // no overridable methods are called there. + continue; + } + if (!constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { + return true; + } + + PsiCodeBlock body = constructor.getBody(); + if (body == null) { + continue; + } + + for (PsiMethodCallExpression call : SyntaxTraverser.psiTraverser().withRoot(body).filter(PsiMethodCallExpression.class)) { + PsiReferenceExpression methodExpression = call.getMethodExpression(); + if (methodExpression.textMatches(PsiKeyword.THIS) || methodExpression.textMatches(PsiKeyword.SUPER)) { + continue; + } + if (SAFE_CALLS.test(call)) { + continue; + } + if (!virtual) { + return true; + } + + PsiMethod target = call.resolveMethod(); + if (target != null && PsiUtil.canBeOverridden(target)) { + return true; + } + } + } + + return false; + } } - boolean canTrustFieldInitializer(PsiField field) { - if (field.hasInitializer()) { - boolean staticField = field.hasModifierProperty(PsiModifier.STATIC); - if (staticField && myClass != null && field.getContainingClass() != myClass) { - return true; + private static class FieldChecker { + private final boolean myTrustDirectFieldInitializers; + private final boolean myTrustFieldInitializersInConstructors; + private final boolean myCanInstantiateItself; + private final PsiClass myClass; + + FieldChecker(PsiElement context) { + PsiMethod method = context instanceof PsiClass ? null : PsiTreeUtil.getParentOfType(context, PsiMethod.class); + PsiClass contextClass = method != null ? method.getContainingClass() : context instanceof PsiClass ? (PsiClass)context : null; + myClass = contextClass; + if (method == null || myClass == null) { + myTrustDirectFieldInitializers = myTrustFieldInitializersInConstructors = myCanInstantiateItself = false; + return; + } + // Indirect instantiation via other class is still possible, but hopefully unlikely + ClassInitializationInfo info = LanguageCachedValueUtil.getCachedValue(contextClass, () -> CachedValueProvider.Result + .create(new ClassInitializationInfo(contextClass), PsiModificationTracker.MODIFICATION_COUNT)); + myCanInstantiateItself = info.myCanInstantiateItself; + if (method.hasModifierProperty(PsiModifier.STATIC) || method.isConstructor()) { + myTrustDirectFieldInitializers = true; + myTrustFieldInitializersInConstructors = false; + return; + } + myTrustFieldInitializersInConstructors = !info.mySuperCtorsCallMethods && !info.myCtorsCallMethods; + myTrustDirectFieldInitializers = !info.mySuperCtorsCallMethods; + } + + boolean canTrustFieldInitializer(PsiField field) { + if (field.hasInitializer()) { + boolean staticField = field.hasModifierProperty(PsiModifier.STATIC); + if (staticField && myClass != null && field.getContainingClass() != myClass) { + return true; + } + return myTrustDirectFieldInitializers && (!myCanInstantiateItself || !staticField); + } + return myTrustFieldInitializersInConstructors; } - return myTrustDirectFieldInitializers && (!myCanInstantiateItself || !staticField); - } - return myTrustFieldInitializersInConstructors; } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/java15api/Java15APIUsageInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/java15api/Java15APIUsageInspection.java index 4cd3272d9b..a1f8d3bcb6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/java15api/Java15APIUsageInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/java15api/Java15APIUsageInspection.java @@ -54,466 +54,490 @@ */ @ExtensionImpl public class Java15APIUsageInspection extends AbstractBaseJavaLocalInspectionTool { - public static final String SHORT_NAME = "Since15"; - - private static final String EFFECTIVE_LL = "effectiveLL"; - - private static final Map>> ourForbiddenAPI = new EnumMap<>(LanguageLevel.class); - private static final Supplier> ourIgnored16ClassesAPI = LazyValue.notNull(() -> loadForbiddenApi("ignore16List.txt")); - private static final Map ourPresentableShortMessage = new EnumMap<>(LanguageLevel.class); - - private static final LanguageLevel ourHighestKnownLanguage = LanguageLevel.JDK_19; - - static { - ourPresentableShortMessage.put(LanguageLevel.JDK_1_3, "1.4"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_4, "1.5"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_5, "1.6"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_6, "1.7"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_7, "1.8"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_8, "9"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_9, "10"); - ourPresentableShortMessage.put(LanguageLevel.JDK_10, "11"); - ourPresentableShortMessage.put(LanguageLevel.JDK_11, "12"); - ourPresentableShortMessage.put(LanguageLevel.JDK_12, "13"); - ourPresentableShortMessage.put(LanguageLevel.JDK_13, "14"); - ourPresentableShortMessage.put(LanguageLevel.JDK_14, "15"); - ourPresentableShortMessage.put(LanguageLevel.JDK_15, "16"); - ourPresentableShortMessage.put(LanguageLevel.JDK_16, "17"); - ourPresentableShortMessage.put(LanguageLevel.JDK_17, "18"); - ourPresentableShortMessage.put(LanguageLevel.JDK_18, "19"); - - } - - private static final Set ourGenerifiedClasses = new HashSet<>(); - - static { - ourGenerifiedClasses.add("javax.swing.JComboBox"); - ourGenerifiedClasses.add("javax.swing.ListModel"); - ourGenerifiedClasses.add("javax.swing.JList"); - } - - private static final Set ourDefaultMethods = new HashSet<>(); - - static { - ourDefaultMethods.add("java.util.Iterator#remove()"); - } - - LanguageLevel myEffectiveLanguageLevel; - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Override - public JComponent createOptionsPanel() { - JPanel panel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 5, true, false)); - panel.add(new JLabel("Forbid API usages:")); - - JRadioButton projectRb = new JRadioButton("Respecting to project language level settings"); - panel.add(projectRb); - JRadioButton customRb = new JRadioButton("Higher than:"); - panel.add(customRb); - ButtonGroup gr = new ButtonGroup(); - gr.add(projectRb); - gr.add(customRb); - - JComboBox llCombo = new ComboBox(LanguageLevel.values()) { - @Override - public void setEnabled(boolean b) { - if (b == customRb.isSelected()) { - super.setEnabled(b); - } - } - }; - llCombo.setSelectedItem(myEffectiveLanguageLevel != null ? myEffectiveLanguageLevel : LanguageLevel.JDK_1_3); - llCombo.setRenderer(SimpleListCellRenderer.create("", languageLevel -> languageLevel.getDescription().get())); - llCombo.addActionListener(e -> myEffectiveLanguageLevel = (LanguageLevel) llCombo.getSelectedItem()); - - JPanel comboPanel = new JPanel(new BorderLayout()); - comboPanel.setBorder(JBUI.Borders.emptyLeft(20)); - comboPanel.add(llCombo, BorderLayout.WEST); - panel.add(comboPanel); - - ActionListener actionListener = e -> { - if (projectRb.isSelected()) { - myEffectiveLanguageLevel = null; - } else { - myEffectiveLanguageLevel = (LanguageLevel) llCombo.getSelectedItem(); - } - UIUtil.setEnabled(comboPanel, !projectRb.isSelected(), true); - }; - projectRb.addActionListener(actionListener); - customRb.addActionListener(actionListener); - projectRb.setSelected(myEffectiveLanguageLevel == null); - customRb.setSelected(myEffectiveLanguageLevel != null); - UIUtil.setEnabled(comboPanel, !projectRb.isSelected(), true); - return panel; - } - - @Nullable - private static Set getForbiddenApi(@Nonnull LanguageLevel languageLevel) { - if (!ourPresentableShortMessage.containsKey(languageLevel)) { - return null; + public static final String SHORT_NAME = "Since15"; + + private static final String EFFECTIVE_LL = "effectiveLL"; + + private static final Map>> ourForbiddenAPI = new EnumMap<>(LanguageLevel.class); + private static final Supplier> ourIgnored16ClassesAPI = LazyValue.notNull(() -> loadForbiddenApi("ignore16List.txt")); + private static final Map ourPresentableShortMessage = new EnumMap<>(LanguageLevel.class); + + private static final LanguageLevel ourHighestKnownLanguage = LanguageLevel.JDK_19; + + static { + ourPresentableShortMessage.put(LanguageLevel.JDK_1_3, "1.4"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_4, "1.5"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_5, "1.6"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_6, "1.7"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_7, "1.8"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_8, "9"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_9, "10"); + ourPresentableShortMessage.put(LanguageLevel.JDK_10, "11"); + ourPresentableShortMessage.put(LanguageLevel.JDK_11, "12"); + ourPresentableShortMessage.put(LanguageLevel.JDK_12, "13"); + ourPresentableShortMessage.put(LanguageLevel.JDK_13, "14"); + ourPresentableShortMessage.put(LanguageLevel.JDK_14, "15"); + ourPresentableShortMessage.put(LanguageLevel.JDK_15, "16"); + ourPresentableShortMessage.put(LanguageLevel.JDK_16, "17"); + ourPresentableShortMessage.put(LanguageLevel.JDK_17, "18"); + ourPresentableShortMessage.put(LanguageLevel.JDK_18, "19"); + } - Reference> ref = ourForbiddenAPI.get(languageLevel); - Set result = SoftReference.dereference(ref); - if (result == null) { - result = loadForbiddenApi("api" + getShortName(languageLevel) + ".txt"); - ourForbiddenAPI.put(languageLevel, new SoftReference<>(result)); + + private static final Set ourGenerifiedClasses = new HashSet<>(); + + static { + ourGenerifiedClasses.add("javax.swing.JComboBox"); + ourGenerifiedClasses.add("javax.swing.ListModel"); + ourGenerifiedClasses.add("javax.swing.JList"); } - return result; - } - - private static Set loadForbiddenApi(String fileName) { - URL resource = Java15APIUsageInspection.class.getResource(fileName); - if (resource == null) { - Logger.getInstance(Java15APIUsageInspection.class).warn("not found: " + fileName); - return Collections.emptySet(); + + private static final Set ourDefaultMethods = new HashSet<>(); + + static { + ourDefaultMethods.add("java.util.Iterator#remove()"); } - try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) { - return new HashSet<>(FileUtil.loadLines(reader)); - } catch (IOException ex) { - Logger.getInstance(Java15APIUsageInspection.class).warn("cannot load: " + fileName, ex); - return Collections.emptySet(); + LanguageLevel myEffectiveLanguageLevel; + + @Override + public boolean isEnabledByDefault() { + return true; } - } - - @Override - @Nonnull - public String getGroupDisplayName() { - return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids().get(); - } - - @Override - @Nonnull - public String getDisplayName() { - return InspectionLocalize.inspection15DisplayName().get(); - } - - @Override - @Nonnull - public String getShortName() { - return SHORT_NAME; - } - - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.ERROR; - } - - @Override - public void readSettings(@Nonnull Element node) throws InvalidDataException { - final Element element = node.getChild(EFFECTIVE_LL); - if (element != null) { - myEffectiveLanguageLevel = LanguageLevel.valueOf(element.getAttributeValue("value")); + + @Override + public JComponent createOptionsPanel() { + JPanel panel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 5, true, false)); + panel.add(new JLabel("Forbid API usages:")); + + JRadioButton projectRb = new JRadioButton("Respecting to project language level settings"); + panel.add(projectRb); + JRadioButton customRb = new JRadioButton("Higher than:"); + panel.add(customRb); + ButtonGroup gr = new ButtonGroup(); + gr.add(projectRb); + gr.add(customRb); + + JComboBox llCombo = new ComboBox(LanguageLevel.values()) { + @Override + public void setEnabled(boolean b) { + if (b == customRb.isSelected()) { + super.setEnabled(b); + } + } + }; + llCombo.setSelectedItem(myEffectiveLanguageLevel != null ? myEffectiveLanguageLevel : LanguageLevel.JDK_1_3); + llCombo.setRenderer(SimpleListCellRenderer.create("", languageLevel -> languageLevel.getDescription().get())); + llCombo.addActionListener(e -> myEffectiveLanguageLevel = (LanguageLevel)llCombo.getSelectedItem()); + + JPanel comboPanel = new JPanel(new BorderLayout()); + comboPanel.setBorder(JBUI.Borders.emptyLeft(20)); + comboPanel.add(llCombo, BorderLayout.WEST); + panel.add(comboPanel); + + ActionListener actionListener = e -> { + if (projectRb.isSelected()) { + myEffectiveLanguageLevel = null; + } + else { + myEffectiveLanguageLevel = (LanguageLevel)llCombo.getSelectedItem(); + } + UIUtil.setEnabled(comboPanel, !projectRb.isSelected(), true); + }; + projectRb.addActionListener(actionListener); + customRb.addActionListener(actionListener); + projectRb.setSelected(myEffectiveLanguageLevel == null); + customRb.setSelected(myEffectiveLanguageLevel != null); + UIUtil.setEnabled(comboPanel, !projectRb.isSelected(), true); + return panel; } - } - - @Override - public void writeSettings(@Nonnull Element node) throws WriteExternalException { - if (myEffectiveLanguageLevel != null) { - final Element llElement = new Element(EFFECTIVE_LL); - llElement.setAttribute("value", myEffectiveLanguageLevel.toString()); - node.addContent(llElement); + + @Nullable + private static Set getForbiddenApi(@Nonnull LanguageLevel languageLevel) { + if (!ourPresentableShortMessage.containsKey(languageLevel)) { + return null; + } + Reference> ref = ourForbiddenAPI.get(languageLevel); + Set result = SoftReference.dereference(ref); + if (result == null) { + result = loadForbiddenApi("api" + getShortName(languageLevel) + ".txt"); + ourForbiddenAPI.put(languageLevel, new SoftReference<>(result)); + } + return result; } - } - - @Override - @Nonnull - public PsiElementVisitor buildVisitorImpl(@Nonnull ProblemsHolder holder, - boolean isOnTheFly, - LocalInspectionToolSession session, - Object state) { - return new MyVisitor(holder, isOnTheFly); - } - - private static boolean isInProject(final PsiElement elt) { - return elt.getManager().isInProject(elt); - } - - public static String getShortName(LanguageLevel languageLevel) { - return ourPresentableShortMessage.get(languageLevel); - } - - private class MyVisitor extends JavaElementVisitor { - private final ProblemsHolder myHolder; - private final boolean myOnTheFly; - - MyVisitor(final ProblemsHolder holder, boolean onTheFly) { - myHolder = holder; - myOnTheFly = onTheFly; + + private static Set loadForbiddenApi(String fileName) { + URL resource = Java15APIUsageInspection.class.getResource(fileName); + if (resource == null) { + Logger.getInstance(Java15APIUsageInspection.class).warn("not found: " + fileName); + return Collections.emptySet(); + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) { + return new HashSet<>(FileUtil.loadLines(reader)); + } + catch (IOException ex) { + Logger.getInstance(Java15APIUsageInspection.class).warn("cannot load: " + fileName, ex); + return Collections.emptySet(); + } } @Override - public void visitDocComment(PsiDocComment comment) { - // No references inside doc comment are of interest. + @Nonnull + public String getGroupDisplayName() { + return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids().get(); } @Override - public void visitClass(PsiClass aClass) { - // Don't go into classes (anonymous, locals). - if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !(aClass instanceof PsiTypeParameter)) { - final Module module = ModuleUtilCore.findModuleForPsiElement(aClass); - final LanguageLevel effectiveLanguageLevel = module != null ? getEffectiveLanguageLevel(module) : null; - if (effectiveLanguageLevel != null && !effectiveLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - final JavaSdkVersion version = JavaVersionService.getInstance().getJavaSdkVersion(aClass); - if (version != null && version.isAtLeast(JavaSdkVersion.JDK_1_8)) { - final List methods = new ArrayList<>(); - for (HierarchicalMethodSignature methodSignature : aClass.getVisibleSignatures()) { - final PsiMethod method = methodSignature.getMethod(); - if (ourDefaultMethods.contains(getSignature(method))) { - methods.add(method); - } - } + @Nonnull + public String getDisplayName() { + return InspectionLocalize.inspection15DisplayName().get(); + } - if (!methods.isEmpty()) { - PsiElement element2Highlight = aClass.getNameIdentifier(); - if (element2Highlight == null) { - element2Highlight = aClass instanceof PsiAnonymousClass ? ((PsiAnonymousClass) aClass).getBaseClassReference() : aClass; - } - myHolder.registerProblem(element2Highlight, - methods.size() == 1 ? InspectionsBundle.message("inspection.1.8.problem.single.descriptor", methods.get(0).getName(), getJdkName(effectiveLanguageLevel)) - : InspectionsBundle.message("inspection.1.8.problem.descriptor", methods.size(), getJdkName(effectiveLanguageLevel)), - QuickFixFactory.getInstance().createImplementMethodsFix(aClass)); - } - } - } - } + @Override + @Nonnull + public String getShortName() { + return SHORT_NAME; } + + @Nonnull @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - visitReferenceElement(expression); + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.ERROR; } @Override - @RequiredReadAction - public void visitNameValuePair(PsiNameValuePair pair) { - super.visitNameValuePair(pair); - PsiReference reference = pair.getReference(); - if (reference != null) { - PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiCompiledElement && resolve instanceof PsiAnnotationMethod) { - final Module module = ModuleUtilCore.findModuleForPsiElement(pair); - if (module != null) { - final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel((PsiMember) resolve, languageLevel); - if (sinceLanguageLevel != null) { - registerError(ObjectUtil.notNull(pair.getNameIdentifier(), pair), sinceLanguageLevel); - } - } + public void readSettings(@Nonnull Element node) throws InvalidDataException { + final Element element = node.getChild(EFFECTIVE_LL); + if (element != null) { + myEffectiveLanguageLevel = LanguageLevel.valueOf(element.getAttributeValue("value")); } - } } @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - super.visitReferenceElement(reference); - final PsiElement resolved = reference.resolve(); - - if (resolved instanceof PsiCompiledElement && resolved instanceof PsiMember) { - final Module module = ModuleUtilCore.findModuleForPsiElement(reference.getElement()); - if (module != null) { - final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel((PsiMember) resolved, languageLevel); - if (sinceLanguageLevel != null) { - PsiClass psiClass = null; - final PsiElement qualifier = reference.getQualifier(); - if (qualifier != null) { - if (qualifier instanceof PsiExpression) { - psiClass = PsiUtil.resolveClassInType(((PsiExpression) qualifier).getType()); - } - } else { - psiClass = PsiTreeUtil.getParentOfType(reference, PsiClass.class); + public void writeSettings(@Nonnull Element node) throws WriteExternalException { + if (myEffectiveLanguageLevel != null) { + final Element llElement = new Element(EFFECTIVE_LL); + llElement.setAttribute("value", myEffectiveLanguageLevel.toString()); + node.addContent(llElement); + } + } + + @Override + @Nonnull + public PsiElementVisitor buildVisitorImpl( + @Nonnull ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state + ) { + return new MyVisitor(holder, isOnTheFly); + } + + private static boolean isInProject(final PsiElement elt) { + return elt.getManager().isInProject(elt); + } + + public static String getShortName(LanguageLevel languageLevel) { + return ourPresentableShortMessage.get(languageLevel); + } + + private class MyVisitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + private final boolean myOnTheFly; + + MyVisitor(final ProblemsHolder holder, boolean onTheFly) { + myHolder = holder; + myOnTheFly = onTheFly; + } + + @Override + public void visitDocComment(PsiDocComment comment) { + // No references inside doc comment are of interest. + } + + @Override + public void visitClass(PsiClass aClass) { + // Don't go into classes (anonymous, locals). + if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !(aClass instanceof PsiTypeParameter)) { + final Module module = ModuleUtilCore.findModuleForPsiElement(aClass); + final LanguageLevel effectiveLanguageLevel = module != null ? getEffectiveLanguageLevel(module) : null; + if (effectiveLanguageLevel != null && !effectiveLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + final JavaSdkVersion version = JavaVersionService.getInstance().getJavaSdkVersion(aClass); + if (version != null && version.isAtLeast(JavaSdkVersion.JDK_1_8)) { + final List methods = new ArrayList<>(); + for (HierarchicalMethodSignature methodSignature : aClass.getVisibleSignatures()) { + final PsiMethod method = methodSignature.getMethod(); + if (ourDefaultMethods.contains(getSignature(method))) { + methods.add(method); + } + } + + if (!methods.isEmpty()) { + PsiElement element2Highlight = aClass.getNameIdentifier(); + if (element2Highlight == null) { + element2Highlight = + aClass instanceof PsiAnonymousClass ? ((PsiAnonymousClass)aClass).getBaseClassReference() : aClass; + } + myHolder.registerProblem( + element2Highlight, + methods.size() == 1 + ? InspectionsBundle.message( + "inspection.1.8.problem.single.descriptor", + methods.get(0).getName(), + getJdkName(effectiveLanguageLevel) + ) + : InspectionsBundle.message( + "inspection.1.8.problem.descriptor", + methods.size(), + getJdkName(effectiveLanguageLevel) + ), + QuickFixFactory.getInstance().createImplementMethodsFix(aClass) + ); + } + } + } } - if (psiClass != null) { - if (isIgnored(psiClass)) { - return; - } - for (PsiClass superClass : psiClass.getSupers()) { - if (isIgnored(superClass)) { - return; + } + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + visitReferenceElement(expression); + } + + @Override + @RequiredReadAction + public void visitNameValuePair(PsiNameValuePair pair) { + super.visitNameValuePair(pair); + PsiReference reference = pair.getReference(); + if (reference != null) { + PsiElement resolve = reference.resolve(); + if (resolve instanceof PsiCompiledElement && resolve instanceof PsiAnnotationMethod) { + final Module module = ModuleUtilCore.findModuleForPsiElement(pair); + if (module != null) { + final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel((PsiMember)resolve, languageLevel); + if (sinceLanguageLevel != null) { + registerError(ObjectUtil.notNull(pair.getNameIdentifier(), pair), sinceLanguageLevel); + } + } } - } } - registerError(reference, sinceLanguageLevel); - } else if (resolved instanceof PsiClass && isInProject(reference) && !languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) { - final PsiReferenceParameterList parameterList = reference.getParameterList(); - if (parameterList != null && parameterList.getTypeParameterElements().length > 0) { - for (String generifiedClass : ourGenerifiedClasses) { - if (InheritanceUtil.isInheritor((PsiClass) resolved, generifiedClass) && - !isRawInheritance(generifiedClass, (PsiClass) resolved, new HashSet<>())) { - String message = InspectionsBundle.message("inspection.1.7.problem.descriptor", getJdkName(languageLevel)); - myHolder.registerProblem(reference, message); - break; + } + + @Override + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + final PsiElement resolved = reference.resolve(); + + if (resolved instanceof PsiCompiledElement && resolved instanceof PsiMember) { + final Module module = ModuleUtilCore.findModuleForPsiElement(reference.getElement()); + if (module != null) { + final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel((PsiMember)resolved, languageLevel); + if (sinceLanguageLevel != null) { + PsiClass psiClass = null; + final PsiElement qualifier = reference.getQualifier(); + if (qualifier != null) { + if (qualifier instanceof PsiExpression) { + psiClass = PsiUtil.resolveClassInType(((PsiExpression)qualifier).getType()); + } + } + else { + psiClass = PsiTreeUtil.getParentOfType(reference, PsiClass.class); + } + if (psiClass != null) { + if (isIgnored(psiClass)) { + return; + } + for (PsiClass superClass : psiClass.getSupers()) { + if (isIgnored(superClass)) { + return; + } + } + } + registerError(reference, sinceLanguageLevel); + } + else if (resolved instanceof PsiClass && isInProject(reference) && !languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) { + final PsiReferenceParameterList parameterList = reference.getParameterList(); + if (parameterList != null && parameterList.getTypeParameterElements().length > 0) { + for (String generifiedClass : ourGenerifiedClasses) { + if (InheritanceUtil.isInheritor((PsiClass)resolved, generifiedClass) && + !isRawInheritance(generifiedClass, (PsiClass)resolved, new HashSet<>())) { + String message = + InspectionsBundle.message("inspection.1.7.problem.descriptor", getJdkName(languageLevel)); + myHolder.registerProblem(reference, message); + break; + } + } + } + } } - } } - } } - } - } - private boolean isRawInheritance(String generifiedClassQName, PsiClass currentClass, Set visited) { - for (PsiClassType classType : currentClass.getSuperTypes()) { - if (classType.isRaw()) { - return true; + private boolean isRawInheritance(String generifiedClassQName, PsiClass currentClass, Set visited) { + for (PsiClassType classType : currentClass.getSuperTypes()) { + if (classType.isRaw()) { + return true; + } + final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); + final PsiClass superClass = resolveResult.getElement(); + if (visited.add(superClass) && InheritanceUtil.isInheritor(superClass, generifiedClassQName)) { + if (isRawInheritance(generifiedClassQName, superClass, visited)) { + return true; + } + } + } + return false; } - final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); - final PsiClass superClass = resolveResult.getElement(); - if (visited.add(superClass) && InheritanceUtil.isInheritor(superClass, generifiedClassQName)) { - if (isRawInheritance(generifiedClassQName, superClass, visited)) { - return true; - } + + private boolean isIgnored(PsiClass psiClass) { + final String qualifiedName = psiClass.getQualifiedName(); + return qualifiedName != null && ourIgnored16ClassesAPI.get().contains(qualifiedName); } - } - return false; - } - private boolean isIgnored(PsiClass psiClass) { - final String qualifiedName = psiClass.getQualifiedName(); - return qualifiedName != null && ourIgnored16ClassesAPI.get().contains(qualifiedName); - } + @Override + @RequiredReadAction + public void visitNewExpression(@Nonnull final PsiNewExpression expression) { + super.visitNewExpression(expression); + final PsiMethod constructor = expression.resolveConstructor(); + final Module module = ModuleUtilCore.findModuleForPsiElement(expression); + if (module != null) { + final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); + if (constructor instanceof PsiCompiledElement) { + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel(constructor, languageLevel); + if (sinceLanguageLevel != null) { + registerError(expression.getClassReference(), sinceLanguageLevel); + } + } + } + } - @Override - @RequiredReadAction - public void visitNewExpression(@Nonnull final PsiNewExpression expression) { - super.visitNewExpression(expression); - final PsiMethod constructor = expression.resolveConstructor(); - final Module module = ModuleUtilCore.findModuleForPsiElement(expression); - if (module != null) { - final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - if (constructor instanceof PsiCompiledElement) { - LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel(constructor, languageLevel); - if (sinceLanguageLevel != null) { - registerError(expression.getClassReference(), sinceLanguageLevel); - } + @Override + @RequiredReadAction + public void visitMethod(@Nonnull PsiMethod method) { + super.visitMethod(method); + PsiAnnotation annotation = !method.isConstructor() + ? AnnotationUtil.findAnnotation(method, CommonClassNames.JAVA_LANG_OVERRIDE) : null; + if (annotation != null) { + final Module module = ModuleUtilCore.findModuleForPsiElement(annotation); + LanguageLevel sinceLanguageLevel = null; + if (module != null) { + final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); + final PsiMethod[] methods = method.findSuperMethods(); + for (PsiMethod superMethod : methods) { + if (superMethod instanceof PsiCompiledElement) { + sinceLanguageLevel = getLastIncompatibleLanguageLevel(superMethod, languageLevel); + if (sinceLanguageLevel == null) { + return; + } + } + else { + return; + } + } + if (methods.length > 0) { + registerError(annotation.getNameReferenceElement(), sinceLanguageLevel); + } + } + } } - } - } - @Override - @RequiredReadAction - public void visitMethod(@Nonnull PsiMethod method) { - super.visitMethod(method); - PsiAnnotation annotation = !method.isConstructor() - ? AnnotationUtil.findAnnotation(method, CommonClassNames.JAVA_LANG_OVERRIDE) : null; - if (annotation != null) { - final Module module = ModuleUtilCore.findModuleForPsiElement(annotation); - LanguageLevel sinceLanguageLevel = null; - if (module != null) { - final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - final PsiMethod[] methods = method.findSuperMethods(); - for (PsiMethod superMethod : methods) { - if (superMethod instanceof PsiCompiledElement) { - sinceLanguageLevel = getLastIncompatibleLanguageLevel(superMethod, languageLevel); - if (sinceLanguageLevel == null) { - return; - } - } else { - return; + @RequiredReadAction + private LanguageLevel getEffectiveLanguageLevel(Module module) { + return myEffectiveLanguageLevel != null ? myEffectiveLanguageLevel : EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); + } + + private void registerError(PsiElement reference, LanguageLevel api) { + if (reference != null && isInProject(reference)) { + myHolder.registerProblem( + reference, + InspectionLocalize.inspection15ProblemDescriptor(getShortName(api)).get(), + myOnTheFly + ? new LocalQuickFix[]{ + (LocalQuickFix)QuickFixFactory.getInstance() + .createIncreaseLanguageLevelFix(LanguageLevel.values()[api.ordinal() + 1]) + } + : null + ); } - } - if (methods.length > 0) { - registerError(annotation.getNameReferenceElement(), sinceLanguageLevel); - } } - } } - @RequiredReadAction - private LanguageLevel getEffectiveLanguageLevel(Module module) { - return myEffectiveLanguageLevel != null ? myEffectiveLanguageLevel : EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); + private static String getJdkName(LanguageLevel languageLevel) { + return languageLevel.getDescription().get(); } - private void registerError(PsiElement reference, LanguageLevel api) { - if (reference != null && isInProject(reference)) { - myHolder.registerProblem( - reference, - InspectionLocalize.inspection15ProblemDescriptor(getShortName(api)).get(), - myOnTheFly - ? new LocalQuickFix[]{ - (LocalQuickFix) QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(LanguageLevel.values()[api.ordinal() + 1]) + public static LanguageLevel getLastIncompatibleLanguageLevel(@Nonnull PsiMember member, @Nonnull LanguageLevel languageLevel) { + if (member instanceof PsiAnonymousClass) { + return null; + } + PsiClass containingClass = member.getContainingClass(); + if (containingClass instanceof PsiAnonymousClass) { + return null; + } + if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) { + return null; + } + + Set forbiddenApi = getForbiddenApi(languageLevel); + String signature = getSignature(member); + if (forbiddenApi != null && signature != null) { + LanguageLevel lastIncompatibleLanguageLevel = + getLastIncompatibleLanguageLevelForSignature(signature, languageLevel, forbiddenApi); + if (lastIncompatibleLanguageLevel != null) { + return lastIncompatibleLanguageLevel; } - : null - ); - } + } + return containingClass != null ? getLastIncompatibleLanguageLevel(containingClass, languageLevel) : null; } - } - private static String getJdkName(LanguageLevel languageLevel) { - return languageLevel.getDescription().get(); - } - - public static LanguageLevel getLastIncompatibleLanguageLevel(@Nonnull PsiMember member, @Nonnull LanguageLevel languageLevel) { - if (member instanceof PsiAnonymousClass) { - return null; - } - PsiClass containingClass = member.getContainingClass(); - if (containingClass instanceof PsiAnonymousClass) { - return null; - } - if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) { - return null; + private static LanguageLevel getLastIncompatibleLanguageLevelForSignature( + @Nonnull String signature, + @Nonnull LanguageLevel languageLevel, + @Nonnull Set forbiddenApi + ) { + if (forbiddenApi.contains(signature)) { + return languageLevel; + } + if (languageLevel.compareTo(ourHighestKnownLanguage) == 0) { + return null; + } + LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1]; + Set nextForbiddenApi = getForbiddenApi(nextLanguageLevel); + return nextForbiddenApi != null + ? getLastIncompatibleLanguageLevelForSignature(signature, nextLanguageLevel, nextForbiddenApi) + : null; } - Set forbiddenApi = getForbiddenApi(languageLevel); - String signature = getSignature(member); - if (forbiddenApi != null && signature != null) { - LanguageLevel lastIncompatibleLanguageLevel = getLastIncompatibleLanguageLevelForSignature(signature, languageLevel, forbiddenApi); - if (lastIncompatibleLanguageLevel != null) { - return lastIncompatibleLanguageLevel; - } - } - return containingClass != null ? getLastIncompatibleLanguageLevel(containingClass, languageLevel) : null; - } - - private static LanguageLevel getLastIncompatibleLanguageLevelForSignature( - @Nonnull String signature, - @Nonnull LanguageLevel languageLevel, - @Nonnull Set forbiddenApi - ) { - if (forbiddenApi.contains(signature)) { - return languageLevel; - } - if (languageLevel.compareTo(ourHighestKnownLanguage) == 0) { - return null; - } - LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1]; - Set nextForbiddenApi = getForbiddenApi(nextLanguageLevel); - return nextForbiddenApi != null ? getLastIncompatibleLanguageLevelForSignature(signature, nextLanguageLevel, nextForbiddenApi) : null; - } - - /** - * please leave public for JavaAPIUsagesInspectionTest#testCollectSinceApiUsages - */ - @Nullable - public static String getSignature(@Nullable PsiMember member) { - if (member instanceof PsiClass psiClass) { - return psiClass.getQualifiedName(); - } - if (member instanceof PsiField) { - String containingClass = getSignature(member.getContainingClass()); - return containingClass == null ? null : containingClass + "#" + member.getName(); - } - if (member instanceof PsiMethod method) { - String containingClass = getSignature(member.getContainingClass()); - if (containingClass == null) { + /** + * please leave public for JavaAPIUsagesInspectionTest#testCollectSinceApiUsages + */ + @Nullable + public static String getSignature(@Nullable PsiMember member) { + if (member instanceof PsiClass psiClass) { + return psiClass.getQualifiedName(); + } + if (member instanceof PsiField) { + String containingClass = getSignature(member.getContainingClass()); + return containingClass == null ? null : containingClass + "#" + member.getName(); + } + if (member instanceof PsiMethod method) { + String containingClass = getSignature(member.getContainingClass()); + if (containingClass == null) { + return null; + } + + StringBuilder buf = new StringBuilder(); + buf.append(containingClass).append('#').append(method.getName()).append('('); + for (PsiType type : method.getSignature(PsiSubstitutor.EMPTY).getParameterTypes()) { + buf.append(type.getCanonicalText()).append(";"); + } + buf.append(')'); + return buf.toString(); + } return null; - } - - StringBuilder buf = new StringBuilder(); - buf.append(containingClass).append('#').append(method.getName()).append('('); - for (PsiType type : method.getSignature(PsiSubstitutor.EMPTY).getParameterTypes()) { - buf.append(type.getCanonicalText()).append(";"); - } - buf.append(')'); - return buf.toString(); } - return null; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/SuspiciousMethodCallUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/SuspiciousMethodCallUtil.java index e069be0ff9..e6c880dde1 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/SuspiciousMethodCallUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/SuspiciousMethodCallUtil.java @@ -26,376 +26,426 @@ public class SuspiciousMethodCallUtil { - // List.of/Set.of are unnecessary here as they don't accept nulls - private static final CallMatcher.Simple SINGLETON_COLLECTION = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singletonList", "singleton").parameterCount(1); - - private static void setupPatternMethods(PsiManager manager, GlobalSearchScope searchScope, List patternMethods) { - final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(manager.getProject()); - final PsiClass collectionClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_COLLECTION, searchScope); - PsiClassType object = PsiType.getJavaLangObject(manager, searchScope); - PsiType[] javaLangObject = {object}; - PsiType[] twoObjects = { - object, - object - }; - MethodSignature removeSignature = - MethodSignatureUtil.createMethodSignature("remove", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - if (collectionClass != null) { - PsiMethod remove = MethodSignatureUtil.findMethodBySignature(collectionClass, removeSignature, false); - addMethod(remove, 0, patternMethods, 0); - - addSingleParameterMethod(patternMethods, collectionClass, "contains", object); - - if (PsiUtil.isLanguageLevel5OrHigher(collectionClass)) { - PsiClassType wildcardCollection = javaPsiFacade.getElementFactory().createType(collectionClass, PsiWildcardType.createUnbounded(manager)); - addSingleParameterMethod(patternMethods, collectionClass, "removeAll", wildcardCollection); - addSingleParameterMethod(patternMethods, collectionClass, "retainAll", wildcardCollection); - } - } + // List.of/Set.of are unnecessary here as they don't accept nulls + private static final CallMatcher.Simple SINGLETON_COLLECTION = + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singletonList", "singleton").parameterCount(1); + + private static void setupPatternMethods(PsiManager manager, GlobalSearchScope searchScope, List patternMethods) { + final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(manager.getProject()); + final PsiClass collectionClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_COLLECTION, searchScope); + PsiClassType object = PsiType.getJavaLangObject(manager, searchScope); + PsiType[] javaLangObject = {object}; + PsiType[] twoObjects = { + object, + object + }; + MethodSignature removeSignature = + MethodSignatureUtil.createMethodSignature("remove", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + if (collectionClass != null) { + PsiMethod remove = MethodSignatureUtil.findMethodBySignature(collectionClass, removeSignature, false); + addMethod(remove, 0, patternMethods, 0); + + addSingleParameterMethod(patternMethods, collectionClass, "contains", object); + + if (PsiUtil.isLanguageLevel5OrHigher(collectionClass)) { + PsiClassType wildcardCollection = + javaPsiFacade.getElementFactory().createType(collectionClass, PsiWildcardType.createUnbounded(manager)); + addSingleParameterMethod(patternMethods, collectionClass, "removeAll", wildcardCollection); + addSingleParameterMethod(patternMethods, collectionClass, "retainAll", wildcardCollection); + } + } - final PsiClass listClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_LIST, searchScope); - if (listClass != null) { - addSingleParameterMethod(patternMethods, listClass, "indexOf", object); - addSingleParameterMethod(patternMethods, listClass, "lastIndexOf", object); - } + final PsiClass listClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_LIST, searchScope); + if (listClass != null) { + addSingleParameterMethod(patternMethods, listClass, "indexOf", object); + addSingleParameterMethod(patternMethods, listClass, "lastIndexOf", object); + } - final PsiClass mapClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_MAP, searchScope); - if (mapClass != null) { - PsiMethod remove = MethodSignatureUtil.findMethodBySignature(mapClass, removeSignature, false); - addMethod(remove, 0, patternMethods, 0); - - addSingleParameterMethod(patternMethods, mapClass, "get", object); - - PsiTypeParameter[] typeParameters = mapClass.getTypeParameters(); - if (typeParameters.length > 0) { - MethodSignature getOrDefaultSignature = MethodSignatureUtil.createMethodSignature("getOrDefault", - new PsiType[]{ - object, - PsiSubstitutor.EMPTY.substitute(typeParameters[1]) - }, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod getOrDefault = MethodSignatureUtil.findMethodBySignature(mapClass, getOrDefaultSignature, false); - addMethod(getOrDefault, 0, patternMethods, 0); - } - - MethodSignature removeWithDefaultSignature = MethodSignatureUtil.createMethodSignature("remove", - twoObjects, - PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod removeWithDefault = MethodSignatureUtil.findMethodBySignature(mapClass, removeWithDefaultSignature, false); - addMethod(removeWithDefault, 0, patternMethods, 0); - addMethod(removeWithDefault, 1, patternMethods, 1); - - addSingleParameterMethod(patternMethods, mapClass, "containsKey", object); - - MethodSignature containsValueSignature = MethodSignatureUtil.createMethodSignature("containsValue", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(mapClass, containsValueSignature, false); - addMethod(containsValue, 1, patternMethods, 0); - } + final PsiClass mapClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_MAP, searchScope); + if (mapClass != null) { + PsiMethod remove = MethodSignatureUtil.findMethodBySignature(mapClass, removeSignature, false); + addMethod(remove, 0, patternMethods, 0); + + addSingleParameterMethod(patternMethods, mapClass, "get", object); + + PsiTypeParameter[] typeParameters = mapClass.getTypeParameters(); + if (typeParameters.length > 0) { + MethodSignature getOrDefaultSignature = MethodSignatureUtil.createMethodSignature("getOrDefault", + new PsiType[]{object, PsiSubstitutor.EMPTY.substitute(typeParameters[1])}, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod getOrDefault = MethodSignatureUtil.findMethodBySignature(mapClass, getOrDefaultSignature, false); + addMethod(getOrDefault, 0, patternMethods, 0); + } - final PsiClass concurrentMapClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_CONCURRENT_HASH_MAP, searchScope); - if (concurrentMapClass != null) { - MethodSignature containsSignature = MethodSignatureUtil.createMethodSignature("contains", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod contains = MethodSignatureUtil.findMethodBySignature(concurrentMapClass, containsSignature, false); - addMethod(contains, 1, patternMethods, 0); - } - PsiClass guavaTable = javaPsiFacade.findClass("com.google.common.collect.Table", searchScope); - if (guavaTable != null) { - MethodSignature getSignature = - MethodSignatureUtil.createMethodSignature("get", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod get = MethodSignatureUtil.findMethodBySignature(guavaTable, getSignature, false); - addMethod(get, 0, patternMethods, 0); - addMethod(get, 1, patternMethods, 1); - - MethodSignature containsSignature = - MethodSignatureUtil.createMethodSignature("contains", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod contains = MethodSignatureUtil.findMethodBySignature(guavaTable, containsSignature, false); - addMethod(contains, 0, patternMethods, 0); - addMethod(contains, 1, patternMethods, 1); - - MethodSignature containsRowSignature = - MethodSignatureUtil.createMethodSignature("containsRow", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsRow = MethodSignatureUtil.findMethodBySignature(guavaTable, containsRowSignature, false); - addMethod(containsRow, 0, patternMethods, 0); - - MethodSignature containsColumnSignature = - MethodSignatureUtil.createMethodSignature("containsColumn", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsColumn = MethodSignatureUtil.findMethodBySignature(guavaTable, containsColumnSignature, false); - addMethod(containsColumn, 1, patternMethods, 0); - - MethodSignature containsValueSignature = - MethodSignatureUtil.createMethodSignature("containsValue", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(guavaTable, containsValueSignature, false); - addMethod(containsValue, 2, patternMethods, 0); - - MethodSignature removeByRowAndColumnSignature = - MethodSignatureUtil.createMethodSignature("remove", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod removeByRowAndColumn = MethodSignatureUtil.findMethodBySignature(guavaTable, removeByRowAndColumnSignature, false); - addMethod(removeByRowAndColumn, 0, patternMethods, 0); - addMethod(removeByRowAndColumn, 1, patternMethods, 1); - } + MethodSignature removeWithDefaultSignature = MethodSignatureUtil.createMethodSignature( + "remove", + twoObjects, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod removeWithDefault = MethodSignatureUtil.findMethodBySignature(mapClass, removeWithDefaultSignature, false); + addMethod(removeWithDefault, 0, patternMethods, 0); + addMethod(removeWithDefault, 1, patternMethods, 1); + + addSingleParameterMethod(patternMethods, mapClass, "containsKey", object); + + MethodSignature containsValueSignature = MethodSignatureUtil.createMethodSignature( + "containsValue", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(mapClass, containsValueSignature, false); + addMethod(containsValue, 1, patternMethods, 0); + } - PsiClass guavaMultimap = javaPsiFacade.findClass("com.google.common.collect.Multimap", searchScope); - if (guavaMultimap != null) { - MethodSignature containsKeySignature = - MethodSignatureUtil.createMethodSignature("containsKey", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsKey = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsKeySignature, false); - addMethod(containsKey, 0, patternMethods, 0); - - MethodSignature containsValueSignature = - MethodSignatureUtil.createMethodSignature("containsValue", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsValueSignature, false); - addMethod(containsValue, 1, patternMethods, 0); - - MethodSignature containsEntrySignature = - MethodSignatureUtil.createMethodSignature("containsEntry", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsEntry = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsEntrySignature, false); - addMethod(containsEntry, 0, patternMethods, 0); - addMethod(containsEntry, 1, patternMethods, 1); - - MethodSignature removeByKeyAndValueSignature = - MethodSignatureUtil.createMethodSignature("remove", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod removeByKeyAndValue = MethodSignatureUtil.findMethodBySignature(guavaMultimap, removeByKeyAndValueSignature, false); - addMethod(removeByKeyAndValue, 0, patternMethods, 0); - addMethod(removeByKeyAndValue, 1, patternMethods, 1); - - MethodSignature removeAllSignature = - MethodSignatureUtil.createMethodSignature("removeAll", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod removeAll = MethodSignatureUtil.findMethodBySignature(guavaMultimap, removeAllSignature, false); - addMethod(removeAll, 0, patternMethods, 0); - } + final PsiClass concurrentMapClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_CONCURRENT_HASH_MAP, searchScope); + if (concurrentMapClass != null) { + MethodSignature containsSignature = + MethodSignatureUtil.createMethodSignature("contains", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod contains = MethodSignatureUtil.findMethodBySignature(concurrentMapClass, containsSignature, false); + addMethod(contains, 1, patternMethods, 0); + } + PsiClass guavaTable = javaPsiFacade.findClass("com.google.common.collect.Table", searchScope); + if (guavaTable != null) { + MethodSignature getSignature = + MethodSignatureUtil.createMethodSignature("get", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod get = MethodSignatureUtil.findMethodBySignature(guavaTable, getSignature, false); + addMethod(get, 0, patternMethods, 0); + addMethod(get, 1, patternMethods, 1); + + MethodSignature containsSignature = + MethodSignatureUtil.createMethodSignature("contains", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod contains = MethodSignatureUtil.findMethodBySignature(guavaTable, containsSignature, false); + addMethod(contains, 0, patternMethods, 0); + addMethod(contains, 1, patternMethods, 1); + + MethodSignature containsRowSignature = MethodSignatureUtil.createMethodSignature( + "containsRow", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsRow = MethodSignatureUtil.findMethodBySignature(guavaTable, containsRowSignature, false); + addMethod(containsRow, 0, patternMethods, 0); + + MethodSignature containsColumnSignature = MethodSignatureUtil.createMethodSignature( + "containsColumn", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsColumn = MethodSignatureUtil.findMethodBySignature(guavaTable, containsColumnSignature, false); + addMethod(containsColumn, 1, patternMethods, 0); + + MethodSignature containsValueSignature = MethodSignatureUtil.createMethodSignature( + "containsValue", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(guavaTable, containsValueSignature, false); + addMethod(containsValue, 2, patternMethods, 0); + + MethodSignature removeByRowAndColumnSignature = + MethodSignatureUtil.createMethodSignature("remove", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod removeByRowAndColumn = MethodSignatureUtil.findMethodBySignature(guavaTable, removeByRowAndColumnSignature, false); + addMethod(removeByRowAndColumn, 0, patternMethods, 0); + addMethod(removeByRowAndColumn, 1, patternMethods, 1); + } - PsiClass guavaMultiset = javaPsiFacade.findClass("com.google.common.collect.Multiset", searchScope); - if (guavaMultiset != null) { - MethodSignature countSignature = - MethodSignatureUtil.createMethodSignature("count", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod count = MethodSignatureUtil.findMethodBySignature(guavaMultiset, countSignature, false); - addMethod(count, 0, patternMethods, 0); - } + PsiClass guavaMultimap = javaPsiFacade.findClass("com.google.common.collect.Multimap", searchScope); + if (guavaMultimap != null) { + MethodSignature containsKeySignature = MethodSignatureUtil.createMethodSignature( + "containsKey", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsKey = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsKeySignature, false); + addMethod(containsKey, 0, patternMethods, 0); + + MethodSignature containsValueSignature = MethodSignatureUtil.createMethodSignature( + "containsValue", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsValueSignature, false); + addMethod(containsValue, 1, patternMethods, 0); + + MethodSignature containsEntrySignature = + MethodSignatureUtil.createMethodSignature("containsEntry", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod containsEntry = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsEntrySignature, false); + addMethod(containsEntry, 0, patternMethods, 0); + addMethod(containsEntry, 1, patternMethods, 1); + + MethodSignature removeByKeyAndValueSignature = + MethodSignatureUtil.createMethodSignature("remove", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod removeByKeyAndValue = MethodSignatureUtil.findMethodBySignature(guavaMultimap, removeByKeyAndValueSignature, false); + addMethod(removeByKeyAndValue, 0, patternMethods, 0); + addMethod(removeByKeyAndValue, 1, patternMethods, 1); + + MethodSignature removeAllSignature = + MethodSignatureUtil.createMethodSignature("removeAll", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod removeAll = MethodSignatureUtil.findMethodBySignature(guavaMultimap, removeAllSignature, false); + addMethod(removeAll, 0, patternMethods, 0); + } - PsiClass guavaCache = javaPsiFacade.findClass("com.google.common.cache.Cache", searchScope); - if (guavaCache != null) { - MethodSignature getIfPresentSignature = - MethodSignatureUtil.createMethodSignature("getIfPresent", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod getIfPresent = MethodSignatureUtil.findMethodBySignature(guavaCache, getIfPresentSignature, false); - addMethod(getIfPresent, 0, patternMethods, 0); - MethodSignature invalidateSignature = - MethodSignatureUtil.createMethodSignature("invalidate", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod invalidate = MethodSignatureUtil.findMethodBySignature(guavaCache, invalidateSignature, false); - addMethod(invalidate, 0, patternMethods, 0); - } - } - - @Contract(value = "null -> false", pure = true) - public static boolean isCollectionAcceptingMethod(@Nullable String name) { - return "removeAll".equals(name) || "retainAll".equals(name) || "containsAll".equals(name); - } - - - private static void addSingleParameterMethod(List patternMethods, - PsiClass methodClass, String methodName, PsiClassType parameterType) { - MethodSignature signature = MethodSignatureUtil - .createMethodSignature(methodName, new PsiType[]{parameterType}, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod method = MethodSignatureUtil.findMethodBySignature(methodClass, signature, false); - addMethod(method, 0, patternMethods, 0); - } - - private static void addMethod(final PsiMethod patternMethod, - int typeParamIndex, - List patternMethods, - int argIdx) { - if (patternMethod != null) { - patternMethods.add(new PatternMethod(patternMethod, typeParamIndex, argIdx)); - } - } + PsiClass guavaMultiset = javaPsiFacade.findClass("com.google.common.collect.Multiset", searchScope); + if (guavaMultiset != null) { + MethodSignature countSignature = + MethodSignatureUtil.createMethodSignature("count", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod count = MethodSignatureUtil.findMethodBySignature(guavaMultiset, countSignature, false); + addMethod(count, 0, patternMethods, 0); + } - private static boolean isInheritorOrSelf(PsiMethod inheritorCandidate, PsiMethod base) { - PsiClass aClass = inheritorCandidate.getContainingClass(); - PsiClass bClass = base.getContainingClass(); - if (aClass == null || bClass == null) { - return false; - } - PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(bClass, aClass, PsiSubstitutor.EMPTY); - return substitutor != null && - MethodSignatureUtil.findMethodBySignature(bClass, inheritorCandidate.getSignature(substitutor), false) == base; - } - - @Nullable - public static String getSuspiciousMethodCallMessage(@Nonnull PsiMethodCallExpression methodCall, - PsiExpression arg, - PsiType argType, - boolean reportConvertibleMethodCalls, - @Nonnull List patternMethods, - int idx) { - final PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); - - if (arg instanceof PsiConditionalExpression && - argType != null && - argType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) && - PsiPolyExpressionUtil.isPolyExpression(arg)) { - return null; + PsiClass guavaCache = javaPsiFacade.findClass("com.google.common.cache.Cache", searchScope); + if (guavaCache != null) { + MethodSignature getIfPresentSignature = MethodSignatureUtil.createMethodSignature( + "getIfPresent", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod getIfPresent = MethodSignatureUtil.findMethodBySignature(guavaCache, getIfPresentSignature, false); + addMethod(getIfPresent, 0, patternMethods, 0); + MethodSignature invalidateSignature = + MethodSignatureUtil.createMethodSignature("invalidate", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod invalidate = MethodSignatureUtil.findMethodBySignature(guavaCache, invalidateSignature, false); + addMethod(invalidate, 0, patternMethods, 0); + } } - return getSuspiciousMethodCallMessage(methodExpression, argType, reportConvertibleMethodCalls, patternMethods, idx); - } - - @Nullable - public static String getSuspiciousMethodCallMessage( - PsiReferenceExpression methodExpression, - PsiType argType, - boolean reportConvertibleMethodCalls, - @Nonnull List patternMethods, - int argIdx - ) { - final PsiExpression qualifier = methodExpression.getQualifierExpression(); - if (qualifier == null || qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) { - return null; + + @Contract(value = "null -> false", pure = true) + public static boolean isCollectionAcceptingMethod(@Nullable String name) { + return "removeAll".equals(name) || "retainAll".equals(name) || "containsAll".equals(name); } - if (argType instanceof PsiPrimitiveType primitiveType) { - argType = primitiveType.getBoxedType(methodExpression); + + + private static void addSingleParameterMethod( + List patternMethods, + PsiClass methodClass, + String methodName, + PsiClassType parameterType + ) { + MethodSignature signature = MethodSignatureUtil + .createMethodSignature(methodName, new PsiType[]{parameterType}, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod method = MethodSignatureUtil.findMethodBySignature(methodClass, signature, false); + addMethod(method, 0, patternMethods, 0); } - if (argType == null) { - return null; + private static void addMethod( + final PsiMethod patternMethod, + int typeParamIndex, + List patternMethods, + int argIdx + ) { + if (patternMethod != null) { + patternMethods.add(new PatternMethod(patternMethod, typeParamIndex, argIdx)); + } } - final JavaResolveResult resolveResult = methodExpression.advancedResolve(false); - PsiElement element = resolveResult.getElement(); - if (!(element instanceof PsiMethod)) { - return null; + private static boolean isInheritorOrSelf(PsiMethod inheritorCandidate, PsiMethod base) { + PsiClass aClass = inheritorCandidate.getContainingClass(); + PsiClass bClass = base.getContainingClass(); + if (aClass == null || bClass == null) { + return false; + } + PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(bClass, aClass, PsiSubstitutor.EMPTY); + return substitutor != null && + MethodSignatureUtil.findMethodBySignature(bClass, inheritorCandidate.getSignature(substitutor), false) == base; } - PsiMethod calleeMethod = (PsiMethod) element; - NullableLazyValue lazyContextMethod = - NullableLazyValue.createValue(() -> PsiTreeUtil.getParentOfType(methodExpression, PsiMethod.class)); - - //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (patternMethods) { - if (patternMethods.isEmpty()) { - setupPatternMethods(methodExpression.getManager(), methodExpression.getResolveScope(), patternMethods); - } + + @Nullable + public static String getSuspiciousMethodCallMessage( + @Nonnull PsiMethodCallExpression methodCall, + PsiExpression arg, + PsiType argType, + boolean reportConvertibleMethodCalls, + @Nonnull List patternMethods, + int idx + ) { + final PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); + + if (arg instanceof PsiConditionalExpression + && argType != null + && argType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) + && PsiPolyExpressionUtil.isPolyExpression(arg)) { + return null; + } + return getSuspiciousMethodCallMessage(methodExpression, argType, reportConvertibleMethodCalls, patternMethods, idx); } - for (PatternMethod patternMethod : patternMethods) { - PsiMethod method = patternMethod.patternMethod; - if (!method.getName().equals(methodExpression.getReferenceName())) { - continue; - } - if (patternMethod.argIdx != argIdx) { - continue; - } - - //we are in collections method implementation - PsiMethod contextMethod = lazyContextMethod.getValue(); - if (contextMethod != null && isInheritorOrSelf(contextMethod, method)) { - return null; - } - - final PsiClass calleeClass = calleeMethod.getContainingClass(); - PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - final PsiClass patternClass = method.getContainingClass(); - assert patternClass != null; - assert calleeClass != null; - substitutor = TypeConversionUtil.getClassSubstitutor(patternClass, calleeClass, substitutor); - if (substitutor == null) { - continue; - } - - if (!method.getSignature(substitutor).equals(calleeMethod.getSignature(resolveResult.getSubstitutor()))) { - continue; - } - - PsiTypeParameter[] typeParameters = patternClass.getTypeParameters(); - if (typeParameters.length <= patternMethod.typeParameterIdx) { - return null; - } - final PsiTypeParameter typeParameter = typeParameters[patternMethod.typeParameterIdx]; - PsiType typeParamMapping = substitutor.substitute(typeParameter); - if (typeParamMapping == null) { - return null; - } - - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (parameters.length == 1 && ("removeAll".equals(method.getName()) || "retainAll".equals(method.getName()))) { - PsiType paramType = parameters[0].getType(); - if (InheritanceUtil.isInheritor(paramType, CommonClassNames.JAVA_UTIL_COLLECTION)) { - PsiType qualifierType = qualifier.getType(); - if (qualifierType != null) { - final PsiType itemType = JavaGenericsUtil.getCollectionItemType(argType, calleeMethod.getResolveScope()); - final PsiType qualifierItemType = JavaGenericsUtil.getCollectionItemType(qualifierType, calleeMethod.getResolveScope()); - if (qualifierItemType != null && itemType != null && !qualifierItemType.isAssignableFrom(itemType)) { - if (TypeUtils.isJavaLangObject(itemType) && hasNullCollectionArg(methodExpression)) { - // removeAll(Collections.singleton(null)) is a valid way to remove all nulls from collection + @Nullable + public static String getSuspiciousMethodCallMessage( + PsiReferenceExpression methodExpression, + PsiType argType, + boolean reportConvertibleMethodCalls, + @Nonnull List patternMethods, + int argIdx + ) { + final PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (qualifier == null || qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) { + return null; + } + if (argType instanceof PsiPrimitiveType primitiveType) { + argType = primitiveType.getBoxedType(methodExpression); + } + + if (argType == null) { + return null; + } + + final JavaResolveResult resolveResult = methodExpression.advancedResolve(false); + PsiElement element = resolveResult.getElement(); + if (!(element instanceof PsiMethod)) { + return null; + } + PsiMethod calleeMethod = (PsiMethod)element; + NullableLazyValue lazyContextMethod = + NullableLazyValue.createValue(() -> PsiTreeUtil.getParentOfType(methodExpression, PsiMethod.class)); + + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (patternMethods) { + if (patternMethods.isEmpty()) { + setupPatternMethods(methodExpression.getManager(), methodExpression.getResolveScope(), patternMethods); + } + } + + for (PatternMethod patternMethod : patternMethods) { + PsiMethod method = patternMethod.patternMethod; + if (!method.getName().equals(methodExpression.getReferenceName())) { + continue; + } + if (patternMethod.argIdx != argIdx) { + continue; + } + + //we are in collections method implementation + PsiMethod contextMethod = lazyContextMethod.getValue(); + if (contextMethod != null && isInheritorOrSelf(contextMethod, method)) { return null; - } - if (qualifierItemType.isConvertibleFrom(itemType) && !reportConvertibleMethodCalls) { + } + + final PsiClass calleeClass = calleeMethod.getContainingClass(); + PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + final PsiClass patternClass = method.getContainingClass(); + assert patternClass != null; + assert calleeClass != null; + substitutor = TypeConversionUtil.getClassSubstitutor(patternClass, calleeClass, substitutor); + if (substitutor == null) { + continue; + } + + if (!method.getSignature(substitutor).equals(calleeMethod.getSignature(resolveResult.getSubstitutor()))) { + continue; + } + + PsiTypeParameter[] typeParameters = patternClass.getTypeParameters(); + if (typeParameters.length <= patternMethod.typeParameterIdx) { return null; - } - return InspectionsBundle.message("inspection.suspicious.collections.method.calls.problem.descriptor", - PsiFormatUtil.formatType(qualifierType, 0, PsiSubstitutor.EMPTY), - PsiFormatUtil.formatType(itemType, 0, PsiSubstitutor.EMPTY), - "objects"); } - } - return null; - } - } - - String message = null; - if (typeParamMapping instanceof PsiCapturedWildcardType capturedWildcardType) { - typeParamMapping = capturedWildcardType.getWildcard(); - } - if (!typeParamMapping.isAssignableFrom(argType)) { - if (typeParamMapping.isConvertibleFrom(argType)) { - if (reportConvertibleMethodCalls) { - message = InspectionLocalize.inspectionSuspiciousCollectionsMethodCallsProblemDescriptor1(PsiFormatUtil.formatMethod( - calleeMethod, - substitutor, - PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_CONTAINING_CLASS, - PsiFormatUtilBase.SHOW_TYPE - )).get(); - } - } else { - PsiType qualifierType = qualifier.getType(); - if (qualifierType != null) { - message = InspectionsBundle.message("inspection.suspicious.collections.method.calls.problem.descriptor", - PsiFormatUtil.formatType(qualifierType, 0, PsiSubstitutor.EMPTY), - PsiFormatUtil.formatType(argType, 0, PsiSubstitutor.EMPTY), - getPreciseObjectTitle(patternClass, patternMethod.typeParameterIdx)); - } + final PsiTypeParameter typeParameter = typeParameters[patternMethod.typeParameterIdx]; + PsiType typeParamMapping = substitutor.substitute(typeParameter); + if (typeParamMapping == null) { + return null; + } + + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length == 1 && ("removeAll".equals(method.getName()) || "retainAll".equals(method.getName()))) { + PsiType paramType = parameters[0].getType(); + if (InheritanceUtil.isInheritor(paramType, CommonClassNames.JAVA_UTIL_COLLECTION)) { + PsiType qualifierType = qualifier.getType(); + if (qualifierType != null) { + final PsiType itemType = JavaGenericsUtil.getCollectionItemType(argType, calleeMethod.getResolveScope()); + final PsiType qualifierItemType = + JavaGenericsUtil.getCollectionItemType(qualifierType, calleeMethod.getResolveScope()); + if (qualifierItemType != null && itemType != null && !qualifierItemType.isAssignableFrom(itemType)) { + if (TypeUtils.isJavaLangObject(itemType) && hasNullCollectionArg(methodExpression)) { + // removeAll(Collections.singleton(null)) is a valid way to remove all nulls from collection + return null; + } + if (qualifierItemType.isConvertibleFrom(itemType) && !reportConvertibleMethodCalls) { + return null; + } + return InspectionsBundle.message( + "inspection.suspicious.collections.method.calls.problem.descriptor", + PsiFormatUtil.formatType(qualifierType, 0, PsiSubstitutor.EMPTY), + PsiFormatUtil.formatType(itemType, 0, PsiSubstitutor.EMPTY), + "objects" + ); + } + } + return null; + } + } + + String message = null; + if (typeParamMapping instanceof PsiCapturedWildcardType capturedWildcardType) { + typeParamMapping = capturedWildcardType.getWildcard(); + } + if (!typeParamMapping.isAssignableFrom(argType)) { + if (typeParamMapping.isConvertibleFrom(argType)) { + if (reportConvertibleMethodCalls) { + message = InspectionLocalize.inspectionSuspiciousCollectionsMethodCallsProblemDescriptor1( + PsiFormatUtil.formatMethod( + calleeMethod, + substitutor, + PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_CONTAINING_CLASS, + PsiFormatUtilBase.SHOW_TYPE + ) + ).get(); + } + } + else { + PsiType qualifierType = qualifier.getType(); + if (qualifierType != null) { + message = InspectionsBundle.message( + "inspection.suspicious.collections.method.calls.problem.descriptor", + PsiFormatUtil.formatType(qualifierType, 0, PsiSubstitutor.EMPTY), + PsiFormatUtil.formatType(argType, 0, PsiSubstitutor.EMPTY), + getPreciseObjectTitle(patternClass, patternMethod.typeParameterIdx) + ); + } + } + } + return message; } - } - return message; + return null; } - return null; - } - private static String getPreciseObjectTitle(PsiClass patternClass, int index) { - if (InheritanceUtil.isInheritor(patternClass, CommonClassNames.JAVA_UTIL_MAP)) { - return index == 0 ? "keys" : "values"; + private static String getPreciseObjectTitle(PsiClass patternClass, int index) { + if (InheritanceUtil.isInheritor(patternClass, CommonClassNames.JAVA_UTIL_MAP)) { + return index == 0 ? "keys" : "values"; + } + + return "objects"; } - return "objects"; - } - - private static boolean hasNullCollectionArg(PsiReferenceExpression methodExpression) { - PsiMethodCallExpression call = ObjectUtil.tryCast(methodExpression.getParent(), PsiMethodCallExpression.class); - if (call != null) { - PsiExpression arg = - ExpressionUtils.resolveExpression(ArrayUtil.getFirstElement(call.getArgumentList().getExpressions())); - PsiMethodCallExpression argCall = - ObjectUtil.tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiMethodCallExpression.class); - return SINGLETON_COLLECTION.test(argCall) && ExpressionUtils.isNullLiteral(argCall.getArgumentList().getExpressions()[0]); + private static boolean hasNullCollectionArg(PsiReferenceExpression methodExpression) { + PsiMethodCallExpression call = ObjectUtil.tryCast(methodExpression.getParent(), PsiMethodCallExpression.class); + if (call != null) { + PsiExpression arg = + ExpressionUtils.resolveExpression(ArrayUtil.getFirstElement(call.getArgumentList().getExpressions())); + PsiMethodCallExpression argCall = + ObjectUtil.tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiMethodCallExpression.class); + return SINGLETON_COLLECTION.test(argCall) && ExpressionUtils.isNullLiteral(argCall.getArgumentList().getExpressions()[0]); + } + return false; } - return false; - } - - public static class PatternMethod { - PsiMethod patternMethod; - int typeParameterIdx; - int argIdx; - - PatternMethod(PsiMethod patternMethod, int typeParameterIdx, int argIdx) { - this.patternMethod = patternMethod; - this.typeParameterIdx = typeParameterIdx; - this.argIdx = argIdx; + + public static class PatternMethod { + PsiMethod patternMethod; + int typeParameterIdx; + int argIdx; + + PatternMethod(PsiMethod patternMethod, int typeParameterIdx, int argIdx) { + this.patternMethod = patternMethod; + this.typeParameterIdx = typeParameterIdx; + this.argIdx = argIdx; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionBase.java index 9db28e51a7..e82397b779 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionBase.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionBase.java @@ -49,1117 +49,1174 @@ import static com.intellij.java.language.patterns.PsiJavaPatterns.psiMethod; public abstract class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspectionTool { - private static final Logger LOG = Logger.getInstance(NullableStuffInspectionBase.class); - - @Override - @Nonnull - public PsiElementVisitor buildVisitorImpl( - @Nonnull final ProblemsHolder holder, - boolean isOnTheFly, - LocalInspectionToolSession session, - NullableStuffInspectionState state - ) { - final PsiFile file = holder.getFile(); - if (!PsiUtil.isLanguageLevel5OrHigher(file) || nullabilityAnnotationsNotAvailable(file)) { - return PsiElementVisitor.EMPTY_VISITOR; - } - return new JavaElementVisitor() { - @Override - public void visitMethod(@Nonnull PsiMethod method) { - checkNullableStuffForMethod(method, holder, isOnTheFly, state); - } - - @Override - public void visitMethodReferenceExpression(@Nonnull PsiMethodReferenceExpression expression) { - checkMethodReference(expression, holder, state); - - JavaResolveResult result = expression.advancedResolve(false); - PsiElement target = result.getElement(); - if (target instanceof PsiMethod method) { - checkCollectionNullityOnAssignment( - expression, - LambdaUtil.getFunctionalInterfaceReturnType(expression), - result.getSubstitutor().substitute(method.getReturnType()) - ); - } - } - - @Override - @RequiredReadAction - public void visitField(@Nonnull PsiField field) { - final PsiType type = field.getType(); - final Annotated annotated = check(field, holder, type); - if (TypeConversionUtil.isPrimitiveAndNotNull(type)) { - return; + private static final Logger LOG = Logger.getInstance(NullableStuffInspectionBase.class); + + @Override + @Nonnull + public PsiElementVisitor buildVisitorImpl( + @Nonnull final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + NullableStuffInspectionState state + ) { + final PsiFile file = holder.getFile(); + if (!PsiUtil.isLanguageLevel5OrHigher(file) || nullabilityAnnotationsNotAvailable(file)) { + return PsiElementVisitor.EMPTY_VISITOR; } - Project project = holder.getProject(); - final NullableNotNullManager manager = NullableNotNullManager.getInstance(project); - if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) { - final String anno = annotated.isDeclaredNotNull ? manager.getDefaultNotNull() : manager.getDefaultNullable(); - final List annoToRemove = annotated.isDeclaredNotNull ? manager.getNullables() : manager.getNotNulls(); + return new JavaElementVisitor() { + @Override + public void visitMethod(@Nonnull PsiMethod method) { + checkNullableStuffForMethod(method, holder, isOnTheFly, state); + } - if (!checkNonStandardAnnotations(field, annotated, manager, anno, holder)) { - return; - } + @Override + public void visitMethodReferenceExpression(@Nonnull PsiMethodReferenceExpression expression) { + checkMethodReference(expression, holder, state); + + JavaResolveResult result = expression.advancedResolve(false); + PsiElement target = result.getElement(); + if (target instanceof PsiMethod method) { + checkCollectionNullityOnAssignment( + expression, + LambdaUtil.getFunctionalInterfaceReturnType(expression), + result.getSubstitutor().substitute(method.getReturnType()) + ); + } + } + + @Override + @RequiredReadAction + public void visitField(@Nonnull PsiField field) { + final PsiType type = field.getType(); + final Annotated annotated = check(field, holder, type); + if (TypeConversionUtil.isPrimitiveAndNotNull(type)) { + return; + } + Project project = holder.getProject(); + final NullableNotNullManager manager = NullableNotNullManager.getInstance(project); + if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) { + final String anno = annotated.isDeclaredNotNull ? manager.getDefaultNotNull() : manager.getDefaultNullable(); + final List annoToRemove = annotated.isDeclaredNotNull ? manager.getNullables() : manager.getNotNulls(); + + if (!checkNonStandardAnnotations(field, annotated, manager, anno, holder)) { + return; + } + + checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder, state); + + checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder, state); + } + } + + @Override + public void visitParameter(@Nonnull PsiParameter parameter) { + check(parameter, holder, parameter.getType()); + } + + @Override + public void visitTypeElement(@Nonnull PsiTypeElement type) { + NullableNotNullManager manager = NullableNotNullManager.getInstance(type.getProject()); + List annotations = getExclusiveAnnotations(type); + + checkType( + null, + holder, + type.getType(), + ContainerUtil.find(annotations, a -> manager.getNotNulls().contains(a.getQualifiedName())), + ContainerUtil.find(annotations, a -> manager.getNullables().contains(a.getQualifiedName())) + ); + } + + private List getExclusiveAnnotations(PsiTypeElement type) { + List annotations = ContainerUtil.newArrayList(type.getAnnotations()); + PsiTypeElement topMost = Objects.requireNonNull(SyntaxTraverser.psiApi().parents(type).filter(PsiTypeElement.class).last()); + PsiElement parent = topMost.getParent(); + if (parent instanceof PsiModifierListOwner && type.getType().equals(topMost.getType().getDeepComponentType())) { + PsiModifierList modifierList = ((PsiModifierListOwner)parent).getModifierList(); + if (modifierList != null) { + PsiAnnotation.TargetType[] targets = + ArrayUtil.remove(AnnotationTargetUtil.getTargetsForLocation(modifierList), PsiAnnotation.TargetType.TYPE_USE); + annotations.addAll(ContainerUtil.filter( + modifierList.getAnnotations(), + a -> AnnotationTargetUtil.isTypeAnnotation(a) && AnnotationTargetUtil.findAnnotationTarget(a, targets) == null + )); + } + } + return annotations; + } + + @Override + public void visitAnnotation(@Nonnull PsiAnnotation annotation) { + if (!AnnotationUtil.NOT_NULL.equals(annotation.getQualifiedName())) { + return; + } + + PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception"); + if (value instanceof PsiClassObjectAccessExpression classObjectAccessExpression) { + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(classObjectAccessExpression.getOperand().getType()); + if (psiClass != null && !hasStringConstructor(psiClass)) { + holder.registerProblem( + value, + "Custom exception class should have a constructor with a single message parameter of String type", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ); + } + } + } + + private boolean hasStringConstructor(PsiClass aClass) { + for (PsiMethod method : aClass.getConstructors()) { + PsiParameterList list = method.getParameterList(); + if (list.getParametersCount() == 1 && + list.getParameters()[0].getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return true; + } + } + return false; + } + + @Override + @RequiredReadAction + public void visitReferenceElement(@Nonnull PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + + checkNullableNotNullInstantiationConflict(reference); + + PsiElement list = reference.getParent(); + PsiElement psiClassElem = list instanceof PsiReferenceList ? list.getParent() : null; + PsiElement intf = reference.resolve(); + if (psiClassElem instanceof PsiClass psiClass && list == psiClass.getImplementsList() && + intf instanceof PsiClass intfClass && intfClass.isInterface()) { + String error = checkIndirectInheritance(psiClass, intfClass, state); + if (error != null) { + holder.registerProblem(reference, error); + } + } + } + + @RequiredReadAction + private void checkNullableNotNullInstantiationConflict(PsiJavaCodeReferenceElement reference) { + PsiElement element = reference.resolve(); + if (element instanceof PsiClass psiClass) { + PsiTypeParameter[] typeParameters = psiClass.getTypeParameters(); + PsiTypeElement[] typeArguments = getReferenceTypeArguments(reference); + if (typeParameters.length > 0 && typeParameters.length == typeArguments.length + && !(typeArguments[0].getType() instanceof PsiDiamondType)) { + for (int i = 0; i < typeParameters.length; i++) { + if (DfaPsiUtil.getTypeNullability(JavaPsiFacade.getElementFactory(element.getProject()) + .createType(typeParameters[i])) == Nullability.NOT_NULL + && DfaPsiUtil.getTypeNullability(typeArguments[i].getType()) != Nullability.NOT_NULL) { + holder.registerProblem( + typeArguments[i], + "Non-null type argument is expected", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ); + } + } + } + } + } + + private PsiTypeElement[] getReferenceTypeArguments(PsiJavaCodeReferenceElement reference) { + PsiReferenceParameterList typeArgList = reference.getParameterList(); + return typeArgList == null ? PsiTypeElement.EMPTY_ARRAY : typeArgList.getTypeParameterElements(); + } + + @Override + public void visitAssignmentExpression(@Nonnull PsiAssignmentExpression expression) { + checkCollectionNullityOnAssignment( + expression.getOperationSign(), + expression.getLExpression().getType(), + expression.getRExpression() + ); + } + + @Override + public void visitLocalVariable(@Nonnull PsiLocalVariable variable) { + PsiIdentifier identifier = variable.getNameIdentifier(); + if (identifier != null) { + checkCollectionNullityOnAssignment(identifier, variable.getType(), variable.getInitializer()); + } + } + + @Override + public void visitReturnStatement(@Nonnull PsiReturnStatement statement) { + PsiExpression returnValue = statement.getReturnValue(); + if (returnValue == null) { + return; + } + + checkCollectionNullityOnAssignment(statement.getReturnValue(), PsiTypesUtil.getMethodReturnType(statement), returnValue); + } + + @Override + public void visitLambdaExpression(@Nonnull PsiLambdaExpression lambda) { + super.visitLambdaExpression(lambda); + PsiElement body = lambda.getBody(); + if (body instanceof PsiExpression) { + checkCollectionNullityOnAssignment(body, LambdaUtil.getFunctionalInterfaceReturnType(lambda), (PsiExpression)body); + } + } + + @Override + public void visitCallExpression(@Nonnull PsiCallExpression callExpression) { + PsiExpressionList argList = callExpression.getArgumentList(); + JavaResolveResult result = callExpression.resolveMethodGenerics(); + PsiMethod method = (PsiMethod)result.getElement(); + if (method == null || argList == null) { + return; + } + + PsiSubstitutor substitutor = result.getSubstitutor(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiExpression[] arguments = argList.getExpressions(); + for (int i = 0; i < arguments.length; i++) { + PsiExpression argument = arguments[i]; + if (i < parameters.length && + (i < parameters.length - 1 || !MethodCallInstruction.isVarArgCall(method, substitutor, arguments, parameters))) { + checkCollectionNullityOnAssignment(argument, substitutor.substitute(parameters[i].getType()), argument); + } + } + } + + private void checkCollectionNullityOnAssignment( + @Nonnull PsiElement errorElement, + @Nullable PsiType expectedType, + @Nullable PsiExpression assignedExpression + ) { + if (assignedExpression == null) { + return; + } + + checkCollectionNullityOnAssignment(errorElement, expectedType, assignedExpression.getType()); + } + + private void checkCollectionNullityOnAssignment( + @Nonnull PsiElement errorElement, + @Nullable PsiType expectedType, + @Nullable PsiType assignedType + ) { + if (isNullableNotNullCollectionConflict(expectedType, assignedType, new HashSet<>())) { + holder.registerProblem( + errorElement, + "Assigning a collection of nullable elements into a collection of non-null elements", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ); + } + } - checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder, state); + private boolean isNullableNotNullCollectionConflict( + @Nullable PsiType expectedType, + @Nullable PsiType assignedType, + @Nonnull Set> visited + ) { + if (!visited.add(Couple.of(expectedType, assignedType))) { + return false; + } + + GlobalSearchScope scope = holder.getFile().getResolveScope(); + if (isNullityConflict( + JavaGenericsUtil.getCollectionItemType(expectedType, scope), + JavaGenericsUtil.getCollectionItemType(assignedType, scope) + )) { + return true; + } + + for (int i = 0; i <= 1; i++) { + PsiType expectedArg = PsiUtil.substituteTypeParameter(expectedType, CommonClassNames.JAVA_UTIL_MAP, i, false); + PsiType assignedArg = PsiUtil.substituteTypeParameter(assignedType, CommonClassNames.JAVA_UTIL_MAP, i, false); + if (isNullityConflict(expectedArg, assignedArg) || + expectedArg != null && assignedArg != null + && isNullableNotNullCollectionConflict(expectedArg, assignedArg, visited)) { + return true; + } + } - checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder, state); + return false; + } + + private boolean isNullityConflict(PsiType expected, PsiType assigned) { + return DfaPsiUtil.getTypeNullability(expected) == Nullability.NOT_NULL + && DfaPsiUtil.getTypeNullability(assigned) == Nullability.NULLABLE; + } + }; + } + + @Nullable + @RequiredReadAction + private String checkIndirectInheritance(PsiElement psiClass, PsiClass intf, NullableStuffInspectionState state) { + for (PsiMethod intfMethod : intf.getAllMethods()) { + PsiClass intfMethodClass = intfMethod.getContainingClass(); + PsiMethod overridingMethod = intfMethodClass == null ? null : + JavaOverridingMethodsSearcher.findOverridingMethod((PsiClass)psiClass, intfMethod, intfMethodClass); + PsiClass overridingMethodClass = overridingMethod == null ? null : overridingMethod.getContainingClass(); + if (overridingMethodClass != null && overridingMethodClass != psiClass) { + String error = checkIndirectInheritance(intfMethod, intfMethodClass, overridingMethod, overridingMethodClass, state); + if (error != null) { + return error; + } + } } - } - - @Override - public void visitParameter(@Nonnull PsiParameter parameter) { - check(parameter, holder, parameter.getType()); - } - - @Override - public void visitTypeElement(@Nonnull PsiTypeElement type) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(type.getProject()); - List annotations = getExclusiveAnnotations(type); - - checkType( - null, - holder, - type.getType(), - ContainerUtil.find(annotations, a -> manager.getNotNulls().contains(a.getQualifiedName())), - ContainerUtil.find(annotations, a -> manager.getNullables().contains(a.getQualifiedName())) - ); - } - - private List getExclusiveAnnotations(PsiTypeElement type) { - List annotations = ContainerUtil.newArrayList(type.getAnnotations()); - PsiTypeElement topMost = Objects.requireNonNull(SyntaxTraverser.psiApi().parents(type).filter(PsiTypeElement.class).last()); - PsiElement parent = topMost.getParent(); - if (parent instanceof PsiModifierListOwner && type.getType().equals(topMost.getType().getDeepComponentType())) { - PsiModifierList modifierList = ((PsiModifierListOwner)parent).getModifierList(); - if (modifierList != null) { - PsiAnnotation.TargetType[] targets = - ArrayUtil.remove(AnnotationTargetUtil.getTargetsForLocation(modifierList), PsiAnnotation.TargetType.TYPE_USE); - annotations.addAll(ContainerUtil.filter( - modifierList.getAnnotations(), - a -> AnnotationTargetUtil.isTypeAnnotation(a) && AnnotationTargetUtil.findAnnotationTarget(a, targets) == null - )); - } + return null; + } + + @Nullable + @RequiredReadAction + private String checkIndirectInheritance( + PsiMethod intfMethod, + PsiClass intfMethodClass, + PsiMethod overridingMethod, + PsiClass overridingMethodClass, + NullableStuffInspectionState state + ) { + if (isNullableOverridingNotNull(Annotated.from(overridingMethod), intfMethod, state)) { + return "Nullable method '" + overridingMethod.getName() + + "' from '" + overridingMethodClass.getName() + + "' implements non-null method from '" + intfMethodClass.getName() + "'"; } - return annotations; - } + if (isNonAnnotatedOverridingNotNull(overridingMethod, intfMethod, state)) { + return "Non-annotated method '" + overridingMethod.getName() + + "' from '" + overridingMethodClass.getName() + + "' implements non-null method from '" + intfMethodClass.getName() + "'"; + } + + PsiParameter[] overridingParameters = overridingMethod.getParameterList().getParameters(); + PsiParameter[] superParameters = intfMethod.getParameterList().getParameters(); + if (overridingParameters.length == superParameters.length) { + NullableNotNullManager manager = getNullityManager(intfMethod); + for (int i = 0; i < overridingParameters.length; i++) { + PsiParameter parameter = overridingParameters[i]; + List supers = Collections.singletonList(superParameters[i]); + if (findNullableSuperForNotNullParameter(parameter, supers, state) != null) { + return "Non-null parameter '" + parameter.getName() + + "' in method '" + overridingMethod.getName() + + "' from '" + overridingMethodClass.getName() + + "' should not override nullable parameter from '" + intfMethodClass.getName() + "'"; + } + if (findNotNullSuperForNonAnnotatedParameter(manager, parameter, supers, state) != null) { + return "Non-annotated parameter '" + parameter.getName() + + "' in method '" + overridingMethod.getName() + + "' from '" + overridingMethodClass.getName() + + "' should not override non-null parameter from '" + intfMethodClass.getName() + "'"; + } + if (isNotNullParameterOverridingNonAnnotated(manager, parameter, supers, state)) { + return "Non-null parameter '" + parameter.getName() + + "' in method '" + overridingMethod.getName() + + "' from '" + overridingMethodClass.getName() + + "' should not override non-annotated parameter from '" + intfMethodClass.getName() + "'"; + } + } + } + + return null; + } - @Override - public void visitAnnotation(@Nonnull PsiAnnotation annotation) { - if (!AnnotationUtil.NOT_NULL.equals(annotation.getQualifiedName())) { - return; + @RequiredReadAction + private void checkMethodReference( + PsiMethodReferenceExpression expression, + @Nonnull ProblemsHolder holder, + NullableStuffInspectionState state + ) { + PsiMethod superMethod = LambdaUtil.getFunctionalInterfaceMethod(expression); + PsiMethod targetMethod = ObjectUtil.tryCast(expression.resolve(), PsiMethod.class); + if (superMethod == null || targetMethod == null) { + return; } - PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception"); - if (value instanceof PsiClassObjectAccessExpression classObjectAccessExpression) { - PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(classObjectAccessExpression.getOperand().getType()); - if (psiClass != null && !hasStringConstructor(psiClass)) { + PsiElement refName = expression.getReferenceNameElement(); + assert refName != null; + if (isNullableOverridingNotNull(check(targetMethod, holder, expression.getType()), superMethod, state)) { + holder.registerProblem( + refName, + InspectionsBundle.message( + "inspection.nullable.problems.Nullable.method.overrides.NotNull", + getPresentableAnnoName(targetMethod), + getPresentableAnnoName(superMethod) + ), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ); + } + else if (isNonAnnotatedOverridingNotNull(targetMethod, superMethod, state)) { holder.registerProblem( - value, - "Custom exception class should have a constructor with a single message parameter of String type", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING + refName, + "Not annotated method is used as an override for a method annotated with " + getPresentableAnnoName(superMethod), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + createFixForNonAnnotatedOverridesNotNull(targetMethod, superMethod) ); - } } - } + } - private boolean hasStringConstructor(PsiClass aClass) { - for (PsiMethod method : aClass.getConstructors()) { - PsiParameterList list = method.getParameterList(); - if (list.getParametersCount() == 1 && - list.getParameters()[0].getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) { - return true; - } + protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) { + return null; + } + + private static boolean nullabilityAnnotationsNotAvailable(final PsiFile file) { + final Project project = file.getProject(); + final GlobalSearchScope scope = GlobalSearchScope.allScope(project); + final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + return ContainerUtil.find( + NullableNotNullManager.getInstance(project).getNullables(), + s -> facade.findClass(s, scope) != null + ) == null; + } + + private static boolean checkNonStandardAnnotations( + PsiField field, + Annotated annotated, + NullableNotNullManager manager, + String anno, + @Nonnull ProblemsHolder holder + ) { + if (!AnnotationUtil.isAnnotatingApplicable(field, anno)) { + PsiAnnotation annotation = Objects.requireNonNull(annotated.isDeclaredNullable ? annotated.nullable : annotated.notNull); + String message = "Not \'" + annotation.getQualifiedName() + "\' but \'" + anno + "\' would be used for code generation."; + final PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement(); + holder.registerProblem( + annotationNameReferenceElement != null && annotationNameReferenceElement.isPhysical() + ? annotationNameReferenceElement : field.getNameIdentifier(), + message, + ProblemHighlightType.WEAK_WARNING, + new ChangeNullableDefaultsFix(annotated.notNull, annotated.nullable, manager) + ); + return false; } - return false; - } - - @Override - @RequiredReadAction - public void visitReferenceElement(@Nonnull PsiJavaCodeReferenceElement reference) { - super.visitReferenceElement(reference); - - checkNullableNotNullInstantiationConflict(reference); - - PsiElement list = reference.getParent(); - PsiElement psiClassElem = list instanceof PsiReferenceList ? list.getParent() : null; - PsiElement intf = reference.resolve(); - if (psiClassElem instanceof PsiClass psiClass && list == psiClass.getImplementsList() && - intf instanceof PsiClass intfClass && intfClass.isInterface()) { - String error = checkIndirectInheritance(psiClass, intfClass, state); - if (error != null) { - holder.registerProblem(reference, error); - } + return true; + } + + @RequiredReadAction + private void checkAccessors( + PsiField field, + Annotated annotated, + Project project, + NullableNotNullManager manager, + final String anno, + final List annoToRemove, + @Nonnull ProblemsHolder holder, + NullableStuffInspectionState state + ) { + String propName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD); + final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); + final PsiMethod getter = PropertyUtilBase.findPropertyGetter(field.getContainingClass(), propName, isStatic, false); + final PsiIdentifier nameIdentifier = getter == null ? null : getter.getNameIdentifier(); + if (nameIdentifier != null && nameIdentifier.isPhysical()) { + if (PropertyUtil.getFieldOfGetter(getter) == field) { + AnnotateMethodFix getterAnnoFix = new AnnotateMethodFix(anno, ArrayUtil.toStringArray(annoToRemove)); + if (state.REPORT_NOT_ANNOTATED_GETTER) { + if (!manager.hasNullability(getter) && !TypeConversionUtil.isPrimitiveAndNotNull(getter.getReturnType())) { + holder.registerProblem( + nameIdentifier, + InspectionLocalize.inspectionNullableProblemsAnnotatedFieldGetterNotAnnotated(getPresentableAnnoName(field)) + .get(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + getterAnnoFix + ); + } + } + if (annotated.isDeclaredNotNull && isNullableNotInferred(getter, false) || + annotated.isDeclaredNullable && isNotNullNotInferred(getter, false, false)) { + holder.registerProblem( + nameIdentifier, + InspectionLocalize.inspectionNullableProblemsAnnotatedFieldGetterConflict( + getPresentableAnnoName(field), + getPresentableAnnoName(getter) + ).get(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + getterAnnoFix + ); + } + } } - } - - @RequiredReadAction - private void checkNullableNotNullInstantiationConflict(PsiJavaCodeReferenceElement reference) { - PsiElement element = reference.resolve(); - if (element instanceof PsiClass psiClass) { - PsiTypeParameter[] typeParameters = psiClass.getTypeParameters(); - PsiTypeElement[] typeArguments = getReferenceTypeArguments(reference); - if (typeParameters.length > 0 && typeParameters.length == typeArguments.length - && !(typeArguments[0].getType() instanceof PsiDiamondType)) { - for (int i = 0; i < typeParameters.length; i++) { - if (DfaPsiUtil.getTypeNullability(JavaPsiFacade.getElementFactory(element.getProject()) - .createType(typeParameters[i])) == Nullability.NOT_NULL - && DfaPsiUtil.getTypeNullability(typeArguments[i].getType()) != Nullability.NOT_NULL) { + + final PsiClass containingClass = field.getContainingClass(); + final PsiMethod setter = PropertyUtilBase.findPropertySetter(containingClass, propName, isStatic, false); + if (setter != null && setter.isPhysical() && PropertyUtil.getFieldOfSetter(setter) == field) { + final PsiParameter[] parameters = setter.getParameterList().getParameters(); + assert parameters.length == 1 : setter.getText(); + final PsiParameter parameter = parameters[0]; + LOG.assertTrue(parameter != null, setter.getText()); + AddAnnotationPsiFix addAnnoFix = createAddAnnotationFix(anno, annoToRemove, parameter); + if (state.REPORT_NOT_ANNOTATED_GETTER && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull( + parameter.getType())) { + final PsiIdentifier parameterName = parameter.getNameIdentifier(); + assertValidElement(setter, parameter, parameterName); holder.registerProblem( - typeArguments[i], - "Non-null type argument is expected", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING + parameterName, + InspectionLocalize.inspectionNullableProblemsAnnotatedFieldSetterParameterNotAnnotated(getPresentableAnnoName(field)) + .get(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + addAnnoFix ); - } } - } - } - } - - private PsiTypeElement[] getReferenceTypeArguments(PsiJavaCodeReferenceElement reference) { - PsiReferenceParameterList typeArgList = reference.getParameterList(); - return typeArgList == null ? PsiTypeElement.EMPTY_ARRAY : typeArgList.getTypeParameterElements(); - } - - @Override - public void visitAssignmentExpression(@Nonnull PsiAssignmentExpression expression) { - checkCollectionNullityOnAssignment( - expression.getOperationSign(), - expression.getLExpression().getType(), - expression.getRExpression() - ); - } - - @Override - public void visitLocalVariable(@Nonnull PsiLocalVariable variable) { - PsiIdentifier identifier = variable.getNameIdentifier(); - if (identifier != null) { - checkCollectionNullityOnAssignment(identifier, variable.getType(), variable.getInitializer()); + if (PropertyUtil.isSimpleSetter(setter)) { + if (annotated.isDeclaredNotNull && isNullableNotInferred(parameter, false)) { + final PsiIdentifier parameterName = parameter.getNameIdentifier(); + assertValidElement(setter, parameter, parameterName); + holder.registerProblem( + parameterName, + InspectionLocalize.inspectionNullableProblemsAnnotatedFieldSetterParameterConflict( + getPresentableAnnoName(field), + getPresentableAnnoName(parameter) + ).get(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + addAnnoFix + ); + } + } } - } + } - @Override - public void visitReturnStatement(@Nonnull PsiReturnStatement statement) { - PsiExpression returnValue = statement.getReturnValue(); - if (returnValue == null) { - return; - } + @Nonnull + private static AddAnnotationPsiFix createAddAnnotationFix(String anno, List annoToRemove, PsiParameter parameter) { + return new AddAnnotationPsiFix(anno, parameter, PsiNameValuePair.EMPTY_ARRAY, ArrayUtil.toStringArray(annoToRemove)); + } - checkCollectionNullityOnAssignment(statement.getReturnValue(), PsiTypesUtil.getMethodReturnType(statement), returnValue); - } + @Contract("_,_,null -> fail") + @RequiredReadAction + private static void assertValidElement(PsiMethod setter, PsiParameter parameter, PsiIdentifier nameIdentifier1) { + LOG.assertTrue(nameIdentifier1 != null && nameIdentifier1.isPhysical(), setter.getText()); + LOG.assertTrue(parameter.isPhysical(), setter.getText()); + } - @Override - public void visitLambdaExpression(@Nonnull PsiLambdaExpression lambda) { - super.visitLambdaExpression(lambda); - PsiElement body = lambda.getBody(); - if (body instanceof PsiExpression) { - checkCollectionNullityOnAssignment(body, LambdaUtil.getFunctionalInterfaceReturnType(lambda), (PsiExpression)body); - } - } - - @Override - public void visitCallExpression(@Nonnull PsiCallExpression callExpression) { - PsiExpressionList argList = callExpression.getArgumentList(); - JavaResolveResult result = callExpression.resolveMethodGenerics(); - PsiMethod method = (PsiMethod)result.getElement(); - if (method == null || argList == null) { - return; + @RequiredReadAction + private void checkConstructorParameters( + PsiField field, + Annotated annotated, + NullableNotNullManager manager, + String anno, + List annoToRemove, + @Nonnull ProblemsHolder holder, + NullableStuffInspectionState state + ) { + List initializers = DfaPsiUtil.findAllConstructorInitializers(field); + if (initializers.isEmpty()) { + return; } - PsiSubstitutor substitutor = result.getSubstitutor(); - PsiParameter[] parameters = method.getParameterList().getParameters(); - PsiExpression[] arguments = argList.getExpressions(); - for (int i = 0; i < arguments.length; i++) { - PsiExpression argument = arguments[i]; - if (i < parameters.length && - (i < parameters.length - 1 || !MethodCallInstruction.isVarArgCall(method, substitutor, arguments, parameters))) { - checkCollectionNullityOnAssignment(argument, substitutor.substitute(parameters[i].getType()), argument); - } - } - } - - private void checkCollectionNullityOnAssignment( - @Nonnull PsiElement errorElement, - @Nullable PsiType expectedType, - @Nullable PsiExpression assignedExpression - ) { - if (assignedExpression == null) { - return; + List notNullParams = new ArrayList<>(); + + boolean isFinal = field.hasModifierProperty(PsiModifier.FINAL); + + for (PsiExpression rhs : initializers) { + if (rhs instanceof PsiReferenceExpression referenceExpression) { + PsiElement target = referenceExpression.resolve(); + if (isConstructorParameter(target) && target.isPhysical()) { + PsiParameter parameter = (PsiParameter)target; + if (state.REPORT_NOT_ANNOTATED_GETTER + && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) { + final PsiIdentifier nameIdentifier = parameter.getNameIdentifier(); + if (nameIdentifier != null && nameIdentifier.isPhysical()) { + holder.registerProblem( + nameIdentifier, + InspectionLocalize.inspectionNullableProblemsAnnotatedFieldConstructorParameterNotAnnotated( + getPresentableAnnoName(field) + ).get(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, createAddAnnotationFix(anno, annoToRemove, parameter) + ); + continue; + } + } + + if (isFinal && annotated.isDeclaredNullable && isNotNullNotInferred(parameter, false, false)) { + notNullParams.add(parameter); + } + } + } } - checkCollectionNullityOnAssignment(errorElement, expectedType, assignedExpression.getType()); - } - - private void checkCollectionNullityOnAssignment( - @Nonnull PsiElement errorElement, - @Nullable PsiType expectedType, - @Nullable PsiType assignedType - ) { - if (isNullableNotNullCollectionConflict(expectedType, assignedType, new HashSet<>())) { - holder.registerProblem( - errorElement, - "Assigning a collection of nullable elements into a collection of non-null elements", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING - ); + if (notNullParams.size() != initializers.size()) { + // it's not the case that the field is final and @Nullable and always initialized via @NotNull parameters + // so there might be other initializers that could justify it being nullable + // so don't highlight field and constructor parameter annotation inconsistency + return; } - } - private boolean isNullableNotNullCollectionConflict(@Nullable PsiType expectedType, - @Nullable PsiType assignedType, - @Nonnull Set> visited) { - if (!visited.add(Couple.of(expectedType, assignedType))) { - return false; + PsiIdentifier nameIdentifier = field.getNameIdentifier(); + if (nameIdentifier.isPhysical()) { + holder.registerProblem( + nameIdentifier, + "@" + getPresentableAnnoName(field) + " field is always initialized not-null", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + AddAnnotationPsiFix.createAddNotNullFix(field) + ); } + } - GlobalSearchScope scope = holder.getFile().getResolveScope(); - if (isNullityConflict(JavaGenericsUtil.getCollectionItemType(expectedType, scope), - JavaGenericsUtil.getCollectionItemType(assignedType, scope))) { - return true; + private static boolean isConstructorParameter(@Nullable PsiElement parameter) { + return parameter instanceof PsiParameter && + psiElement(PsiParameterList.class).withParent(psiMethod().constructor(true)).accepts(parameter.getParent()); + } + + @Nonnull + private static String getPresentableAnnoName(@Nonnull PsiModifierListOwner owner) { + NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); + String name = info == null ? null : info.getAnnotation().getQualifiedName(); + if (name == null) { + return "???"; } + return StringUtil.getShortName(name); + } - for (int i = 0; i <= 1; i++) { - PsiType expectedArg = PsiUtil.substituteTypeParameter(expectedType, CommonClassNames.JAVA_UTIL_MAP, i, false); - PsiType assignedArg = PsiUtil.substituteTypeParameter(assignedType, CommonClassNames.JAVA_UTIL_MAP, i, false); - if (isNullityConflict(expectedArg, assignedArg) || - expectedArg != null && assignedArg != null && isNullableNotNullCollectionConflict(expectedArg, assignedArg, visited)) { - return true; - } + public static String getPresentableAnnoName(@Nonnull PsiAnnotation annotation) { + return StringUtil.getShortName(StringUtil.notNullize(annotation.getQualifiedName(), "???")); + } + + private static class Annotated { + private final boolean isDeclaredNotNull; + private final boolean isDeclaredNullable; + @Nullable + private final PsiAnnotation notNull; + @Nullable + private final PsiAnnotation nullable; + + private Annotated(@Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable) { + this.isDeclaredNotNull = notNull != null; + this.isDeclaredNullable = nullable != null; + this.notNull = notNull; + this.nullable = nullable; } - return false; - } - - private boolean isNullityConflict(PsiType expected, PsiType assigned) { - return DfaPsiUtil.getTypeNullability(expected) == Nullability.NOT_NULL - && DfaPsiUtil.getTypeNullability(assigned) == Nullability.NULLABLE; - } - }; - } - - @Nullable - @RequiredReadAction - private String checkIndirectInheritance(PsiElement psiClass, PsiClass intf, NullableStuffInspectionState state) { - for (PsiMethod intfMethod : intf.getAllMethods()) { - PsiClass intfMethodClass = intfMethod.getContainingClass(); - PsiMethod overridingMethod = intfMethodClass == null ? null : - JavaOverridingMethodsSearcher.findOverridingMethod((PsiClass)psiClass, intfMethod, intfMethodClass); - PsiClass overridingMethodClass = overridingMethod == null ? null : overridingMethod.getContainingClass(); - if (overridingMethodClass != null && overridingMethodClass != psiClass) { - String error = checkIndirectInheritance(intfMethod, intfMethodClass, overridingMethod, overridingMethodClass, state); - if (error != null) { - return error; + @Nonnull + static Annotated from(@Nonnull PsiModifierListOwner owner) { + NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); + return new Annotated( + manager.findExplicitNullabilityAnnotation(owner, Collections.singleton(Nullability.NOT_NULL)), + manager.findExplicitNullabilityAnnotation(owner, Collections.singleton(Nullability.NULLABLE)) + ); } - } - } - return null; - } - - @Nullable - @RequiredReadAction - private String checkIndirectInheritance( - PsiMethod intfMethod, - PsiClass intfMethodClass, - PsiMethod overridingMethod, - PsiClass overridingMethodClass, - NullableStuffInspectionState state - ) { - if (isNullableOverridingNotNull(Annotated.from(overridingMethod), intfMethod, state)) { - return "Nullable method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' implements non-null method from '" + intfMethodClass.getName() + "'"; } - if (isNonAnnotatedOverridingNotNull(overridingMethod, intfMethod, state)) { - return "Non-annotated method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' implements non-null method from '" + intfMethodClass.getName() + "'"; + + private static Annotated check(final PsiModifierListOwner owner, final ProblemsHolder holder, PsiType type) { + Annotated annotated = Annotated.from(owner); + checkType(owner, holder, type, annotated.notNull, annotated.nullable); + return annotated; } - PsiParameter[] overridingParameters = overridingMethod.getParameterList().getParameters(); - PsiParameter[] superParameters = intfMethod.getParameterList().getParameters(); - if (overridingParameters.length == superParameters.length) { - NullableNotNullManager manager = getNullityManager(intfMethod); - for (int i = 0; i < overridingParameters.length; i++) { - PsiParameter parameter = overridingParameters[i]; - List supers = Collections.singletonList(superParameters[i]); - if (findNullableSuperForNotNullParameter(parameter, supers, state) != null) { - return "Non-null parameter '" + parameter.getName() + - "' in method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' should not override nullable parameter from '" + intfMethodClass.getName() + "'"; + private static void checkType( + @Nullable PsiModifierListOwner listOwner, + ProblemsHolder holder, + PsiType type, + @Nullable PsiAnnotation notNull, + @Nullable PsiAnnotation nullable + ) { + if (nullable != null && notNull != null) { + reportNullableNotNullConflict(holder, listOwner, nullable, notNull); } - if (findNotNullSuperForNonAnnotatedParameter(manager, parameter, supers, state) != null) { - return "Non-annotated parameter '" + parameter.getName() + - "' in method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' should not override non-null parameter from '" + intfMethodClass.getName() + "'"; + if ((notNull != null || nullable != null) && type != null && TypeConversionUtil.isPrimitive(type.getCanonicalText())) { + PsiAnnotation annotation = notNull == null ? nullable : notNull; + reportPrimitiveType(holder, annotation, listOwner); } - if (isNotNullParameterOverridingNonAnnotated(manager, parameter, supers, state)) { - return "Non-null parameter '" + parameter.getName() + - "' in method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' should not override non-annotated parameter from '" + intfMethodClass.getName() + "'"; + if (listOwner instanceof PsiParameter psiParameter) { + checkLoopParameterNullability( + holder, + notNull, + nullable, + DfaPsiUtil.inferParameterNullability((PsiParameter)listOwner), + psiParameter + ); } - } - } - - return null; - } - - @RequiredReadAction - private void checkMethodReference( - PsiMethodReferenceExpression expression, - @Nonnull ProblemsHolder holder, - NullableStuffInspectionState state - ) { - PsiMethod superMethod = LambdaUtil.getFunctionalInterfaceMethod(expression); - PsiMethod targetMethod = ObjectUtil.tryCast(expression.resolve(), PsiMethod.class); - if (superMethod == null || targetMethod == null) { - return; } - PsiElement refName = expression.getReferenceNameElement(); - assert refName != null; - if (isNullableOverridingNotNull(check(targetMethod, holder, expression.getType()), superMethod, state)) { - holder.registerProblem(refName, - InspectionsBundle.message("inspection.nullable.problems.Nullable.method.overrides.NotNull", - getPresentableAnnoName(targetMethod), getPresentableAnnoName(superMethod)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } - else if (isNonAnnotatedOverridingNotNull(targetMethod, superMethod, state)) { - holder.registerProblem( - refName, - "Not annotated method is used as an override for a method annotated with " + getPresentableAnnoName(superMethod), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - createFixForNonAnnotatedOverridesNotNull(targetMethod, superMethod) - ); - } - } - - protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) { - return null; - } - - private static boolean nullabilityAnnotationsNotAvailable(final PsiFile file) { - final Project project = file.getProject(); - final GlobalSearchScope scope = GlobalSearchScope.allScope(project); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - return ContainerUtil.find(NullableNotNullManager.getInstance(project).getNullables(), s -> facade.findClass(s, scope) != null) == null; - } - - private static boolean checkNonStandardAnnotations( - PsiField field, - Annotated annotated, - NullableNotNullManager manager, - String anno, - @Nonnull ProblemsHolder holder - ) { - if (!AnnotationUtil.isAnnotatingApplicable(field, anno)) { - PsiAnnotation annotation = Objects.requireNonNull(annotated.isDeclaredNullable ? annotated.nullable : annotated.notNull); - String message = "Not \'" + annotation.getQualifiedName() + "\' but \'" + anno + "\' would be used for code generation."; - final PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement(); - holder.registerProblem( - annotationNameReferenceElement != null && annotationNameReferenceElement.isPhysical() - ? annotationNameReferenceElement : field.getNameIdentifier(), - message, - ProblemHighlightType.WEAK_WARNING, - new ChangeNullableDefaultsFix(annotated.notNull, annotated.nullable, manager) - ); - return false; - } - return true; - } - - @RequiredReadAction - private void checkAccessors( - PsiField field, - Annotated annotated, - Project project, - NullableNotNullManager manager, - final String anno, - final List annoToRemove, - @Nonnull ProblemsHolder holder, - NullableStuffInspectionState state - ) { - String propName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD); - final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); - final PsiMethod getter = PropertyUtilBase.findPropertyGetter(field.getContainingClass(), propName, isStatic, false); - final PsiIdentifier nameIdentifier = getter == null ? null : getter.getNameIdentifier(); - if (nameIdentifier != null && nameIdentifier.isPhysical()) { - if (PropertyUtil.getFieldOfGetter(getter) == field) { - AnnotateMethodFix getterAnnoFix = new AnnotateMethodFix(anno, ArrayUtil.toStringArray(annoToRemove)); - if (state.REPORT_NOT_ANNOTATED_GETTER) { - if (!manager.hasNullability(getter) && !TypeConversionUtil.isPrimitiveAndNotNull(getter.getReturnType())) { + private static void checkLoopParameterNullability( + ProblemsHolder holder, + @Nullable PsiAnnotation notNull, + @Nullable PsiAnnotation nullable, + Nullability expectedNullability, + PsiParameter owner + ) { + if (notNull != null && expectedNullability == Nullability.NULLABLE) { holder.registerProblem( - nameIdentifier, - InspectionLocalize.inspectionNullableProblemsAnnotatedFieldGetterNotAnnotated(getPresentableAnnoName(field)).get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - getterAnnoFix + notNull, + "Parameter can be null", + new RemoveAnnotationQuickFix(notNull, null) ); - } } - if (annotated.isDeclaredNotNull && isNullableNotInferred(getter, false) || - annotated.isDeclaredNullable && isNotNullNotInferred(getter, false, false)) { - holder.registerProblem( - nameIdentifier, - InspectionLocalize.inspectionNullableProblemsAnnotatedFieldGetterConflict( - getPresentableAnnoName(field), - getPresentableAnnoName(getter) - ).get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - getterAnnoFix - ); + else if (nullable != null && expectedNullability == Nullability.NOT_NULL) { + if (nullable.getContainingFile() != owner.getContainingFile()) { + return; + } + + holder.registerProblem( + nullable, + "Parameter is always not-null", + new RemoveAnnotationQuickFix(nullable, null) + ); } - } } - final PsiClass containingClass = field.getContainingClass(); - final PsiMethod setter = PropertyUtilBase.findPropertySetter(containingClass, propName, isStatic, false); - if (setter != null && setter.isPhysical() && PropertyUtil.getFieldOfSetter(setter) == field) { - final PsiParameter[] parameters = setter.getParameterList().getParameters(); - assert parameters.length == 1 : setter.getText(); - final PsiParameter parameter = parameters[0]; - LOG.assertTrue(parameter != null, setter.getText()); - AddAnnotationPsiFix addAnnoFix = createAddAnnotationFix(anno, annoToRemove, parameter); - if (state.REPORT_NOT_ANNOTATED_GETTER && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) { - final PsiIdentifier parameterName = parameter.getNameIdentifier(); - assertValidElement(setter, parameter, parameterName); + private static void reportPrimitiveType( + ProblemsHolder holder, + PsiAnnotation annotation, + @Nullable PsiModifierListOwner listOwner + ) { holder.registerProblem( - parameterName, - InspectionLocalize.inspectionNullableProblemsAnnotatedFieldSetterParameterNotAnnotated(getPresentableAnnoName(field)).get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - addAnnoFix - ); - } - if (PropertyUtil.isSimpleSetter(setter)) { - if (annotated.isDeclaredNotNull && isNullableNotInferred(parameter, false)) { - final PsiIdentifier parameterName = parameter.getNameIdentifier(); - assertValidElement(setter, parameter, parameterName); - holder.registerProblem( - parameterName, - InspectionLocalize.inspectionNullableProblemsAnnotatedFieldSetterParameterConflict( - getPresentableAnnoName(field), - getPresentableAnnoName(parameter) - ).get(), + !annotation.isPhysical() && listOwner != null ? listOwner.getNavigationElement() : annotation, + InspectionLocalize.inspectionNullableProblemsPrimitiveTypeAnnotation().get(), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - addAnnoFix - ); - } - } + new RemoveAnnotationQuickFix(annotation, listOwner) + ); } - } - - @Nonnull - private static AddAnnotationPsiFix createAddAnnotationFix(String anno, List annoToRemove, PsiParameter parameter) { - return new AddAnnotationPsiFix(anno, parameter, PsiNameValuePair.EMPTY_ARRAY, ArrayUtil.toStringArray(annoToRemove)); - } - - @Contract("_,_,null -> fail") - @RequiredReadAction - private static void assertValidElement(PsiMethod setter, PsiParameter parameter, PsiIdentifier nameIdentifier1) { - LOG.assertTrue(nameIdentifier1 != null && nameIdentifier1.isPhysical(), setter.getText()); - LOG.assertTrue(parameter.isPhysical(), setter.getText()); - } - - @RequiredReadAction - private void checkConstructorParameters( - PsiField field, - Annotated annotated, - NullableNotNullManager manager, - String anno, - List annoToRemove, - @Nonnull ProblemsHolder holder, - NullableStuffInspectionState state - ) { - List initializers = DfaPsiUtil.findAllConstructorInitializers(field); - if (initializers.isEmpty()) { - return; + + @Override + @Nonnull + public String getDisplayName() { + return InspectionLocalize.inspectionNullableProblemsDisplayName().get(); } - List notNullParams = new ArrayList<>(); + @Override + @Nonnull + public String getGroupDisplayName() { + return InspectionLocalize.groupNamesProbableBugs().get(); + } - boolean isFinal = field.hasModifierProperty(PsiModifier.FINAL); + @Override + @Nonnull + public String getShortName() { + return "NullableProblems"; + } - for (PsiExpression rhs : initializers) { - if (rhs instanceof PsiReferenceExpression referenceExpression) { - PsiElement target = referenceExpression.resolve(); - if (isConstructorParameter(target) && target.isPhysical()) { - PsiParameter parameter = (PsiParameter)target; - if (state.REPORT_NOT_ANNOTATED_GETTER - && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) { - final PsiIdentifier nameIdentifier = parameter.getNameIdentifier(); - if (nameIdentifier != null && nameIdentifier.isPhysical()) { - holder.registerProblem( - nameIdentifier, - InspectionLocalize.inspectionNullableProblemsAnnotatedFieldConstructorParameterNotAnnotated(getPresentableAnnoName(field)).get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, createAddAnnotationFix(anno, annoToRemove, parameter) - ); - continue; - } - } + private void checkNullableStuffForMethod( + PsiMethod method, + final ProblemsHolder holder, + boolean isOnFly, + NullableStuffInspectionState state + ) { + Annotated annotated = check(method, holder, method.getReturnType()); + + List superMethods = ContainerUtil.map( + method.findSuperMethodSignaturesIncludingStatic(true), + MethodSignatureBackedByPsiMethod::getMethod + ); - if (isFinal && annotated.isDeclaredNullable && isNotNullNotInferred(parameter, false, false)) { - notNullParams.add(parameter); - } - } - } - } + final NullableNotNullManager nullableManager = NullableNotNullManager.getInstance(holder.getProject()); - if (notNullParams.size() != initializers.size()) { - // it's not the case that the field is final and @Nullable and always initialized via @NotNull parameters - // so there might be other initializers that could justify it being nullable - // so don't highlight field and constructor parameter annotation inconsistency - return; + checkSupers(method, holder, annotated, superMethods, state); + checkParameters(method, holder, superMethods, nullableManager, isOnFly, state); + checkOverriders(method, holder, annotated, nullableManager, state); } - PsiIdentifier nameIdentifier = field.getNameIdentifier(); - if (nameIdentifier.isPhysical()) { - holder.registerProblem(nameIdentifier, "@" + getPresentableAnnoName(field) + " field is always initialized not-null", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, AddAnnotationPsiFix.createAddNotNullFix(field)); - } - } - - private static boolean isConstructorParameter(@Nullable PsiElement parameter) { - return parameter instanceof PsiParameter && - psiElement(PsiParameterList.class).withParent(psiMethod().constructor(true)).accepts(parameter.getParent()); - } - - @Nonnull - private static String getPresentableAnnoName(@Nonnull PsiModifierListOwner owner) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); - NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); - String name = info == null ? null : info.getAnnotation().getQualifiedName(); - if (name == null) { - return "???"; + private void checkSupers( + PsiMethod method, + ProblemsHolder holder, + Annotated annotated, + List superMethods, + NullableStuffInspectionState state + ) { + for (PsiMethod superMethod : superMethods) { + if (isNullableOverridingNotNull(annotated, superMethod, state)) { + PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, getNullityManager(method).getNullables(), true); + holder.registerProblem( + annotation != null ? annotation : method.getNameIdentifier(), + InspectionsBundle.message( + "inspection.nullable.problems.Nullable.method.overrides.NotNull", + getPresentableAnnoName(method), + getPresentableAnnoName(superMethod) + ), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ); + break; + } + + if (isNonAnnotatedOverridingNotNull(method, superMethod, state)) { + holder.registerProblem( + method.getNameIdentifier(), + InspectionsBundle.message( + "inspection.nullable.problems.method.overrides.NotNull", + getPresentableAnnoName(superMethod) + ), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + createFixForNonAnnotatedOverridesNotNull(method, superMethod) + ); + break; + } + } } - return StringUtil.getShortName(name); - } - public static String getPresentableAnnoName(@Nonnull PsiAnnotation annotation) { - return StringUtil.getShortName(StringUtil.notNullize(annotation.getQualifiedName(), "???")); - } + private static NullableNotNullManager getNullityManager(PsiMethod method) { + return NullableNotNullManager.getInstance(method.getProject()); + } - private static class Annotated { - private final boolean isDeclaredNotNull; - private final boolean isDeclaredNullable; - @Nullable - private final PsiAnnotation notNull; @Nullable - private final PsiAnnotation nullable; - - private Annotated(@Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable) { - this.isDeclaredNotNull = notNull != null; - this.isDeclaredNullable = nullable != null; - this.notNull = notNull; - this.nullable = nullable; + private static LocalQuickFix createFixForNonAnnotatedOverridesNotNull( + PsiMethod method, + PsiMethod superMethod + ) { + NullableNotNullManager nullableManager = getNullityManager(method); + return AnnotationUtil.isAnnotatingApplicable(method, nullableManager.getDefaultNotNull()) + ? AddAnnotationPsiFix.createAddNotNullFix(method) + : createChangeDefaultNotNullFix(nullableManager, superMethod); } - @Nonnull - static Annotated from(@Nonnull PsiModifierListOwner owner) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); - return new Annotated( - manager.findExplicitNullabilityAnnotation(owner, Collections.singleton(Nullability.NOT_NULL)), - manager.findExplicitNullabilityAnnotation(owner, Collections.singleton(Nullability.NULLABLE)) - ); + private boolean isNullableOverridingNotNull(Annotated methodInfo, PsiMethod superMethod, NullableStuffInspectionState state) { + return state.REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE && methodInfo.isDeclaredNullable + && isNotNullNotInferred(superMethod, true, false); } - } - - private static Annotated check(final PsiModifierListOwner owner, final ProblemsHolder holder, PsiType type) { - Annotated annotated = Annotated.from(owner); - checkType(owner, holder, type, annotated.notNull, annotated.nullable); - return annotated; - } - - private static void checkType( - @Nullable PsiModifierListOwner listOwner, - ProblemsHolder holder, - PsiType type, - @Nullable PsiAnnotation notNull, - @Nullable PsiAnnotation nullable - ) { - if (nullable != null && notNull != null) { - reportNullableNotNullConflict(holder, listOwner, nullable, notNull); - } - if ((notNull != null || nullable != null) && type != null && TypeConversionUtil.isPrimitive(type.getCanonicalText())) { - PsiAnnotation annotation = notNull == null ? nullable : notNull; - reportPrimitiveType(holder, annotation, listOwner); + + private boolean isNonAnnotatedOverridingNotNull(PsiMethod method, PsiMethod superMethod, NullableStuffInspectionState state) { + return state.REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL && + !(method.getReturnType() instanceof PsiPrimitiveType) && + !method.isConstructor() && + !getNullityManager(method).hasNullability(method) && + isNotNullNotInferred(superMethod, true, state.IGNORE_EXTERNAL_SUPER_NOTNULL) && + !hasInheritableNotNull(superMethod); } - if (listOwner instanceof PsiParameter psiParameter) { - checkLoopParameterNullability(holder, notNull, nullable, DfaPsiUtil.inferParameterNullability((PsiParameter)listOwner), psiParameter); + + private static boolean hasInheritableNotNull(PsiModifierListOwner owner) { + return AnnotationUtil.isAnnotated(owner, "javax.annotation.constraints.NotNull", CHECK_HIERARCHY | CHECK_TYPE); } - } - - private static void checkLoopParameterNullability( - ProblemsHolder holder, - @Nullable PsiAnnotation notNull, - @Nullable PsiAnnotation nullable, - Nullability expectedNullability, - PsiParameter owner - ) { - if (notNull != null && expectedNullability == Nullability.NULLABLE) { - holder.registerProblem( - notNull, - "Parameter can be null", - new RemoveAnnotationQuickFix(notNull, null) - ); + + private void checkParameters( + PsiMethod method, + ProblemsHolder holder, + List superMethods, + NullableNotNullManager nullableManager, + boolean isOnFly, + NullableStuffInspectionState state + ) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + if (parameter.getType() instanceof PsiPrimitiveType) { + continue; + } + + List superParameters = new ArrayList<>(); + for (PsiMethod superMethod : superMethods) { + PsiParameter[] _superParameters = superMethod.getParameterList().getParameters(); + if (_superParameters.length == parameters.length) { + superParameters.add(_superParameters[i]); + } + } + + PsiParameter nullableSuper = findNullableSuperForNotNullParameter(parameter, superParameters, state); + if (nullableSuper != null) { + PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameter, nullableManager.getNotNulls(), true); + holder.registerProblem( + annotation != null ? annotation : parameter.getNameIdentifier(), + InspectionsBundle.message( + "inspection.nullable.problems.NotNull.parameter.overrides.Nullable", + getPresentableAnnoName(parameter), + getPresentableAnnoName(nullableSuper) + ), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ); + } + PsiParameter notNullSuper = findNotNullSuperForNonAnnotatedParameter(nullableManager, parameter, superParameters, state); + if (notNullSuper != null) { + LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(parameter, nullableManager.getDefaultNotNull()) + ? AddAnnotationPsiFix.createAddNotNullFix(parameter) + : createChangeDefaultNotNullFix(nullableManager, notNullSuper); + holder.registerProblem( + parameter.getNameIdentifier(), + InspectionsBundle.message( + "inspection.nullable.problems.parameter.overrides.NotNull", + getPresentableAnnoName(notNullSuper) + ), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + fix + ); + } + if (isNotNullParameterOverridingNonAnnotated(nullableManager, parameter, superParameters, state)) { + NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); + assert info != null; + PsiAnnotation notNullAnnotation = info.getAnnotation(); + boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true); + final LocalQuickFix fix = physical ? new RemoveAnnotationQuickFix(notNullAnnotation, parameter) : null; + holder.registerProblem( + physical ? notNullAnnotation : parameter.getNameIdentifier(), + JavaInspectionsLocalize.inspectionNullableProblemsNotnullParameterOverridesNotAnnotated( + getPresentableAnnoName(parameter) + ).get(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + fix + ); + } + + checkNullLiteralArgumentOfNotNullParameterUsages(method, holder, nullableManager, isOnFly, i, parameter, state); + } } - else if (nullable != null && expectedNullability == Nullability.NOT_NULL) { - if (nullable.getContainingFile() != owner.getContainingFile()) { - return; - } - - holder.registerProblem( - nullable, - "Parameter is always not-null", - new RemoveAnnotationQuickFix(nullable, null) - ); + + @Nullable + private PsiParameter findNotNullSuperForNonAnnotatedParameter( + NullableNotNullManager nullableManager, + PsiParameter parameter, + List superParameters, + NullableStuffInspectionState state + ) { + return state.REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL && !nullableManager.hasNullability(parameter) + ? ContainerUtil.find( + superParameters, + sp -> isNotNullNotInferred(sp, false, state.IGNORE_EXTERNAL_SUPER_NOTNULL) && !hasInheritableNotNull(sp) + ) + : null; } - } - - private static void reportPrimitiveType( - ProblemsHolder holder, - PsiAnnotation annotation, - @Nullable PsiModifierListOwner listOwner - ) { - holder.registerProblem( - !annotation.isPhysical() && listOwner != null ? listOwner.getNavigationElement() : annotation, - InspectionLocalize.inspectionNullableProblemsPrimitiveTypeAnnotation().get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - new RemoveAnnotationQuickFix(annotation, listOwner) - ); - } - - @Override - @Nonnull - public String getDisplayName() { - return InspectionLocalize.inspectionNullableProblemsDisplayName().get(); - } - - @Override - @Nonnull - public String getGroupDisplayName() { - return InspectionLocalize.groupNamesProbableBugs().get(); - } - - @Override - @Nonnull - public String getShortName() { - return "NullableProblems"; - } - - private void checkNullableStuffForMethod( - PsiMethod method, - final ProblemsHolder holder, - boolean isOnFly, - NullableStuffInspectionState state - ) { - Annotated annotated = check(method, holder, method.getReturnType()); - - List superMethods = ContainerUtil.map( - method.findSuperMethodSignaturesIncludingStatic(true), - MethodSignatureBackedByPsiMethod::getMethod - ); - - final NullableNotNullManager nullableManager = NullableNotNullManager.getInstance(holder.getProject()); - - checkSupers(method, holder, annotated, superMethods, state); - checkParameters(method, holder, superMethods, nullableManager, isOnFly, state); - checkOverriders(method, holder, annotated, nullableManager, state); - } - - private void checkSupers( - PsiMethod method, - ProblemsHolder holder, - Annotated annotated, - List superMethods, - NullableStuffInspectionState state - ) { - for (PsiMethod superMethod : superMethods) { - if (isNullableOverridingNotNull(annotated, superMethod, state)) { - PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, getNullityManager(method).getNullables(), true); - holder.registerProblem(annotation != null ? annotation : method.getNameIdentifier(), - InspectionsBundle.message("inspection.nullable.problems.Nullable.method.overrides.NotNull", - getPresentableAnnoName(method), getPresentableAnnoName(superMethod)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - break; - } - - if (isNonAnnotatedOverridingNotNull(method, superMethod, state)) { - holder.registerProblem(method.getNameIdentifier(), - InspectionsBundle - .message("inspection.nullable.problems.method.overrides.NotNull", getPresentableAnnoName(superMethod)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - createFixForNonAnnotatedOverridesNotNull(method, superMethod)); - break; - } + + @Nullable + private PsiParameter findNullableSuperForNotNullParameter( + PsiParameter parameter, + List superParameters, + NullableStuffInspectionState state + ) { + return state.REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE && isNotNullNotInferred(parameter, false, false) + ? ContainerUtil.find(superParameters, sp -> isNullableNotInferred(sp, false)) + : null; } - } - - private static NullableNotNullManager getNullityManager(PsiMethod method) { - return NullableNotNullManager.getInstance(method.getProject()); - } - - @Nullable - private static LocalQuickFix createFixForNonAnnotatedOverridesNotNull(PsiMethod method, - PsiMethod superMethod) { - NullableNotNullManager nullableManager = getNullityManager(method); - return AnnotationUtil.isAnnotatingApplicable(method, nullableManager.getDefaultNotNull()) - ? AddAnnotationPsiFix.createAddNotNullFix(method) - : createChangeDefaultNotNullFix(nullableManager, superMethod); - } - - private boolean isNullableOverridingNotNull(Annotated methodInfo, PsiMethod superMethod, NullableStuffInspectionState state) { - return state.REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE && methodInfo.isDeclaredNullable - && isNotNullNotInferred(superMethod, true, false); - } - - private boolean isNonAnnotatedOverridingNotNull(PsiMethod method, PsiMethod superMethod, NullableStuffInspectionState state) { - return state.REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL && - !(method.getReturnType() instanceof PsiPrimitiveType) && - !method.isConstructor() && - !getNullityManager(method).hasNullability(method) && - isNotNullNotInferred(superMethod, true, state.IGNORE_EXTERNAL_SUPER_NOTNULL) && - !hasInheritableNotNull(superMethod); - } - - private static boolean hasInheritableNotNull(PsiModifierListOwner owner) { - return AnnotationUtil.isAnnotated(owner, "javax.annotation.constraints.NotNull", CHECK_HIERARCHY | CHECK_TYPE); - } - - private void checkParameters( - PsiMethod method, - ProblemsHolder holder, - List superMethods, - NullableNotNullManager nullableManager, - boolean isOnFly, - NullableStuffInspectionState state - ) { - PsiParameter[] parameters = method.getParameterList().getParameters(); - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - if (parameter.getType() instanceof PsiPrimitiveType) { - continue; - } - - List superParameters = new ArrayList<>(); - for (PsiMethod superMethod : superMethods) { - PsiParameter[] _superParameters = superMethod.getParameterList().getParameters(); - if (_superParameters.length == parameters.length) { - superParameters.add(_superParameters[i]); + + private boolean isNotNullParameterOverridingNonAnnotated( + NullableNotNullManager nullableManager, + PsiParameter parameter, + List superParameters, + NullableStuffInspectionState state + ) { + if (!state.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) { + return false; } - } - - PsiParameter nullableSuper = findNullableSuperForNotNullParameter(parameter, superParameters, state); - if (nullableSuper != null) { - PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameter, nullableManager.getNotNulls(), true); - holder.registerProblem(annotation != null ? annotation : parameter.getNameIdentifier(), - InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.overrides.Nullable", - getPresentableAnnoName(parameter), - getPresentableAnnoName(nullableSuper)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } - PsiParameter notNullSuper = findNotNullSuperForNonAnnotatedParameter(nullableManager, parameter, superParameters, state); - if (notNullSuper != null) { - LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(parameter, nullableManager.getDefaultNotNull()) - ? AddAnnotationPsiFix.createAddNotNullFix(parameter) - : createChangeDefaultNotNullFix(nullableManager, notNullSuper); - holder.registerProblem(parameter.getNameIdentifier(), - InspectionsBundle.message("inspection.nullable.problems.parameter.overrides.NotNull", - getPresentableAnnoName(notNullSuper)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix); - } - if (isNotNullParameterOverridingNonAnnotated(nullableManager, parameter, superParameters, state)) { NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); - assert info != null; - PsiAnnotation notNullAnnotation = info.getAnnotation(); - boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true); - final LocalQuickFix fix = physical ? new RemoveAnnotationQuickFix(notNullAnnotation, parameter) : null; + return info != null && info.getNullability() == Nullability.NOT_NULL && !info.isInferred() && + ContainerUtil.exists(superParameters, sp -> !nullableManager.hasNullability(sp)); + } + + private void checkNullLiteralArgumentOfNotNullParameterUsages( + PsiMethod method, + ProblemsHolder holder, + NullableNotNullManager nullableManager, + boolean isOnFly, + int parameterIdx, + PsiParameter parameter, + NullableStuffInspectionState state + ) { + if (!state.REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER || !isOnFly) { + return; + } + + PsiElement elementToHighlight; + if (DfaPsiUtil.getTypeNullability(getMemberType(parameter)) == Nullability.NOT_NULL) { + elementToHighlight = parameter.getNameIdentifier(); + } + else { + NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); + if (info == null || info.getNullability() != Nullability.NOT_NULL || info.isInferred()) { + return; + } + PsiAnnotation notNullAnnotation = info.getAnnotation(); + boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true); + elementToHighlight = physical ? notNullAnnotation : parameter.getNameIdentifier(); + } + if (elementToHighlight == null || !JavaNullMethodArgumentUtil.hasNullArgument(method, parameterIdx)) { + return; + } + holder.registerProblem( - physical ? notNullAnnotation : parameter.getNameIdentifier(), - JavaInspectionsLocalize.inspectionNullableProblemsNotnullParameterOverridesNotAnnotated(getPresentableAnnoName(parameter)).get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix + elementToHighlight, + InspectionsBundle.message( + "inspection.nullable.problems.NotNull.parameter.receives.null.literal", + getPresentableAnnoName(parameter) + ), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + createNavigateToNullParameterUsagesFix(parameter) ); - } - - checkNullLiteralArgumentOfNotNullParameterUsages(method, holder, nullableManager, isOnFly, i, parameter, state); } - } - - @Nullable - private PsiParameter findNotNullSuperForNonAnnotatedParameter( - NullableNotNullManager nullableManager, - PsiParameter parameter, - List superParameters, - NullableStuffInspectionState state - ) { - return state.REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL && !nullableManager.hasNullability(parameter) - ? ContainerUtil.find( - superParameters, - sp -> isNotNullNotInferred(sp, false, state.IGNORE_EXTERNAL_SUPER_NOTNULL) && !hasInheritableNotNull(sp) - ) - : null; - } - - @Nullable - private PsiParameter findNullableSuperForNotNullParameter( - PsiParameter parameter, - List superParameters, - NullableStuffInspectionState state - ) { - return state.REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE && isNotNullNotInferred(parameter, false, false) - ? ContainerUtil.find(superParameters, sp -> isNullableNotInferred(sp, false)) - : null; - } - - private boolean isNotNullParameterOverridingNonAnnotated( - NullableNotNullManager nullableManager, - PsiParameter parameter, - List superParameters, - NullableStuffInspectionState state - ) { - if (!state.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) { - return false; + + private void checkOverriders( + @Nonnull PsiMethod method, + @Nonnull ProblemsHolder holder, + @Nonnull Annotated annotated, + @Nonnull NullableNotNullManager nullableManager, + NullableStuffInspectionState state + ) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (state.REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS) { + boolean[] checkParameter = new boolean[parameters.length]; + boolean[] parameterQuickFixSuggested = new boolean[parameters.length]; + boolean hasAnnotatedParameter = false; + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + checkParameter[i] = isNotNullNotInferred(parameter, false, false) && + !hasInheritableNotNull(parameter) && + !(parameter.getType() instanceof PsiPrimitiveType); + hasAnnotatedParameter |= checkParameter[i]; + } + boolean checkReturnType = + annotated.isDeclaredNotNull && !hasInheritableNotNull(method) && !(method.getReturnType() instanceof PsiPrimitiveType); + if (hasAnnotatedParameter || checkReturnType) { + final String defaultNotNull = nullableManager.getDefaultNotNull(); + final boolean superMethodApplicable = AnnotationUtil.isAnnotatingApplicable(method, defaultNotNull); + PsiMethod[] overridings = + OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY); + boolean methodQuickFixSuggested = false; + for (PsiMethod overriding : overridings) { + if (shouldSkipOverriderAsGenerated(overriding)) { + continue; + } + + if (!methodQuickFixSuggested + && checkReturnType + && !isNotNullNotInferred(overriding, false, false) + && (isNullableNotInferred(overriding, false) || !isNullableNotInferred(overriding, true)) + && AddAnnotationPsiFix.isAvailable(overriding, defaultNotNull)) { + PsiIdentifier identifier = method.getNameIdentifier();//load tree + PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, nullableManager.getNotNulls()); + final String[] annotationsToRemove = ArrayUtil.toStringArray(nullableManager.getNullables()); + + LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(overriding, defaultNotNull) + ? new MyAnnotateMethodFix(defaultNotNull, annotationsToRemove) + : superMethodApplicable ? null : createChangeDefaultNotNullFix(nullableManager, method); + + PsiElement psiElement = annotation; + if (annotation != null && !annotation.isPhysical()) { + psiElement = identifier; + } + if (psiElement == null) { + continue; + } + holder.registerProblem( + psiElement, + InspectionLocalize.nullableStuffProblemsOverriddenMethodsAreNotAnnotated().get(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + fix + ); + methodQuickFixSuggested = true; + } + if (hasAnnotatedParameter) { + PsiParameter[] psiParameters = overriding.getParameterList().getParameters(); + for (int i = 0; i < psiParameters.length; i++) { + if (parameterQuickFixSuggested[i]) { + continue; + } + PsiParameter parameter = psiParameters[i]; + if (checkParameter[i] && + !isNotNullNotInferred(parameter, false, false) && + !isNullableNotInferred(parameter, false) && + AddAnnotationPsiFix.isAvailable(parameter, defaultNotNull)) { + PsiIdentifier identifier = + parameters[i].getNameIdentifier(); //be sure that corresponding tree element available + PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameters[i], nullableManager.getNotNulls()); + PsiElement psiElement = annotation; + if (annotation == null || !annotation.isPhysical()) { + psiElement = identifier; + if (psiElement == null) { + continue; + } + } + LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(parameter, defaultNotNull) + ? new AnnotateOverriddenMethodParameterFix(defaultNotNull, nullableManager.getDefaultNullable()) + : createChangeDefaultNotNullFix(nullableManager, parameters[i]); + holder.registerProblem( + psiElement, + InspectionLocalize.nullableStuffProblemsOverriddenMethodParametersAreNotAnnotated().get(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + fix + ); + parameterQuickFixSuggested[i] = true; + } + } + } + } + } + } } - NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); - return info != null && info.getNullability() == Nullability.NOT_NULL && !info.isInferred() && - ContainerUtil.exists(superParameters, sp -> !nullableManager.hasNullability(sp)); - } - - private void checkNullLiteralArgumentOfNotNullParameterUsages( - PsiMethod method, - ProblemsHolder holder, - NullableNotNullManager nullableManager, - boolean isOnFly, - int parameterIdx, - PsiParameter parameter, - NullableStuffInspectionState state - ) { - if (!state.REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER || !isOnFly) { - return; + + @RequiredReadAction + public static boolean shouldSkipOverriderAsGenerated(PsiMethod overriding) { + if (Registry.is("idea.report.nullity.missing.in.generated.overriders", true)) { + return false; + } + + PsiFile file = overriding.getContainingFile(); + VirtualFile virtualFile = file != null ? file.getVirtualFile() : null; + return virtualFile != null && GeneratedSourcesFilter.isGenerated(overriding.getProject(), virtualFile); } - PsiElement elementToHighlight; - if (DfaPsiUtil.getTypeNullability(getMemberType(parameter)) == Nullability.NOT_NULL) { - elementToHighlight = parameter.getNameIdentifier(); + private static boolean isNotNullNotInferred(@Nonnull PsiModifierListOwner owner, boolean checkBases, boolean skipExternal) { + Project project = owner.getProject(); + NullableNotNullManager manager = NullableNotNullManager.getInstance(project); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); + return info != null && !info.isInferred() && info.getNullability() == Nullability.NOT_NULL + && (checkBases || info.getInheritedFrom() == null) + && (!skipExternal || !info.isExternal()); } - else { - NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); - if (info == null || info.getNullability() != Nullability.NOT_NULL || info.isInferred()) { - return; - } - PsiAnnotation notNullAnnotation = info.getAnnotation(); - boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true); - elementToHighlight = physical ? notNullAnnotation : parameter.getNameIdentifier(); + + public static boolean isNullableNotInferred(@Nonnull PsiModifierListOwner owner, boolean checkBases) { + Project project = owner.getProject(); + NullableNotNullManager manager = NullableNotNullManager.getInstance(project); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); + return info != null && !info.isInferred() && info.getNullability() == Nullability.NULLABLE + && (checkBases || info.getInheritedFrom() == null); } - if (elementToHighlight == null || !JavaNullMethodArgumentUtil.hasNullArgument(method, parameterIdx)) { - return; + + private static PsiType getMemberType(@Nonnull PsiModifierListOwner owner) { + return owner instanceof PsiMethod method ? method.getReturnType() : owner instanceof PsiVariable variable ? variable.getType() : null; } - holder.registerProblem( - elementToHighlight, - InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.receives.null.literal", getPresentableAnnoName(parameter)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - createNavigateToNullParameterUsagesFix(parameter) - ); - } - - private void checkOverriders( - @Nonnull PsiMethod method, - @Nonnull ProblemsHolder holder, - @Nonnull Annotated annotated, - @Nonnull NullableNotNullManager nullableManager, - NullableStuffInspectionState state - ) { - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (state.REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS) { - boolean[] checkParameter = new boolean[parameters.length]; - boolean[] parameterQuickFixSuggested = new boolean[parameters.length]; - boolean hasAnnotatedParameter = false; - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - checkParameter[i] = isNotNullNotInferred(parameter, false, false) && - !hasInheritableNotNull(parameter) && - !(parameter.getType() instanceof PsiPrimitiveType); - hasAnnotatedParameter |= checkParameter[i]; - } - boolean checkReturnType = - annotated.isDeclaredNotNull && !hasInheritableNotNull(method) && !(method.getReturnType() instanceof PsiPrimitiveType); - if (hasAnnotatedParameter || checkReturnType) { - final String defaultNotNull = nullableManager.getDefaultNotNull(); - final boolean superMethodApplicable = AnnotationUtil.isAnnotatingApplicable(method, defaultNotNull); - PsiMethod[] overridings = - OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY); - boolean methodQuickFixSuggested = false; - for (PsiMethod overriding : overridings) { - if (shouldSkipOverriderAsGenerated(overriding)) { - continue; - } - - if (!methodQuickFixSuggested - && checkReturnType - && !isNotNullNotInferred(overriding, false, false) - && (isNullableNotInferred(overriding, false) || !isNullableNotInferred(overriding, true)) - && AddAnnotationPsiFix.isAvailable(overriding, defaultNotNull)) { - PsiIdentifier identifier = method.getNameIdentifier();//load tree - PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, nullableManager.getNotNulls()); - final String[] annotationsToRemove = ArrayUtil.toStringArray(nullableManager.getNullables()); - - LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(overriding, defaultNotNull) - ? new MyAnnotateMethodFix(defaultNotNull, annotationsToRemove) - : superMethodApplicable ? null : createChangeDefaultNotNullFix(nullableManager, method); - - PsiElement psiElement = annotation; - if (annotation != null && !annotation.isPhysical()) { - psiElement = identifier; - } - if (psiElement == null) continue; - holder.registerProblem( - psiElement, - InspectionLocalize.nullableStuffProblemsOverriddenMethodsAreNotAnnotated().get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix - ); - methodQuickFixSuggested = true; - } - if (hasAnnotatedParameter) { - PsiParameter[] psiParameters = overriding.getParameterList().getParameters(); - for (int i = 0; i < psiParameters.length; i++) { - if (parameterQuickFixSuggested[i]) { - continue; - } - PsiParameter parameter = psiParameters[i]; - if (checkParameter[i] && - !isNotNullNotInferred(parameter, false, false) && - !isNullableNotInferred(parameter, false) && - AddAnnotationPsiFix.isAvailable(parameter, defaultNotNull)) { - PsiIdentifier identifier = parameters[i].getNameIdentifier(); //be sure that corresponding tree element available - PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameters[i], nullableManager.getNotNulls()); - PsiElement psiElement = annotation; - if (annotation == null || !annotation.isPhysical()) { - psiElement = identifier; - if (psiElement == null) { - continue; - } + private static LocalQuickFix createChangeDefaultNotNullFix( + NullableNotNullManager nullableManager, + PsiModifierListOwner modifierListOwner + ) { + final PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierListOwner, nullableManager.getNotNulls()); + if (annotation != null) { + final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement(); + if (referenceElement != null) { + JavaResolveResult resolveResult = referenceElement.advancedResolve(false); + if (resolveResult.getElement() != null && + resolveResult.isValidResult() && + !nullableManager.getDefaultNotNull().equals(annotation.getQualifiedName())) { + return new ChangeNullableDefaultsFix(annotation.getQualifiedName(), null, nullableManager); } - LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(parameter, defaultNotNull) - ? new AnnotateOverriddenMethodParameterFix(defaultNotNull, nullableManager.getDefaultNullable()) - : createChangeDefaultNotNullFix(nullableManager, parameters[i]); - holder.registerProblem( - psiElement, - InspectionLocalize.nullableStuffProblemsOverriddenMethodParametersAreNotAnnotated().get(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix - ); - parameterQuickFixSuggested[i] = true; - } } - } } - } - } - } - - @RequiredReadAction - public static boolean shouldSkipOverriderAsGenerated(PsiMethod overriding) { - if (Registry.is("idea.report.nullity.missing.in.generated.overriders", true)) { - return false; + return null; } - PsiFile file = overriding.getContainingFile(); - VirtualFile virtualFile = file != null ? file.getVirtualFile() : null; - return virtualFile != null && GeneratedSourcesFilter.isGenerated(overriding.getProject(), virtualFile); - } - - private static boolean isNotNullNotInferred(@Nonnull PsiModifierListOwner owner, boolean checkBases, boolean skipExternal) { - Project project = owner.getProject(); - NullableNotNullManager manager = NullableNotNullManager.getInstance(project); - NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); - return info != null && !info.isInferred() && info.getNullability() == Nullability.NOT_NULL - && (checkBases || info.getInheritedFrom() == null) - && (!skipExternal || !info.isExternal()); - } - - public static boolean isNullableNotInferred(@Nonnull PsiModifierListOwner owner, boolean checkBases) { - Project project = owner.getProject(); - NullableNotNullManager manager = NullableNotNullManager.getInstance(project); - NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); - return info != null && !info.isInferred() && info.getNullability() == Nullability.NULLABLE - && (checkBases || info.getInheritedFrom() == null); - } - - private static PsiType getMemberType(@Nonnull PsiModifierListOwner owner) { - return owner instanceof PsiMethod method ? method.getReturnType() : owner instanceof PsiVariable variable ? variable.getType() : null; - } - - private static LocalQuickFix createChangeDefaultNotNullFix( - NullableNotNullManager nullableManager, - PsiModifierListOwner modifierListOwner - ) { - final PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierListOwner, nullableManager.getNotNulls()); - if (annotation != null) { - final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement(); - if (referenceElement != null) { - JavaResolveResult resolveResult = referenceElement.advancedResolve(false); - if (resolveResult.getElement() != null && - resolveResult.isValidResult() && - !nullableManager.getDefaultNotNull().equals(annotation.getQualifiedName())) { - return new ChangeNullableDefaultsFix(annotation.getQualifiedName(), null, nullableManager); - } - } - } - return null; - } - - private static void reportNullableNotNullConflict( - final ProblemsHolder holder, - final PsiModifierListOwner listOwner, - final PsiAnnotation declaredNullable, - final PsiAnnotation declaredNotNull - ) { - final String bothNullableNotNullMessage = InspectionsBundle.message("inspection.nullable.problems.Nullable.NotNull.conflict", - getPresentableAnnoName(declaredNullable), - getPresentableAnnoName(declaredNotNull)); - holder.registerProblem( - declaredNotNull.isPhysical() ? declaredNotNull : listOwner.getNavigationElement(), - bothNullableNotNullMessage, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - new RemoveAnnotationQuickFix(declaredNotNull, listOwner) - ); - holder.registerProblem( - declaredNullable.isPhysical() ? declaredNullable : listOwner.getNavigationElement(), - bothNullableNotNullMessage, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - new RemoveAnnotationQuickFix(declaredNullable, listOwner) - ); - } - - @Nonnull - @Override - public NullableStuffInspectionState createStateProvider() { - return new NullableStuffInspectionState(); - } - - private static class MyAnnotateMethodFix extends AnnotateMethodFix { - MyAnnotateMethodFix(String defaultNotNull, String[] annotationsToRemove) { - super(defaultNotNull, annotationsToRemove); + private static void reportNullableNotNullConflict( + final ProblemsHolder holder, + final PsiModifierListOwner listOwner, + final PsiAnnotation declaredNullable, + final PsiAnnotation declaredNotNull + ) { + final String bothNullableNotNullMessage = InspectionsBundle.message( + "inspection.nullable.problems.Nullable.NotNull.conflict", + getPresentableAnnoName(declaredNullable), + getPresentableAnnoName(declaredNotNull) + ); + holder.registerProblem( + declaredNotNull.isPhysical() ? declaredNotNull : listOwner.getNavigationElement(), + bothNullableNotNullMessage, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + new RemoveAnnotationQuickFix(declaredNotNull, listOwner) + ); + holder.registerProblem( + declaredNullable.isPhysical() ? declaredNullable : listOwner.getNavigationElement(), + bothNullableNotNullMessage, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + new RemoveAnnotationQuickFix(declaredNullable, listOwner) + ); } @Nonnull @Override - protected String getPreposition() { - return "as"; + public NullableStuffInspectionState createStateProvider() { + return new NullableStuffInspectionState(); } - @Override - protected boolean annotateOverriddenMethods() { - return true; - } + private static class MyAnnotateMethodFix extends AnnotateMethodFix { + MyAnnotateMethodFix(String defaultNotNull, String[] annotationsToRemove) { + super(defaultNotNull, annotationsToRemove); + } - @Override - protected boolean annotateSelf() { - return false; + @Nonnull + @Override + protected String getPreposition() { + return "as"; + } + + @Override + protected boolean annotateOverriddenMethods() { + return true; + } + + @Override + protected boolean annotateSelf() { + return false; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java index 8257e4827d..82ffe5f2ef 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java @@ -31,6 +31,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import java.util.function.Function; @@ -38,752 +39,766 @@ * @author Pavel.Dolgov */ public final class JavaReflectionReferenceUtil { - // MethodHandle (Java 7) and VarHandle (Java 9) infrastructure - public static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java.lang.invoke.MethodHandles.Lookup"; - public static final String JAVA_LANG_INVOKE_METHOD_TYPE = "java.lang.invoke.MethodType"; - - public static final String METHOD_TYPE = "methodType"; - public static final String GENERIC_METHOD_TYPE = "genericMethodType"; - - public static final String FIND_VIRTUAL = "findVirtual"; - public static final String FIND_STATIC = "findStatic"; - public static final String FIND_SPECIAL = "findSpecial"; - - public static final String FIND_GETTER = "findGetter"; - public static final String FIND_SETTER = "findSetter"; - public static final String FIND_STATIC_GETTER = "findStaticGetter"; - public static final String FIND_STATIC_SETTER = "findStaticSetter"; - - public static final String FIND_VAR_HANDLE = "findVarHandle"; - public static final String FIND_STATIC_VAR_HANDLE = "findStaticVarHandle"; - - public static final String FIND_CONSTRUCTOR = "findConstructor"; - public static final String FIND_CLASS = "findClass"; - - public static final String[] HANDLE_FACTORY_METHOD_NAMES = { - FIND_VIRTUAL, - FIND_STATIC, - FIND_SPECIAL, - FIND_GETTER, - FIND_SETTER, - FIND_STATIC_GETTER, - FIND_STATIC_SETTER, - FIND_VAR_HANDLE, - FIND_STATIC_VAR_HANDLE - }; - - // Classic reflection infrastructure - public static final String GET_FIELD = "getField"; - public static final String GET_DECLARED_FIELD = "getDeclaredField"; - public static final String GET_METHOD = "getMethod"; - public static final String GET_DECLARED_METHOD = "getDeclaredMethod"; - public static final String GET_CONSTRUCTOR = "getConstructor"; - public static final String GET_DECLARED_CONSTRUCTOR = "getDeclaredConstructor"; - - public static final String JAVA_LANG_CLASS_LOADER = "java.lang.ClassLoader"; - public static final String FOR_NAME = "forName"; - public static final String LOAD_CLASS = "loadClass"; - public static final String GET_CLASS = "getClass"; - public static final String NEW_INSTANCE = "newInstance"; - public static final String TYPE = "TYPE"; - - // Atomic field updaters - public static final String NEW_UPDATER = "newUpdater"; - public static final String ATOMIC_LONG_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicLongFieldUpdater"; - public static final String ATOMIC_INTEGER_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicIntegerFieldUpdater"; - public static final String ATOMIC_REFERENCE_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicReferenceFieldUpdater"; - - private static final RecursionGuard ourGuard = RecursionManager.createGuard("JavaLangClassMemberReference"); - - @Contract("null -> null") - public static ReflectiveType getReflectiveType(@Nullable PsiExpression context) { - context = PsiUtil.skipParenthesizedExprDown(context); - if (context == null) { - return null; - } - if (context instanceof PsiClassObjectAccessExpression) { - final PsiTypeElement operand = ((PsiClassObjectAccessExpression) context).getOperand(); - return ReflectiveType.create(operand.getType(), true); - } - - if (context instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) context; - final String methodReferenceName = methodCall.getMethodExpression().getReferenceName(); - if (FOR_NAME.equals(methodReferenceName)) { - final PsiMethod method = methodCall.resolveMethod(); - if (method != null && isJavaLangClass(method.getContainingClass())) { - final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions(); - if (expressions.length == 1) { - final PsiExpression argument = findDefinition(PsiUtil.skipParenthesizedExprDown(expressions[0])); - final String className = computeConstantExpression(argument, String.class); - if (className != null) { - return ReflectiveType.create(findClass(className, context), true); + // MethodHandle (Java 7) and VarHandle (Java 9) infrastructure + public static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java.lang.invoke.MethodHandles.Lookup"; + public static final String JAVA_LANG_INVOKE_METHOD_TYPE = "java.lang.invoke.MethodType"; + + public static final String METHOD_TYPE = "methodType"; + public static final String GENERIC_METHOD_TYPE = "genericMethodType"; + + public static final String FIND_VIRTUAL = "findVirtual"; + public static final String FIND_STATIC = "findStatic"; + public static final String FIND_SPECIAL = "findSpecial"; + + public static final String FIND_GETTER = "findGetter"; + public static final String FIND_SETTER = "findSetter"; + public static final String FIND_STATIC_GETTER = "findStaticGetter"; + public static final String FIND_STATIC_SETTER = "findStaticSetter"; + + public static final String FIND_VAR_HANDLE = "findVarHandle"; + public static final String FIND_STATIC_VAR_HANDLE = "findStaticVarHandle"; + + public static final String FIND_CONSTRUCTOR = "findConstructor"; + public static final String FIND_CLASS = "findClass"; + + public static final String[] HANDLE_FACTORY_METHOD_NAMES = { + FIND_VIRTUAL, + FIND_STATIC, + FIND_SPECIAL, + FIND_GETTER, + FIND_SETTER, + FIND_STATIC_GETTER, + FIND_STATIC_SETTER, + FIND_VAR_HANDLE, + FIND_STATIC_VAR_HANDLE + }; + + // Classic reflection infrastructure + public static final String GET_FIELD = "getField"; + public static final String GET_DECLARED_FIELD = "getDeclaredField"; + public static final String GET_METHOD = "getMethod"; + public static final String GET_DECLARED_METHOD = "getDeclaredMethod"; + public static final String GET_CONSTRUCTOR = "getConstructor"; + public static final String GET_DECLARED_CONSTRUCTOR = "getDeclaredConstructor"; + + public static final String JAVA_LANG_CLASS_LOADER = "java.lang.ClassLoader"; + public static final String FOR_NAME = "forName"; + public static final String LOAD_CLASS = "loadClass"; + public static final String GET_CLASS = "getClass"; + public static final String NEW_INSTANCE = "newInstance"; + public static final String TYPE = "TYPE"; + + // Atomic field updaters + public static final String NEW_UPDATER = "newUpdater"; + public static final String ATOMIC_LONG_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicLongFieldUpdater"; + public static final String ATOMIC_INTEGER_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicIntegerFieldUpdater"; + public static final String ATOMIC_REFERENCE_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicReferenceFieldUpdater"; + + private static final RecursionGuard ourGuard = RecursionManager.createGuard("JavaLangClassMemberReference"); + + @Contract("null -> null") + public static ReflectiveType getReflectiveType(@Nullable PsiExpression context) { + context = PsiUtil.skipParenthesizedExprDown(context); + if (context == null) { + return null; + } + if (context instanceof PsiClassObjectAccessExpression) { + final PsiTypeElement operand = ((PsiClassObjectAccessExpression)context).getOperand(); + return ReflectiveType.create(operand.getType(), true); + } + + if (context instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)context; + final String methodReferenceName = methodCall.getMethodExpression().getReferenceName(); + if (FOR_NAME.equals(methodReferenceName)) { + final PsiMethod method = methodCall.resolveMethod(); + if (method != null && isJavaLangClass(method.getContainingClass())) { + final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions(); + if (expressions.length == 1) { + final PsiExpression argument = findDefinition(PsiUtil.skipParenthesizedExprDown(expressions[0])); + final String className = computeConstantExpression(argument, String.class); + if (className != null) { + return ReflectiveType.create(findClass(className, context), true); + } + } + } } - } - } - } else if (GET_CLASS.equals(methodReferenceName) && methodCall.getArgumentList().isEmpty()) { - final PsiMethod method = methodCall.resolveMethod(); - if (method != null && isJavaLangObject(method.getContainingClass())) { - final PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(methodCall.getMethodExpression().getQualifierExpression()); - if (qualifier instanceof PsiReferenceExpression) { - final PsiExpression definition = findVariableDefinition((PsiReferenceExpression) qualifier); - if (definition != null) { - return getClassInstanceType(definition); + else if (GET_CLASS.equals(methodReferenceName) && methodCall.getArgumentList().isEmpty()) { + final PsiMethod method = methodCall.resolveMethod(); + if (method != null && isJavaLangObject(method.getContainingClass())) { + final PsiExpression qualifier = + PsiUtil.skipParenthesizedExprDown(methodCall.getMethodExpression().getQualifierExpression()); + if (qualifier instanceof PsiReferenceExpression) { + final PsiExpression definition = findVariableDefinition((PsiReferenceExpression)qualifier); + if (definition != null) { + return getClassInstanceType(definition); + } + } + //TODO type of the qualifier may be a supertype of the actual value - need to compute the type of the actual value + // otherwise getDeclaredField and getDeclaredMethod may work not reliably + if (qualifier != null) { + return getClassInstanceType(qualifier); + } + } } - } - //TODO type of the qualifier may be a supertype of the actual value - need to compute the type of the actual value - // otherwise getDeclaredField and getDeclaredMethod may work not reliably - if (qualifier != null) { - return getClassInstanceType(qualifier); - } - } - } - } - - if (context instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) context; - final PsiElement resolved = reference.resolve(); - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - if (isJavaLangClass(PsiTypesUtil.getPsiClass(variable.getType()))) { - final PsiExpression definition = findVariableDefinition(reference, variable); - if (definition != null) { - ReflectiveType result = ourGuard.doPreventingRecursion(variable, false, () -> getReflectiveType(definition)); - if (result != null) { - return result; + } + + if (context instanceof PsiReferenceExpression) { + PsiReferenceExpression reference = (PsiReferenceExpression)context; + final PsiElement resolved = reference.resolve(); + if (resolved instanceof PsiVariable) { + PsiVariable variable = (PsiVariable)resolved; + if (isJavaLangClass(PsiTypesUtil.getPsiClass(variable.getType()))) { + final PsiExpression definition = findVariableDefinition(reference, variable); + if (definition != null) { + ReflectiveType result = ourGuard.doPreventingRecursion(variable, false, () -> getReflectiveType(definition)); + if (result != null) { + return result; + } + } + } } - } } - } - } - final PsiType type = context.getType(); - if (type instanceof PsiClassType) { - final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType) type).resolveGenerics(); - final PsiClass resolvedElement = resolveResult.getElement(); - if (!isJavaLangClass(resolvedElement)) { - return null; - } - - if (context instanceof PsiReferenceExpression && TYPE.equals(((PsiReferenceExpression) context).getReferenceName())) { - final PsiElement resolved = ((PsiReferenceExpression) context).resolve(); - if (resolved instanceof PsiField) { - final PsiField field = (PsiField) resolved; - if (field.hasModifierProperty(PsiModifier.FINAL) && field.hasModifierProperty(PsiModifier.STATIC)) { - final PsiType[] classTypeArguments = ((PsiClassType) type).getParameters(); - final PsiPrimitiveType unboxedType = classTypeArguments.length == 1 - ? PsiPrimitiveType.getUnboxedType(classTypeArguments[0]) : null; - if (unboxedType != null && field.getContainingClass() == PsiUtil.resolveClassInClassTypeOnly(classTypeArguments[0])) { - return ReflectiveType.create(unboxedType, true); + final PsiType type = context.getType(); + if (type instanceof PsiClassType) { + final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)type).resolveGenerics(); + final PsiClass resolvedElement = resolveResult.getElement(); + if (!isJavaLangClass(resolvedElement)) { + return null; } - } - } - } - final PsiTypeParameter[] parameters = resolvedElement.getTypeParameters(); - if (parameters.length == 1) { - final PsiType typeArgument = resolveResult.getSubstitutor().substitute(parameters[0]); - final PsiType erasure = TypeConversionUtil.erasure(typeArgument); - final PsiClass argumentClass = PsiTypesUtil.getPsiClass(erasure); - if (argumentClass != null && !isJavaLangObject(argumentClass)) { - return ReflectiveType.create(argumentClass, false); - } - } - } - return null; - } - - @Nullable - private static ReflectiveType getClassInstanceType(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression == null) { - return null; - } - if (expression instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) expression; - final String methodReferenceName = methodCall.getMethodExpression().getReferenceName(); - - if (NEW_INSTANCE.equals(methodReferenceName)) { - final PsiMethod method = methodCall.resolveMethod(); - if (method != null) { - final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); - if (arguments.length == 0 && isClassWithName(method.getContainingClass(), CommonClassNames.JAVA_LANG_CLASS)) { - final PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); - if (qualifier != null) { - return ourGuard.doPreventingRecursion(qualifier, false, () -> getReflectiveType(qualifier)); + + if (context instanceof PsiReferenceExpression && TYPE.equals(((PsiReferenceExpression)context).getReferenceName())) { + final PsiElement resolved = ((PsiReferenceExpression)context).resolve(); + if (resolved instanceof PsiField) { + final PsiField field = (PsiField)resolved; + if (field.hasModifierProperty(PsiModifier.FINAL) && field.hasModifierProperty(PsiModifier.STATIC)) { + final PsiType[] classTypeArguments = ((PsiClassType)type).getParameters(); + final PsiPrimitiveType unboxedType = classTypeArguments.length == 1 + ? PsiPrimitiveType.getUnboxedType(classTypeArguments[0]) : null; + if (unboxedType != null && field.getContainingClass() == PsiUtil.resolveClassInClassTypeOnly(classTypeArguments[0])) { + return ReflectiveType.create(unboxedType, true); + } + } + } } - } else if (arguments.length > 1 && isClassWithName(method.getContainingClass(), CommonClassNames.JAVA_LANG_REFLECT_ARRAY)) { - final PsiExpression typeExpression = arguments[0]; - if (typeExpression != null) { - final ReflectiveType itemType = - ourGuard.doPreventingRecursion(typeExpression, false, () -> getReflectiveType(typeExpression)); - return ReflectiveType.arrayOf(itemType); + final PsiTypeParameter[] parameters = resolvedElement.getTypeParameters(); + if (parameters.length == 1) { + final PsiType typeArgument = resolveResult.getSubstitutor().substitute(parameters[0]); + final PsiType erasure = TypeConversionUtil.erasure(typeArgument); + final PsiClass argumentClass = PsiTypesUtil.getPsiClass(erasure); + if (argumentClass != null && !isJavaLangObject(argumentClass)) { + return ReflectiveType.create(argumentClass, false); + } } - } - } - } - } - return ReflectiveType.create(expression.getType(), false); - } - - @Contract("null,_->null") - @Nullable - public static T computeConstantExpression(@Nullable PsiExpression expression, @Nonnull Class expectedType) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - final Object computed = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false); - return ObjectUtil.tryCast(computed, expectedType); - } - - @Nullable - public static ReflectiveClass getReflectiveClass(PsiExpression context) { - final ReflectiveType reflectiveType = getReflectiveType(context); - return reflectiveType != null ? reflectiveType.getReflectiveClass() : null; - } - - @Nullable - public static PsiExpression findDefinition(@Nullable PsiExpression expression) { - int preventEndlessLoop = 5; - while (expression instanceof PsiReferenceExpression) { - if (--preventEndlessLoop == 0) { - return null; - } - expression = findVariableDefinition((PsiReferenceExpression) expression); - } - return expression; - } - - @Nullable - private static PsiExpression findVariableDefinition(@Nonnull PsiReferenceExpression referenceExpression) { - final PsiElement resolved = referenceExpression.resolve(); - return resolved instanceof PsiVariable ? findVariableDefinition(referenceExpression, (PsiVariable) resolved) : null; - } - - @Nullable - private static PsiExpression findVariableDefinition(@Nonnull PsiReferenceExpression referenceExpression, @Nonnull PsiVariable variable) { - if (variable.hasModifierProperty(PsiModifier.FINAL)) { - final PsiExpression initializer = variable.getInitializer(); - if (initializer != null) { - return initializer; - } - if (variable instanceof PsiField) { - return findFinalFieldDefinition(referenceExpression, (PsiField) variable); - } - } - return DeclarationSearchUtils.findDefinition(referenceExpression, variable); - } - - @Nullable - private static PsiExpression findFinalFieldDefinition(@Nonnull PsiReferenceExpression referenceExpression, @Nonnull PsiField field) { - if (!field.hasModifierProperty(PsiModifier.FINAL)) { - return null; - } - final PsiClass psiClass = ObjectUtil.tryCast(field.getParent(), PsiClass.class); - if (psiClass != null) { - final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); - final List initializers = - ContainerUtil.filter(psiClass.getInitializers(), initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isStatic); - for (PsiClassInitializer initializer : initializers) { - final PsiExpression assignedExpression = getAssignedExpression(initializer, field); - if (assignedExpression != null) { - return assignedExpression; - } - } - if (!isStatic) { - final PsiMethod[] constructors = psiClass.getConstructors(); - if (constructors.length == 1) { - return getAssignedExpression(constructors[0], field); - } - for (PsiMethod constructor : constructors) { - if (PsiTreeUtil.isAncestor(constructor, referenceExpression, true)) { - return getAssignedExpression(constructor, field); - } - } - } - } - return null; - } - - @Nullable - private static PsiExpression getAssignedExpression(@Nonnull PsiMember maybeContainsAssignment, @Nonnull PsiField field) { - final PsiAssignmentExpression assignment = SyntaxTraverser.psiTraverser(maybeContainsAssignment) - .filter(PsiAssignmentExpression.class) - .find(expression -> ExpressionUtils.isReferenceTo(expression.getLExpression(), field)); - return assignment != null ? assignment.getRExpression() : null; - } - - private static PsiClass findClass(@Nonnull String qualifiedName, @Nonnull PsiElement context) { - final Project project = context.getProject(); - return JavaPsiFacade.getInstance(project).findClass(qualifiedName, GlobalSearchScope.allScope(project)); - } - - @Contract("null -> false") - public static boolean isJavaLangClass(@Nullable PsiClass aClass) { - return isClassWithName(aClass, CommonClassNames.JAVA_LANG_CLASS); - } - - @Contract("null -> false") - public static boolean isJavaLangObject(@Nullable PsiClass aClass) { - return isClassWithName(aClass, CommonClassNames.JAVA_LANG_OBJECT); - } - - @Contract("null, _ -> false") - public static boolean isClassWithName(@Nullable PsiClass aClass, @Nonnull String name) { - return aClass != null && name.equals(aClass.getQualifiedName()); - } - - @Contract("null -> false") - public static boolean isRegularMethod(@Nullable PsiMethod method) { - return method != null && !method.isConstructor(); - } - - public static boolean isPublic(@Nonnull PsiMember member) { - return member.hasModifierProperty(PsiModifier.PUBLIC); - } - - public static boolean isAtomicallyUpdateable(@Nonnull PsiField field) { - if (field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.VOLATILE)) { - return false; - } - final PsiType type = field.getType(); - return !(type instanceof PsiPrimitiveType) || PsiType.INT.equals(type) || PsiType.LONG.equals(type); - } - - @Nullable - public static String getParameterTypesText(@Nonnull PsiMethod method) { - final StringJoiner joiner = new StringJoiner(", "); - for (PsiParameter parameter : method.getParameterList().getParameters()) { - final String typeText = getTypeText(parameter.getType()); - joiner.add(typeText + ".class"); - } - return joiner.toString(); - } - - public static void shortenArgumentsClassReferences(@Nonnull InsertionContext context) { - final PsiElement parameter = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); - final PsiExpressionList parameterList = PsiTreeUtil.getParentOfType(parameter, PsiExpressionList.class); - if (parameterList != null && parameterList.getParent() instanceof PsiMethodCallExpression) { - JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(parameterList); - } - } - - @Nonnull - public static LookupElement withPriority(@Nonnull LookupElement lookupElement, boolean hasPriority) { - return hasPriority ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, -1); - } - - @Nullable - public static LookupElement withPriority(@Nullable LookupElement lookupElement, int priority) { - return priority == 0 || lookupElement == null ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, priority); - } - - public static int getMethodSortOrder(@Nonnull PsiMethod method) { - return isJavaLangObject(method.getContainingClass()) ? 1 : isPublic(method) ? -1 : 0; - } - - @Nullable - public static String getMemberType(@Nullable PsiElement element) { - final PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class); - return methodCall != null ? methodCall.getMethodExpression().getReferenceName() : null; - } - - @Nullable - public static LookupElement lookupMethod(@Nonnull PsiMethod method, @Nullable InsertHandler insertHandler) { - final ReflectiveSignature signature = getMethodSignature(method); - return signature != null - ? LookupElementBuilder.create(signature, method.getName()) - .withIcon(signature.getIcon()) - .withTailText(signature.getShortArgumentTypes()) - .withInsertHandler(insertHandler) - : null; - } - - public static void replaceText(@Nonnull InsertionContext context, @Nonnull String text) { - final PsiElement newElement = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); - final PsiElement params = newElement.getParent().getParent(); - final int end = params.getTextRange().getEndOffset() - 1; - final int start = Math.min(newElement.getTextRange().getEndOffset(), end); - - context.getDocument().replaceString(start, end, text); - context.commitDocument(); - shortenArgumentsClassReferences(context); - } - - @Nonnull - public static String getTypeText(@Nonnull PsiType type) { - final ReflectiveType reflectiveType = ReflectiveType.create(type, false); - return reflectiveType.getQualifiedName(); - } - - @Nullable - public static String getTypeText(@Nullable PsiExpression argument) { - final ReflectiveType reflectiveType = getReflectiveType(argument); - return reflectiveType != null ? reflectiveType.getQualifiedName() : null; - } - - @Contract("null -> null") - @Nullable - public static ReflectiveSignature getMethodSignature(@Nullable PsiMethod method) { - if (method != null) { - final List types = new ArrayList<>(); - final PsiType returnType = method.getReturnType(); - types.add(getTypeText(returnType != null ? returnType : PsiType.VOID)); // null return type means it's a constructor - - for (PsiParameter parameter : method.getParameterList().getParameters()) { - types.add(getTypeText(parameter.getType())); - } - final Image icon = IconDescriptorUpdaters.getIcon(method, Iconable.ICON_FLAG_VISIBILITY); - return ReflectiveSignature.create(icon, types); - } - return null; - } - - @Nonnull - public static String getMethodTypeExpressionText(@Nonnull ReflectiveSignature signature) { - final String types = signature.getText(true, type -> type + ".class"); - return JAVA_LANG_INVOKE_METHOD_TYPE + "." + METHOD_TYPE + types; - } - - public static boolean isCallToMethod(@Nonnull PsiMethodCallExpression methodCall, @Nonnull String className, @Nonnull String methodName) { - return MethodCallUtils.isCallToMethod(methodCall, className, null, methodName, (PsiType[]) null); - } - - /** - * Tries to unwrap array and find its components - * - * @param maybeArray an array to unwrap - * @return list of unwrapped array components, some or all of them could be null if unknown (but the length is known); - * returns null if nothing is known. - */ - @Nullable - public static List getVarargs(@Nullable PsiExpression maybeArray) { - if (ExpressionUtils.isNullLiteral(maybeArray)) { - return Collections.emptyList(); - } - if (isVarargAsArray(maybeArray)) { - final PsiExpression argumentsDefinition = findDefinition(maybeArray); - if (argumentsDefinition instanceof PsiArrayInitializerExpression) { - return Arrays.asList(((PsiArrayInitializerExpression) argumentsDefinition).getInitializers()); - } - if (argumentsDefinition instanceof PsiNewExpression) { - final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression) argumentsDefinition).getArrayInitializer(); - if (arrayInitializer != null) { - return Arrays.asList(arrayInitializer.getInitializers()); - } - final PsiExpression[] dimensions = ((PsiNewExpression) argumentsDefinition).getArrayDimensions(); - if (dimensions.length == 1) { // new Object[length] or new Class[length] - final Integer itemCount = computeConstantExpression(findDefinition(dimensions[0]), Integer.class); - if (itemCount != null && itemCount >= 0 && itemCount < 256) { - return Collections.nCopies(itemCount, null); - } - } - } - } - return null; - } - - @Contract("null -> false") - public static boolean isVarargAsArray(@Nullable PsiExpression maybeArray) { - final PsiType type = maybeArray != null ? maybeArray.getType() : null; - return type instanceof PsiArrayType && - type.getArrayDimensions() == 1 && - type.getDeepComponentType() instanceof PsiClassType; - } - - /** - * Take method's return type and parameter types - * from arguments of MethodType.methodType(Class...) and MethodType.genericMethodType(int, boolean?) - */ - @Nullable - public static ReflectiveSignature composeMethodSignature(@Nullable PsiExpression methodTypeExpression) { - final PsiExpression typeDefinition = findDefinition(methodTypeExpression); - if (typeDefinition instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) typeDefinition; - final String referenceName = methodCallExpression.getMethodExpression().getReferenceName(); - - Function composer = null; - if (METHOD_TYPE.equals(referenceName)) { - composer = JavaReflectionReferenceUtil::composeMethodSignatureFromTypes; - } else if (GENERIC_METHOD_TYPE.equals(referenceName)) { - composer = JavaReflectionReferenceUtil::composeGenericMethodSignature; - } - - if (composer != null) { - final PsiMethod method = methodCallExpression.resolveMethod(); - if (method != null) { - final PsiClass psiClass = method.getContainingClass(); - if (psiClass != null && JAVA_LANG_INVOKE_METHOD_TYPE.equals(psiClass.getQualifiedName())) { - final PsiExpression[] arguments = methodCallExpression.getArgumentList().getExpressions(); - return composer.apply(arguments); - } } - } + return null; } - return null; - } - - @Nullable - private static ReflectiveSignature composeMethodSignatureFromTypes(@Nonnull PsiExpression[] returnAndParameterTypes) { - final List typeTexts = ContainerUtil.map(returnAndParameterTypes, JavaReflectionReferenceUtil::getTypeText); - return ReflectiveSignature.create(typeTexts); - } - @Nullable - public static Pair.NonNull getGenericSignature(@Nonnull PsiExpression[] genericSignatureShape) { - if (genericSignatureShape.length == 0 || genericSignatureShape.length > 2) { - return null; + @Nullable + private static ReflectiveType getClassInstanceType(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression == null) { + return null; + } + if (expression instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; + final String methodReferenceName = methodCall.getMethodExpression().getReferenceName(); + + if (NEW_INSTANCE.equals(methodReferenceName)) { + final PsiMethod method = methodCall.resolveMethod(); + if (method != null) { + final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + if (arguments.length == 0 && isClassWithName(method.getContainingClass(), CommonClassNames.JAVA_LANG_CLASS)) { + final PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); + if (qualifier != null) { + return ourGuard.doPreventingRecursion(qualifier, false, () -> getReflectiveType(qualifier)); + } + } + else if (arguments.length > 1 + && isClassWithName(method.getContainingClass(), CommonClassNames.JAVA_LANG_REFLECT_ARRAY)) { + final PsiExpression typeExpression = arguments[0]; + if (typeExpression != null) { + final ReflectiveType itemType = + ourGuard.doPreventingRecursion(typeExpression, false, () -> getReflectiveType(typeExpression)); + return ReflectiveType.arrayOf(itemType); + } + } + } + } + } + return ReflectiveType.create(expression.getType(), false); } - final Integer objectArgCount = computeConstantExpression(genericSignatureShape[0], Integer.class); - final Boolean finalArray = // there's an additional parameter which is an ellipsis or an array - genericSignatureShape.length > 1 ? computeConstantExpression(genericSignatureShape[1], Boolean.class) : false; - - if (objectArgCount == null || objectArgCount < 0 || objectArgCount > 255) { - return null; + @Contract("null,_->null") + @Nullable + public static T computeConstantExpression(@Nullable PsiExpression expression, @Nonnull Class expectedType) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + final Object computed = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false); + return ObjectUtil.tryCast(computed, expectedType); } - if (finalArray == null || finalArray && objectArgCount > 254) { - return null; + + @Nullable + public static ReflectiveClass getReflectiveClass(PsiExpression context) { + final ReflectiveType reflectiveType = getReflectiveType(context); + return reflectiveType != null ? reflectiveType.getReflectiveClass() : null; } - return Pair.createNonNull(objectArgCount, finalArray); - } - /** - * All the types in the method signature are either unbounded type parameters or java.lang.Object (with possible vararg) - */ - @Nullable - private static ReflectiveSignature composeGenericMethodSignature(@Nonnull PsiExpression[] genericSignatureShape) { - final Pair.NonNull signature = getGenericSignature(genericSignatureShape); - if (signature == null) { - return null; + @Nullable + public static PsiExpression findDefinition(@Nullable PsiExpression expression) { + int preventEndlessLoop = 5; + while (expression instanceof PsiReferenceExpression) { + if (--preventEndlessLoop == 0) { + return null; + } + expression = findVariableDefinition((PsiReferenceExpression)expression); + } + return expression; } - final int objectArgCount = signature.getFirst(); - final boolean finalArray = signature.getSecond(); - final List typeNames = new ArrayList<>(); - typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); // return type + @Nullable + private static PsiExpression findVariableDefinition(@Nonnull PsiReferenceExpression referenceExpression) { + final PsiElement resolved = referenceExpression.resolve(); + return resolved instanceof PsiVariable ? findVariableDefinition(referenceExpression, (PsiVariable)resolved) : null; + } - for (int i = 0; i < objectArgCount; i++) { - typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); + @Nullable + private static PsiExpression findVariableDefinition( + @Nonnull PsiReferenceExpression referenceExpression, + @Nonnull PsiVariable variable + ) { + if (variable.hasModifierProperty(PsiModifier.FINAL)) { + final PsiExpression initializer = variable.getInitializer(); + if (initializer != null) { + return initializer; + } + if (variable instanceof PsiField) { + return findFinalFieldDefinition(referenceExpression, (PsiField)variable); + } + } + return DeclarationSearchUtils.findDefinition(referenceExpression, variable); } - if (finalArray) { - typeNames.add(CommonClassNames.JAVA_LANG_OBJECT + "[]"); + + @Nullable + private static PsiExpression findFinalFieldDefinition(@Nonnull PsiReferenceExpression referenceExpression, @Nonnull PsiField field) { + if (!field.hasModifierProperty(PsiModifier.FINAL)) { + return null; + } + final PsiClass psiClass = ObjectUtil.tryCast(field.getParent(), PsiClass.class); + if (psiClass != null) { + final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); + final List initializers = ContainerUtil.filter( + psiClass.getInitializers(), + initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isStatic + ); + for (PsiClassInitializer initializer : initializers) { + final PsiExpression assignedExpression = getAssignedExpression(initializer, field); + if (assignedExpression != null) { + return assignedExpression; + } + } + if (!isStatic) { + final PsiMethod[] constructors = psiClass.getConstructors(); + if (constructors.length == 1) { + return getAssignedExpression(constructors[0], field); + } + for (PsiMethod constructor : constructors) { + if (PsiTreeUtil.isAncestor(constructor, referenceExpression, true)) { + return getAssignedExpression(constructor, field); + } + } + } + } + return null; } - return ReflectiveSignature.create(typeNames); - } + @Nullable + private static PsiExpression getAssignedExpression(@Nonnull PsiMember maybeContainsAssignment, @Nonnull PsiField field) { + final PsiAssignmentExpression assignment = SyntaxTraverser.psiTraverser(maybeContainsAssignment) + .filter(PsiAssignmentExpression.class) + .find(expression -> ExpressionUtils.isReferenceTo(expression.getLExpression(), field)); + return assignment != null ? assignment.getRExpression() : null; + } - public static final class ReflectiveType { - final PsiType myType; - final boolean myIsExact; + private static PsiClass findClass(@Nonnull String qualifiedName, @Nonnull PsiElement context) { + final Project project = context.getProject(); + return JavaPsiFacade.getInstance(project).findClass(qualifiedName, GlobalSearchScope.allScope(project)); + } - private ReflectiveType(@Nonnull PsiType erasedType, boolean isExact) { - myType = erasedType; - myIsExact = isExact; + @Contract("null -> false") + public static boolean isJavaLangClass(@Nullable PsiClass aClass) { + return isClassWithName(aClass, CommonClassNames.JAVA_LANG_CLASS); } - @Nonnull - public String getQualifiedName() { - return myType.getCanonicalText(); + @Contract("null -> false") + public static boolean isJavaLangObject(@Nullable PsiClass aClass) { + return isClassWithName(aClass, CommonClassNames.JAVA_LANG_OBJECT); } - @Override - public String toString() { - return myType.getCanonicalText(); + @Contract("null, _ -> false") + public static boolean isClassWithName(@Nullable PsiClass aClass, @Nonnull String name) { + return aClass != null && name.equals(aClass.getQualifiedName()); } - public boolean isEqualTo(@Nullable PsiType otherType) { - return otherType != null && myType.equals(erasure(otherType)); + @Contract("null -> false") + public static boolean isRegularMethod(@Nullable PsiMethod method) { + return method != null && !method.isConstructor(); } - public boolean isAssignableFrom(@Nonnull PsiType type) { - return myType.isAssignableFrom(type); + public static boolean isPublic(@Nonnull PsiMember member) { + return member.hasModifierProperty(PsiModifier.PUBLIC); } - public boolean isPrimitive() { - return myType instanceof PsiPrimitiveType; + public static boolean isAtomicallyUpdateable(@Nonnull PsiField field) { + if (field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.VOLATILE)) { + return false; + } + final PsiType type = field.getType(); + return !(type instanceof PsiPrimitiveType) || PsiType.INT.equals(type) || PsiType.LONG.equals(type); } - @Nonnull - public PsiType getType() { - return myType; + @Nullable + public static String getParameterTypesText(@Nonnull PsiMethod method) { + final StringJoiner joiner = new StringJoiner(", "); + for (PsiParameter parameter : method.getParameterList().getParameters()) { + final String typeText = getTypeText(parameter.getType()); + joiner.add(typeText + ".class"); + } + return joiner.toString(); } - public boolean isExact() { - return myIsExact; + public static void shortenArgumentsClassReferences(@Nonnull InsertionContext context) { + final PsiElement parameter = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); + final PsiExpressionList parameterList = PsiTreeUtil.getParentOfType(parameter, PsiExpressionList.class); + if (parameterList != null && parameterList.getParent() instanceof PsiMethodCallExpression) { + JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(parameterList); + } } - @Nullable - public ReflectiveClass getReflectiveClass() { - PsiClass psiClass = getPsiClass(); - if (psiClass != null) { - return new ReflectiveClass(psiClass, myIsExact); - } - return null; + @Nonnull + public static LookupElement withPriority(@Nonnull LookupElement lookupElement, boolean hasPriority) { + return hasPriority ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, -1); } @Nullable - public ReflectiveType getArrayComponentType() { - if (myType instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType) myType).getComponentType(); - return new ReflectiveType(componentType, myIsExact); - } - return null; + public static LookupElement withPriority(@Nullable LookupElement lookupElement, int priority) { + return priority == 0 || lookupElement == null ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, priority); } - @Nullable - public PsiClass getPsiClass() { - return PsiTypesUtil.getPsiClass(myType); + public static int getMethodSortOrder(@Nonnull PsiMethod method) { + return isJavaLangObject(method.getContainingClass()) ? 1 : isPublic(method) ? -1 : 0; } - @Contract("!null,_ -> !null; null,_ -> null") @Nullable - public static ReflectiveType create(@Nullable PsiType originalType, boolean isExact) { - if (originalType != null) { - return new ReflectiveType(erasure(originalType), isExact); - } - return null; + public static String getMemberType(@Nullable PsiElement element) { + final PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class); + return methodCall != null ? methodCall.getMethodExpression().getReferenceName() : null; } - @Contract("!null,_ -> !null; null,_ -> null") @Nullable - public static ReflectiveType create(@Nullable PsiClass psiClass, boolean isExact) { - if (psiClass != null) { - final PsiElementFactory factory = JavaPsiFacade.getElementFactory(psiClass.getProject()); - return new ReflectiveType(factory.createType(psiClass), isExact); - } - return null; + public static LookupElement lookupMethod(@Nonnull PsiMethod method, @Nullable InsertHandler insertHandler) { + final ReflectiveSignature signature = getMethodSignature(method); + return signature != null + ? LookupElementBuilder.create(signature, method.getName()) + .withIcon(signature.getIcon()) + .withTailText(signature.getShortArgumentTypes()) + .withInsertHandler(insertHandler) + : null; } - @Contract("!null -> !null; null -> null") - @Nullable - public static ReflectiveType arrayOf(@Nullable ReflectiveType itemType) { - if (itemType != null) { - return new ReflectiveType(itemType.myType.createArrayType(), itemType.myIsExact); - } - return null; + public static void replaceText(@Nonnull InsertionContext context, @Nonnull String text) { + final PsiElement newElement = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); + final PsiElement params = newElement.getParent().getParent(); + final int end = params.getTextRange().getEndOffset() - 1; + final int start = Math.min(newElement.getTextRange().getEndOffset(), end); + + context.getDocument().replaceString(start, end, text); + context.commitDocument(); + shortenArgumentsClassReferences(context); } @Nonnull - private static PsiType erasure(@Nonnull PsiType type) { - final PsiType erasure = TypeConversionUtil.erasure(type); - if (erasure instanceof PsiEllipsisType) { - return ((PsiEllipsisType) erasure).toArrayType(); - } - return erasure; + public static String getTypeText(@Nonnull PsiType type) { + final ReflectiveType reflectiveType = ReflectiveType.create(type, false); + return reflectiveType.getQualifiedName(); } - } - public static class ReflectiveClass { - final PsiClass myPsiClass; - final boolean myIsExact; + @Nullable + public static String getTypeText(@Nullable PsiExpression argument) { + final ReflectiveType reflectiveType = getReflectiveType(argument); + return reflectiveType != null ? reflectiveType.getQualifiedName() : null; + } - public ReflectiveClass(@Nonnull PsiClass psiClass, boolean isExact) { - myPsiClass = psiClass; - myIsExact = isExact; + @Contract("null -> null") + @Nullable + public static ReflectiveSignature getMethodSignature(@Nullable PsiMethod method) { + if (method != null) { + final List types = new ArrayList<>(); + final PsiType returnType = method.getReturnType(); + types.add(getTypeText(returnType != null ? returnType : PsiType.VOID)); // null return type means it's a constructor + + for (PsiParameter parameter : method.getParameterList().getParameters()) { + types.add(getTypeText(parameter.getType())); + } + final Image icon = IconDescriptorUpdaters.getIcon(method, Iconable.ICON_FLAG_VISIBILITY); + return ReflectiveSignature.create(icon, types); + } + return null; } @Nonnull - public PsiClass getPsiClass() { - return myPsiClass; + public static String getMethodTypeExpressionText(@Nonnull ReflectiveSignature signature) { + final String types = signature.getText(true, type -> type + ".class"); + return JAVA_LANG_INVOKE_METHOD_TYPE + "." + METHOD_TYPE + types; + } + + public static boolean isCallToMethod( + @Nonnull PsiMethodCallExpression methodCall, + @Nonnull String className, + @Nonnull String methodName + ) { + return MethodCallUtils.isCallToMethod(methodCall, className, null, methodName, (PsiType[])null); + } + + /** + * Tries to unwrap array and find its components + * + * @param maybeArray an array to unwrap + * @return list of unwrapped array components, some or all of them could be null if unknown (but the length is known); + * returns null if nothing is known. + */ + @Nullable + public static List getVarargs(@Nullable PsiExpression maybeArray) { + if (ExpressionUtils.isNullLiteral(maybeArray)) { + return Collections.emptyList(); + } + if (isVarargAsArray(maybeArray)) { + final PsiExpression argumentsDefinition = findDefinition(maybeArray); + if (argumentsDefinition instanceof PsiArrayInitializerExpression) { + return Arrays.asList(((PsiArrayInitializerExpression)argumentsDefinition).getInitializers()); + } + if (argumentsDefinition instanceof PsiNewExpression) { + final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)argumentsDefinition).getArrayInitializer(); + if (arrayInitializer != null) { + return Arrays.asList(arrayInitializer.getInitializers()); + } + final PsiExpression[] dimensions = ((PsiNewExpression)argumentsDefinition).getArrayDimensions(); + if (dimensions.length == 1) { // new Object[length] or new Class[length] + final Integer itemCount = computeConstantExpression(findDefinition(dimensions[0]), Integer.class); + if (itemCount != null && itemCount >= 0 && itemCount < 256) { + return Collections.nCopies(itemCount, null); + } + } + } + } + return null; } - public boolean isExact() { - return myIsExact || myPsiClass.hasModifierProperty(PsiModifier.FINAL); + @Contract("null -> false") + public static boolean isVarargAsArray(@Nullable PsiExpression maybeArray) { + final PsiType type = maybeArray != null ? maybeArray.getType() : null; + return type instanceof PsiArrayType && + type.getArrayDimensions() == 1 && + type.getDeepComponentType() instanceof PsiClassType; } - } - public static final class ReflectiveSignature implements Comparable { - public static final ReflectiveSignature NO_ARGUMENT_CONSTRUCTOR_SIGNATURE = - new ReflectiveSignature(null, PsiKeyword.VOID, ArrayUtil.EMPTY_STRING_ARRAY); + /** + * Take method's return type and parameter types + * from arguments of MethodType.methodType(Class...) and MethodType.genericMethodType(int, boolean?) + */ + @Nullable + public static ReflectiveSignature composeMethodSignature(@Nullable PsiExpression methodTypeExpression) { + final PsiExpression typeDefinition = findDefinition(methodTypeExpression); + if (typeDefinition instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)typeDefinition; + final String referenceName = methodCallExpression.getMethodExpression().getReferenceName(); + + Function composer = null; + if (METHOD_TYPE.equals(referenceName)) { + composer = JavaReflectionReferenceUtil::composeMethodSignatureFromTypes; + } + else if (GENERIC_METHOD_TYPE.equals(referenceName)) { + composer = JavaReflectionReferenceUtil::composeGenericMethodSignature; + } - private final Image myIcon; - @Nonnull - private final String myReturnType; - @Nonnull - private final String[] myArgumentTypes; + if (composer != null) { + final PsiMethod method = methodCallExpression.resolveMethod(); + if (method != null) { + final PsiClass psiClass = method.getContainingClass(); + if (psiClass != null && JAVA_LANG_INVOKE_METHOD_TYPE.equals(psiClass.getQualifiedName())) { + final PsiExpression[] arguments = methodCallExpression.getArgumentList().getExpressions(); + return composer.apply(arguments); + } + } + } + } + return null; + } @Nullable - public static ReflectiveSignature create(@Nonnull List typeTexts) { - return create(null, typeTexts); + private static ReflectiveSignature composeMethodSignatureFromTypes(@Nonnull PsiExpression[] returnAndParameterTypes) { + final List typeTexts = ContainerUtil.map(returnAndParameterTypes, JavaReflectionReferenceUtil::getTypeText); + return ReflectiveSignature.create(typeTexts); } @Nullable - public static ReflectiveSignature create(@Nullable Image icon, @Nonnull List typeTexts) { - if (!typeTexts.isEmpty() && !typeTexts.contains(null)) { - final String[] argumentTypes = ArrayUtil.toStringArray(typeTexts.subList(1, typeTexts.size())); - return new ReflectiveSignature(icon, typeTexts.get(0), argumentTypes); - } - return null; - } + public static Pair.NonNull getGenericSignature(@Nonnull PsiExpression[] genericSignatureShape) { + if (genericSignatureShape.length == 0 || genericSignatureShape.length > 2) { + return null; + } - private ReflectiveSignature(@Nullable Image icon, @Nonnull String returnType, @Nonnull String[] argumentTypes) { - myIcon = icon; - myReturnType = returnType; - myArgumentTypes = argumentTypes; - } + final Integer objectArgCount = computeConstantExpression(genericSignatureShape[0], Integer.class); + final Boolean finalArray = // there's an additional parameter which is an ellipsis or an array + genericSignatureShape.length > 1 ? computeConstantExpression(genericSignatureShape[1], Boolean.class) : false; - public String getText(boolean withReturnType, @Nonnull Function transformation) { - return getText(withReturnType, true, transformation); + if (objectArgCount == null || objectArgCount < 0 || objectArgCount > 255) { + return null; + } + if (finalArray == null || finalArray && objectArgCount > 254) { + return null; + } + return Pair.createNonNull(objectArgCount, finalArray); } - public String getText(boolean withReturnType, boolean withParentheses, @Nonnull Function transformation) { - final StringJoiner joiner = new StringJoiner(", ", withParentheses ? "(" : "", withParentheses ? ")" : ""); - if (withReturnType) { - joiner.add(transformation.apply(myReturnType)); - } - for (String argumentType : myArgumentTypes) { - joiner.add(transformation.apply(argumentType)); - } - return joiner.toString(); + /** + * All the types in the method signature are either unbounded type parameters or java.lang.Object (with possible vararg) + */ + @Nullable + private static ReflectiveSignature composeGenericMethodSignature(@Nonnull PsiExpression[] genericSignatureShape) { + final Pair.NonNull signature = getGenericSignature(genericSignatureShape); + if (signature == null) { + return null; + } + final int objectArgCount = signature.getFirst(); + final boolean finalArray = signature.getSecond(); + + final List typeNames = new ArrayList<>(); + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); // return type + + for (int i = 0; i < objectArgCount; i++) { + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); + } + if (finalArray) { + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT + "[]"); + } + return ReflectiveSignature.create(typeNames); } - @Nonnull - public String getShortReturnType() { - return PsiNameHelper.getShortClassName(myReturnType); + + public static final class ReflectiveType { + final PsiType myType; + final boolean myIsExact; + + private ReflectiveType(@Nonnull PsiType erasedType, boolean isExact) { + myType = erasedType; + myIsExact = isExact; + } + + @Nonnull + public String getQualifiedName() { + return myType.getCanonicalText(); + } + + @Override + public String toString() { + return myType.getCanonicalText(); + } + + public boolean isEqualTo(@Nullable PsiType otherType) { + return otherType != null && myType.equals(erasure(otherType)); + } + + public boolean isAssignableFrom(@Nonnull PsiType type) { + return myType.isAssignableFrom(type); + } + + public boolean isPrimitive() { + return myType instanceof PsiPrimitiveType; + } + + @Nonnull + public PsiType getType() { + return myType; + } + + public boolean isExact() { + return myIsExact; + } + + @Nullable + public ReflectiveClass getReflectiveClass() { + PsiClass psiClass = getPsiClass(); + if (psiClass != null) { + return new ReflectiveClass(psiClass, myIsExact); + } + return null; + } + + @Nullable + public ReflectiveType getArrayComponentType() { + if (myType instanceof PsiArrayType) { + PsiType componentType = ((PsiArrayType)myType).getComponentType(); + return new ReflectiveType(componentType, myIsExact); + } + return null; + } + + @Nullable + public PsiClass getPsiClass() { + return PsiTypesUtil.getPsiClass(myType); + } + + @Contract("!null,_ -> !null; null,_ -> null") + @Nullable + public static ReflectiveType create(@Nullable PsiType originalType, boolean isExact) { + if (originalType != null) { + return new ReflectiveType(erasure(originalType), isExact); + } + return null; + } + + @Contract("!null,_ -> !null; null,_ -> null") + @Nullable + public static ReflectiveType create(@Nullable PsiClass psiClass, boolean isExact) { + if (psiClass != null) { + final PsiElementFactory factory = JavaPsiFacade.getElementFactory(psiClass.getProject()); + return new ReflectiveType(factory.createType(psiClass), isExact); + } + return null; + } + + @Contract("!null -> !null; null -> null") + @Nullable + public static ReflectiveType arrayOf(@Nullable ReflectiveType itemType) { + if (itemType != null) { + return new ReflectiveType(itemType.myType.createArrayType(), itemType.myIsExact); + } + return null; + } + + @Nonnull + private static PsiType erasure(@Nonnull PsiType type) { + final PsiType erasure = TypeConversionUtil.erasure(type); + if (erasure instanceof PsiEllipsisType) { + return ((PsiEllipsisType)erasure).toArrayType(); + } + return erasure; + } } - @Nonnull - public String getShortArgumentTypes() { - return getText(false, PsiNameHelper::getShortClassName); + public static class ReflectiveClass { + final PsiClass myPsiClass; + final boolean myIsExact; + + public ReflectiveClass(@Nonnull PsiClass psiClass, boolean isExact) { + myPsiClass = psiClass; + myIsExact = isExact; + } + + @Nonnull + public PsiClass getPsiClass() { + return myPsiClass; + } + + public boolean isExact() { + return myIsExact || myPsiClass.hasModifierProperty(PsiModifier.FINAL); + } } - @Nonnull - public Image getIcon() { - return myIcon != null ? myIcon : PlatformIconGroup.nodesMethod(); - } - - @Override - public int compareTo(@Nonnull ReflectiveSignature other) { - int c = myArgumentTypes.length - other.myArgumentTypes.length; - if (c != 0) { - return c; - } - c = ArrayUtil.lexicographicCompare(myArgumentTypes, other.myArgumentTypes); - if (c != 0) { - return c; - } - return myReturnType.compareTo(other.myReturnType); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ReflectiveSignature)) { - return false; - } - final ReflectiveSignature other = (ReflectiveSignature) o; - return Objects.equals(myReturnType, other.myReturnType) && - Arrays.equals(myArgumentTypes, other.myArgumentTypes); - } - - @Override - public int hashCode() { - return Objects.hash(myReturnType, myArgumentTypes); - } - - @Override - public String toString() { - return myReturnType + " " + Arrays.toString(myArgumentTypes); - } - } + public static final class ReflectiveSignature implements Comparable { + public static final ReflectiveSignature NO_ARGUMENT_CONSTRUCTOR_SIGNATURE = + new ReflectiveSignature(null, PsiKeyword.VOID, ArrayUtil.EMPTY_STRING_ARRAY); + + private final Image myIcon; + @Nonnull + private final String myReturnType; + @Nonnull + private final String[] myArgumentTypes; + + @Nullable + public static ReflectiveSignature create(@Nonnull List typeTexts) { + return create(null, typeTexts); + } + + @Nullable + public static ReflectiveSignature create(@Nullable Image icon, @Nonnull List typeTexts) { + if (!typeTexts.isEmpty() && !typeTexts.contains(null)) { + final String[] argumentTypes = ArrayUtil.toStringArray(typeTexts.subList(1, typeTexts.size())); + return new ReflectiveSignature(icon, typeTexts.get(0), argumentTypes); + } + return null; + } + + private ReflectiveSignature(@Nullable Image icon, @Nonnull String returnType, @Nonnull String[] argumentTypes) { + myIcon = icon; + myReturnType = returnType; + myArgumentTypes = argumentTypes; + } + + public String getText(boolean withReturnType, @Nonnull Function transformation) { + return getText(withReturnType, true, transformation); + } + + public String getText(boolean withReturnType, boolean withParentheses, @Nonnull Function transformation) { + final StringJoiner joiner = new StringJoiner(", ", withParentheses ? "(" : "", withParentheses ? ")" : ""); + if (withReturnType) { + joiner.add(transformation.apply(myReturnType)); + } + for (String argumentType : myArgumentTypes) { + joiner.add(transformation.apply(argumentType)); + } + return joiner.toString(); + } + + @Nonnull + public String getShortReturnType() { + return PsiNameHelper.getShortClassName(myReturnType); + } + + @Nonnull + public String getShortArgumentTypes() { + return getText(false, PsiNameHelper::getShortClassName); + } + + @Nonnull + public Image getIcon() { + return myIcon != null ? myIcon : PlatformIconGroup.nodesMethod(); + } + + @Override + public int compareTo(@Nonnull ReflectiveSignature other) { + int c = myArgumentTypes.length - other.myArgumentTypes.length; + if (c != 0) { + return c; + } + c = ArrayUtil.lexicographicCompare(myArgumentTypes, other.myArgumentTypes); + if (c != 0) { + return c; + } + return myReturnType.compareTo(other.myReturnType); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReflectiveSignature)) { + return false; + } + final ReflectiveSignature other = (ReflectiveSignature)o; + return Objects.equals(myReturnType, other.myReturnType) && + Arrays.equals(myArgumentTypes, other.myArgumentTypes); + } + + @Override + public int hashCode() { + return Objects.hash(myReturnType, myArgumentTypes); + } + + @Override + public String toString() { + return myReturnType + " " + Arrays.toString(myArgumentTypes); + } + } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/callMatcher/CallMatcher.java b/java-analysis-impl/src/main/java/com/siyeh/ig/callMatcher/CallMatcher.java index 48cecd9037..489c847861 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/callMatcher/CallMatcher.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/callMatcher/CallMatcher.java @@ -14,6 +14,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.Collections; import java.util.Set; import java.util.function.Predicate; @@ -26,368 +27,366 @@ * @author Tagir Valeev */ public interface CallMatcher extends Predicate { - /** - * @return names of the methods for which this matcher may return true. For any other method it guaranteed to return false - */ - Stream names(); - - @Contract(value = "null -> false", pure = true) - boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef); - - @Override - @Contract(value = "null -> false", pure = true) - boolean test(@Nullable PsiMethodCallExpression call); - - @Contract(value = "null -> false", pure = true) - boolean methodMatches(@Nullable PsiMethod method); - - /** - * Returns true if the supplied expression is (possibly parenthesized) method call which matches this matcher - * - * @param expression expression to test - * @return true if the supplied expression matches this matcher - */ - @Contract(value = "null -> false", pure = true) - default boolean matches(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - return expression instanceof PsiMethodCallExpression && test((PsiMethodCallExpression) expression); - } - - /** - * Returns a new matcher which will return true if any of supplied matchers return true - * - * @param matchers - * @return a new matcher - */ - static CallMatcher anyOf(CallMatcher... matchers) { - return new CallMatcher() { - @Override - public Stream names() { - return Stream.of(matchers).flatMap(CallMatcher::names); - } - - @Override - public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { - for (CallMatcher m : matchers) { - if (m.methodReferenceMatches(methodRef)) { - return true; - } - } - return false; - } - - @Override - public boolean methodMatches(PsiMethod method) { - for (CallMatcher m : matchers) { - if (m.methodMatches(method)) { - return true; - } - } - return false; - } - - @Override - public boolean test(PsiMethodCallExpression call) { - for (CallMatcher m : matchers) { - if (m.test(call)) { - return true; - } - } - return false; - } - - @Override - public String toString() { - return Stream.of(matchers).map(CallMatcher::toString).collect(Collectors.joining(" or ", "{", "}")); - } - }; - } - - /** - * Creates a matcher which matches an instance method having one of supplied names which class (or any of superclasses) is className - * - * @param className fully-qualified class name - * @param methodNames names of the methods - * @return a new matcher - */ - @Contract(pure = true) - static Simple instanceCall(@Nonnull String className, String... methodNames) { - return new Simple(className, Set.of(methodNames), null, CallType.INSTANCE); - } - - /** - * Creates a matcher which matches an instance method having one of supplied names which class is exactly a className - * - * @param className fully-qualified class name - * @param methodNames names of the methods - * @return a new matcher - */ - @Contract(pure = true) - static Simple exactInstanceCall(@Nonnull String className, String... methodNames) { - return new Simple(className, Set.of(methodNames), null, CallType.EXACT_INSTANCE); - } - - /** - * Creates a matcher which matches a static method having one of supplied names which class is className - * - * @param className fully-qualified class name - * @param methodNames names of the methods - * @return a new matcher - */ - @Contract(pure = true) - static Simple staticCall(@Nonnull String className, String... methodNames) { - return new Simple(className, Set.of(methodNames), null, CallType.STATIC); - } - - static Simple enumValues() { - return Simple.ENUM_VALUES; - } - - static Simple enumValueOf() { - return Simple.ENUM_VALUE_OF; - } - - /** - * Matches given expression if its a call or a method reference returning a corresponding PsiReferenceExpression if match is successful. - * - * @param expression expression to match - * @return PsiReferenceExpression if match is successful, null otherwise - */ - @Nullable - @Contract(pure = true) - default PsiReferenceExpression getReferenceIfMatched(PsiExpression expression) { - if (expression instanceof PsiMethodReferenceExpression && methodReferenceMatches((PsiMethodReferenceExpression) expression)) { - return (PsiReferenceExpression) expression; - } - if (expression instanceof PsiMethodCallExpression && test((PsiMethodCallExpression) expression)) { - return ((PsiMethodCallExpression) expression).getMethodExpression(); - } - return null; - } - - /** - * @return call matcher with additional check before actual call matching - */ - @Contract(pure = true) - default CallMatcher withContextFilter(@Nonnull Predicate filter) { - return new CallMatcher() { - @Override - public Stream names() { - return CallMatcher.this.names(); - } - - @Override - public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { - if (methodRef == null || !filter.test(methodRef)) { - return false; - } - return CallMatcher.this.methodReferenceMatches(methodRef); - } + /** + * @return names of the methods for which this matcher may return true. For any other method it guaranteed to return false + */ + Stream names(); - @Override - public boolean test(@Nullable PsiMethodCallExpression call) { - if (call == null || !filter.test(call)) { - return false; - } - return CallMatcher.this.test(call); - } + @Contract(value = "null -> false", pure = true) + boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef); - @Override - public boolean methodMatches(@Nullable PsiMethod method) { - if (method == null || !filter.test(method)) { - return false; - } - return CallMatcher.this.methodMatches(method); - } - - @Override - public String toString() { - return CallMatcher.this.toString(); - } - }; - } - - /** - * @return call matcher, that matches element for file with given language level or higher - */ - @Contract(pure = true) - default CallMatcher withLanguageLevelAtLeast(@Nonnull LanguageLevel level) { - return withContextFilter(element -> PsiUtil.getLanguageLevel(element).isAtLeast(level)); - } - - class Simple implements CallMatcher { - static final Simple ENUM_VALUES = new Simple("", Collections.singleton("values"), ArrayUtil.EMPTY_STRING_ARRAY, CallType.ENUM_STATIC); - static final Simple ENUM_VALUE_OF = - new Simple("", Collections.singleton("valueOf"), new String[]{CommonClassNames.JAVA_LANG_STRING}, CallType.ENUM_STATIC); - private final - @Nonnull - String myClassName; - private final - @Nonnull - Set myNames; - private final - @Nullable - String[] myParameters; - private final CallType myCallType; - - private Simple(@Nonnull String className, @Nonnull Set names, @Nullable String[] parameters, CallType callType) { - myClassName = className; - myNames = names; - myParameters = parameters; - myCallType = callType; + @Override + @Contract(value = "null -> false", pure = true) + boolean test(@Nullable PsiMethodCallExpression call); + + @Contract(value = "null -> false", pure = true) + boolean methodMatches(@Nullable PsiMethod method); + + /** + * Returns true if the supplied expression is (possibly parenthesized) method call which matches this matcher + * + * @param expression expression to test + * @return true if the supplied expression matches this matcher + */ + @Contract(value = "null -> false", pure = true) + default boolean matches(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + return expression instanceof PsiMethodCallExpression && test((PsiMethodCallExpression)expression); } - @Override - public Stream names() { - return myNames.stream(); + /** + * Returns a new matcher which will return true if any of supplied matchers return true + * + * @param matchers + * @return a new matcher + */ + static CallMatcher anyOf(CallMatcher... matchers) { + return new CallMatcher() { + @Override + public Stream names() { + return Stream.of(matchers).flatMap(CallMatcher::names); + } + + @Override + public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { + for (CallMatcher m : matchers) { + if (m.methodReferenceMatches(methodRef)) { + return true; + } + } + return false; + } + + @Override + public boolean methodMatches(PsiMethod method) { + for (CallMatcher m : matchers) { + if (m.methodMatches(method)) { + return true; + } + } + return false; + } + + @Override + public boolean test(PsiMethodCallExpression call) { + for (CallMatcher m : matchers) { + if (m.test(call)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return Stream.of(matchers).map(CallMatcher::toString).collect(Collectors.joining(" or ", "{", "}")); + } + }; } /** - * Creates a new matcher which in addition to current matcher checks the number of parameters of the called method + * Creates a matcher which matches an instance method having one of supplied names which class (or any of superclasses) is className * - * @param count expected number of parameters + * @param className fully-qualified class name + * @param methodNames names of the methods * @return a new matcher - * @throws IllegalStateException if this matcher is already limited to parameters count or types */ @Contract(pure = true) - public Simple parameterCount(int count) { - if (myParameters != null) { - throw new IllegalStateException("Parameter count is already set to " + count); - } - return new Simple(myClassName, myNames, count == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : new String[count], myCallType); + static Simple instanceCall(@Nonnull String className, String... methodNames) { + return new Simple(className, Set.of(methodNames), null, CallType.INSTANCE); } /** - * Creates a new matcher which in addition to current matcher checks the number of parameters of the called method - * and their types + * Creates a matcher which matches an instance method having one of supplied names which class is exactly a className * - * @param types textual representation of parameter types (may contain null to ignore checking parameter type of specific argument) + * @param className fully-qualified class name + * @param methodNames names of the methods * @return a new matcher - * @throws IllegalStateException if this matcher is already limited to parameters count or types */ @Contract(pure = true) - public Simple parameterTypes(@Nonnull String... types) { - if (myParameters != null) { - throw new IllegalStateException("Parameters are already registered"); - } - return new Simple(myClassName, myNames, types.length == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : types.clone(), myCallType); + static Simple exactInstanceCall(@Nonnull String className, String... methodNames) { + return new Simple(className, Set.of(methodNames), null, CallType.EXACT_INSTANCE); } - private static boolean parameterTypeMatches(String type, PsiParameter parameter) { - if (type == null) { - return true; - } - PsiType psiType = parameter.getType(); - return psiType.equalsToText(type) || - psiType instanceof PsiClassType && ((PsiClassType) psiType).rawType().equalsToText(type); + /** + * Creates a matcher which matches a static method having one of supplied names which class is className + * + * @param className fully-qualified class name + * @param methodNames names of the methods + * @return a new matcher + */ + @Contract(pure = true) + static Simple staticCall(@Nonnull String className, String... methodNames) { + return new Simple(className, Set.of(methodNames), null, CallType.STATIC); } - @Contract(pure = true) - @Override - public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { - if (methodRef == null) { - return false; - } - String name = methodRef.getReferenceName(); - if (!myNames.contains(name)) { - return false; - } - PsiMethod method = ObjectUtil.tryCast(methodRef.resolve(), PsiMethod.class); - if (!methodMatches(method)) { - return false; - } - PsiParameterList parameterList = method.getParameterList(); - return parametersMatch(parameterList); + static Simple enumValues() { + return Simple.ENUM_VALUES; + } + + static Simple enumValueOf() { + return Simple.ENUM_VALUE_OF; } + /** + * Matches given expression if its a call or a method reference returning a corresponding PsiReferenceExpression if match is successful. + * + * @param expression expression to match + * @return PsiReferenceExpression if match is successful, null otherwise + */ + @Nullable @Contract(pure = true) - @Override - public boolean test(PsiMethodCallExpression call) { - if (call == null) { - return false; - } - String name = call.getMethodExpression().getReferenceName(); - if (!myNames.contains(name)) { - return false; - } - PsiExpression[] args = call.getArgumentList().getExpressions(); - if (myParameters != null && myParameters.length > 0) { - if (args.length < myParameters.length - 1) { - return false; + default PsiReferenceExpression getReferenceIfMatched(PsiExpression expression) { + if (expression instanceof PsiMethodReferenceExpression && methodReferenceMatches((PsiMethodReferenceExpression)expression)) { + return (PsiReferenceExpression)expression; } - } - PsiMethod method = call.resolveMethod(); - if (method == null) { - return false; - } - PsiParameterList parameterList = method.getParameterList(); - int count = parameterList.getParametersCount(); - if (count > args.length + 1 || (!MethodCallUtils.isVarArgCall(call) && count != args.length)) { - return false; - } - return methodMatches(method); + if (expression instanceof PsiMethodCallExpression && test((PsiMethodCallExpression)expression)) { + return ((PsiMethodCallExpression)expression).getMethodExpression(); + } + return null; } - private boolean parametersMatch(@Nonnull PsiParameterList parameterList) { - if (myParameters == null) { - return true; - } - if (myParameters.length != parameterList.getParametersCount()) { - return false; - } - return StreamEx.zip(myParameters, parameterList.getParameters(), - Simple::parameterTypeMatches).allMatch(Boolean.TRUE::equals); + /** + * @return call matcher with additional check before actual call matching + */ + @Contract(pure = true) + default CallMatcher withContextFilter(@Nonnull Predicate filter) { + return new CallMatcher() { + @Override + public Stream names() { + return CallMatcher.this.names(); + } + + @Override + public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { + if (methodRef == null || !filter.test(methodRef)) { + return false; + } + return CallMatcher.this.methodReferenceMatches(methodRef); + } + + @Override + public boolean test(@Nullable PsiMethodCallExpression call) { + if (call == null || !filter.test(call)) { + return false; + } + return CallMatcher.this.test(call); + } + + @Override + public boolean methodMatches(@Nullable PsiMethod method) { + if (method == null || !filter.test(method)) { + return false; + } + return CallMatcher.this.methodMatches(method); + } + + @Override + public String toString() { + return CallMatcher.this.toString(); + } + }; } - @Override - @Contract(value = "null -> false", pure = true) - public boolean methodMatches(PsiMethod method) { - if (method == null) { - return false; - } - if (!myNames.contains(method.getName())) { - return false; - } - PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return false; - } - return myCallType.matches(aClass, myClassName, method.hasModifierProperty(PsiModifier.STATIC)) && - parametersMatch(method.getParameterList()); + /** + * @return call matcher, that matches element for file with given language level or higher + */ + @Contract(pure = true) + default CallMatcher withLanguageLevelAtLeast(@Nonnull LanguageLevel level) { + return withContextFilter(element -> PsiUtil.getLanguageLevel(element).isAtLeast(level)); } - @Override - public String toString() { - return myClassName + "." + String.join("|", myNames); + class Simple implements CallMatcher { + static final Simple ENUM_VALUES = + new Simple("", Collections.singleton("values"), ArrayUtil.EMPTY_STRING_ARRAY, CallType.ENUM_STATIC); + static final Simple ENUM_VALUE_OF = + new Simple("", Collections.singleton("valueOf"), new String[]{CommonClassNames.JAVA_LANG_STRING}, CallType.ENUM_STATIC); + @Nonnull + private final String myClassName; + @Nonnull + private final Set myNames; + @Nullable + private final String[] myParameters; + private final CallType myCallType; + + private Simple(@Nonnull String className, @Nonnull Set names, @Nullable String[] parameters, CallType callType) { + myClassName = className; + myNames = names; + myParameters = parameters; + myCallType = callType; + } + + @Override + public Stream names() { + return myNames.stream(); + } + + /** + * Creates a new matcher which in addition to current matcher checks the number of parameters of the called method + * + * @param count expected number of parameters + * @return a new matcher + * @throws IllegalStateException if this matcher is already limited to parameters count or types + */ + @Contract(pure = true) + public Simple parameterCount(int count) { + if (myParameters != null) { + throw new IllegalStateException("Parameter count is already set to " + count); + } + return new Simple(myClassName, myNames, count == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : new String[count], myCallType); + } + + /** + * Creates a new matcher which in addition to current matcher checks the number of parameters of the called method + * and their types + * + * @param types textual representation of parameter types (may contain null to ignore checking parameter type of specific argument) + * @return a new matcher + * @throws IllegalStateException if this matcher is already limited to parameters count or types + */ + @Contract(pure = true) + public Simple parameterTypes(@Nonnull String... types) { + if (myParameters != null) { + throw new IllegalStateException("Parameters are already registered"); + } + return new Simple(myClassName, myNames, types.length == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : types.clone(), myCallType); + } + + private static boolean parameterTypeMatches(String type, PsiParameter parameter) { + if (type == null) { + return true; + } + PsiType psiType = parameter.getType(); + return psiType.equalsToText(type) || + psiType instanceof PsiClassType && ((PsiClassType)psiType).rawType().equalsToText(type); + } + + @Contract(pure = true) + @Override + public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { + if (methodRef == null) { + return false; + } + String name = methodRef.getReferenceName(); + if (!myNames.contains(name)) { + return false; + } + PsiMethod method = ObjectUtil.tryCast(methodRef.resolve(), PsiMethod.class); + if (!methodMatches(method)) { + return false; + } + PsiParameterList parameterList = method.getParameterList(); + return parametersMatch(parameterList); + } + + @Contract(pure = true) + @Override + public boolean test(PsiMethodCallExpression call) { + if (call == null) { + return false; + } + String name = call.getMethodExpression().getReferenceName(); + if (!myNames.contains(name)) { + return false; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + if (myParameters != null && myParameters.length > 0) { + if (args.length < myParameters.length - 1) { + return false; + } + } + PsiMethod method = call.resolveMethod(); + if (method == null) { + return false; + } + PsiParameterList parameterList = method.getParameterList(); + int count = parameterList.getParametersCount(); + if (count > args.length + 1 || (!MethodCallUtils.isVarArgCall(call) && count != args.length)) { + return false; + } + return methodMatches(method); + } + + private boolean parametersMatch(@Nonnull PsiParameterList parameterList) { + if (myParameters == null) { + return true; + } + if (myParameters.length != parameterList.getParametersCount()) { + return false; + } + return StreamEx.zip(myParameters, parameterList.getParameters(), Simple::parameterTypeMatches) + .allMatch(Boolean.TRUE::equals); + } + + @Override + @Contract(value = "null -> false", pure = true) + public boolean methodMatches(PsiMethod method) { + if (method == null) { + return false; + } + if (!myNames.contains(method.getName())) { + return false; + } + PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return false; + } + return myCallType.matches(aClass, myClassName, method.hasModifierProperty(PsiModifier.STATIC)) && + parametersMatch(method.getParameterList()); + } + + @Override + public String toString() { + return myClassName + "." + String.join("|", myNames); + } + } + + enum CallType { + STATIC { + @Override + boolean matches(PsiClass aClass, String className, boolean isStatic) { + return isStatic && className.equals(aClass.getQualifiedName()); + } + }, + ENUM_STATIC { + @Override + boolean matches(PsiClass aClass, String className, boolean isStatic) { + return isStatic && aClass.isEnum(); + } + }, + INSTANCE { + @Override + boolean matches(PsiClass aClass, String className, boolean isStatic) { + return !isStatic && InheritanceUtil.isInheritor(aClass, className); + } + }, + EXACT_INSTANCE { + @Override + boolean matches(PsiClass aClass, String className, boolean isStatic) { + return !isStatic && className.equals(aClass.getQualifiedName()); + } + }; + + abstract boolean matches(PsiClass aClass, String className, boolean isStatic); } - } - - enum CallType { - STATIC { - @Override - boolean matches(PsiClass aClass, String className, boolean isStatic) { - return isStatic && className.equals(aClass.getQualifiedName()); - } - }, - ENUM_STATIC { - @Override - boolean matches(PsiClass aClass, String className, boolean isStatic) { - return isStatic && aClass.isEnum(); - } - }, - INSTANCE { - @Override - boolean matches(PsiClass aClass, String className, boolean isStatic) { - return !isStatic && InheritanceUtil.isInheritor(aClass, className); - } - }, - EXACT_INSTANCE { - @Override - boolean matches(PsiClass aClass, String className, boolean isStatic) { - return !isStatic && className.equals(aClass.getQualifiedName()); - } - }; - - abstract boolean matches(PsiClass aClass, String className, boolean isStatic); - } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CollectionUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CollectionUtils.java index fe0d5b80e5..acf1118baf 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CollectionUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CollectionUtils.java @@ -24,256 +24,259 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; public class CollectionUtils { - /** - * @noinspection StaticCollection - */ - @NonNls - private static final Set s_allCollectionClassesAndInterfaces; - /** - * @noinspection StaticCollection - */ - @NonNls - private static final Map s_interfaceForCollection = new HashMap<>(); - - static { - final Set allCollectionClassesAndInterfaces = new HashSet<>(); - allCollectionClassesAndInterfaces.add("java.util.AbstractCollection"); - allCollectionClassesAndInterfaces.add("java.util.AbstractList"); - allCollectionClassesAndInterfaces.add("java.util.AbstractMap"); - allCollectionClassesAndInterfaces.add("java.util.AbstractQueue"); - allCollectionClassesAndInterfaces.add("java.util.AbstractSequentialList"); - allCollectionClassesAndInterfaces.add("java.util.AbstractSet"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_ARRAY_LIST); - allCollectionClassesAndInterfaces.add("java.util.ArrayDeque"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_COLLECTION); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_DICTIONARY); - allCollectionClassesAndInterfaces.add("java.util.EnumMap"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_HASH_MAP); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_HASH_SET); - allCollectionClassesAndInterfaces.add("java.util.Hashtable"); - allCollectionClassesAndInterfaces.add("java.util.IdentityHashMap"); - allCollectionClassesAndInterfaces.add("java.util.LinkedHashMap"); - allCollectionClassesAndInterfaces.add("java.util.LinkedHashSet"); - allCollectionClassesAndInterfaces.add("java.util.LinkedList"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_LIST); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_MAP); - allCollectionClassesAndInterfaces.add("java.util.PriorityQueue"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_QUEUE); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_SET); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_SORTED_MAP); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_SORTED_SET); - allCollectionClassesAndInterfaces.add("java.util.Stack"); - allCollectionClassesAndInterfaces.add("java.util.TreeMap"); - allCollectionClassesAndInterfaces.add("java.util.TreeSet"); - allCollectionClassesAndInterfaces.add("java.util.Vector"); - allCollectionClassesAndInterfaces.add("java.util.WeakHashMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ArrayBlockingQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.BlockingDeque"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.BlockingQueue"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_CONCURRENT_HASH_MAP); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentLinkedDeque"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentLinkedQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentNavigableMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentSkipListMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentSkipListSet"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.CopyOnWriteArrayList"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.CopyOnWriteArraySet"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.DelayQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedBlockingDeque"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedBlockingQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedTransferQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.PriorityBlockingQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.SynchronousQueue"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.ArrayList"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Collection"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.HashMap"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.HashSet"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Hashtable"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.LinkedList"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.List"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Map"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Set"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.SortedMap"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.SortedSet"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.TreeMap"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.TreeSet"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Vector"); - s_allCollectionClassesAndInterfaces = Collections.unmodifiableSet(allCollectionClassesAndInterfaces); - - s_interfaceForCollection.put("ArrayList", "List"); - s_interfaceForCollection.put("EnumMap", "Map"); - s_interfaceForCollection.put("EnumSet", "Set"); - s_interfaceForCollection.put("HashMap", "Map"); - s_interfaceForCollection.put("HashSet", "Set"); - s_interfaceForCollection.put("Hashtable", "Map"); - s_interfaceForCollection.put("IdentityHashMap", "Map"); - s_interfaceForCollection.put("LinkedHashMap", "Map"); - s_interfaceForCollection.put("LinkedHashSet", "Set"); - s_interfaceForCollection.put("LinkedList", "List"); - s_interfaceForCollection.put("PriorityQueue", "Queue"); - s_interfaceForCollection.put("TreeMap", "Map"); - s_interfaceForCollection.put("TreeSet", "SortedSet"); - s_interfaceForCollection.put("Vector", "List"); - s_interfaceForCollection.put("WeakHashMap", "Map"); - s_interfaceForCollection.put(JavaClassNames.JAVA_UTIL_ARRAY_LIST, JavaClassNames.JAVA_UTIL_LIST); - s_interfaceForCollection.put("java.util.EnumMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.EnumSet", JavaClassNames.JAVA_UTIL_SET); - s_interfaceForCollection.put(JavaClassNames.JAVA_UTIL_HASH_MAP, JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put(JavaClassNames.JAVA_UTIL_HASH_SET, JavaClassNames.JAVA_UTIL_SET); - s_interfaceForCollection.put("java.util.Hashtable", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.IdentityHashMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.LinkedHashMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.LinkedHashSet", JavaClassNames.JAVA_UTIL_SET); - s_interfaceForCollection.put("java.util.LinkedList", JavaClassNames.JAVA_UTIL_LIST); - s_interfaceForCollection.put("java.util.PriorityQueue", JavaClassNames.JAVA_UTIL_QUEUE); - s_interfaceForCollection.put("java.util.TreeMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.TreeSet", JavaClassNames.JAVA_UTIL_SET); - s_interfaceForCollection.put("java.util.Vector", JavaClassNames.JAVA_UTIL_LIST); - s_interfaceForCollection.put("java.util.WeakHashMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("com.sun.java.util.collections.HashSet", "com.sun.java.util.collections.Set"); - s_interfaceForCollection.put("com.sun.java.util.collections.TreeSet", "com.sun.java.util.collections.Set"); - s_interfaceForCollection.put("com.sun.java.util.collections.Vector", "com.sun.java.util.collections.List"); - s_interfaceForCollection.put("com.sun.java.util.collections.ArrayList", "com.sun.java.util.collections.List"); - s_interfaceForCollection.put("com.sun.java.util.collections.LinkedList", "com.sun.java.util.collections.List"); - s_interfaceForCollection.put("com.sun.java.util.collections.TreeMap", "com.sun.java.util.collections.Map"); - s_interfaceForCollection.put("com.sun.java.util.collections.HashMap", "com.sun.java.util.collections.Map"); - s_interfaceForCollection.put("com.sun.java.util.collections.Hashtable", "com.sun.java.util.collections.Map"); - } + /** + * @noinspection StaticCollection + */ + @NonNls + private static final Set s_allCollectionClassesAndInterfaces; + /** + * @noinspection StaticCollection + */ + @NonNls + private static final Map s_interfaceForCollection = new HashMap<>(); - /** - * Matches a call which creates collection of the same size as the qualifier collection - */ - public static final CallMatcher DERIVED_COLLECTION = - CallMatcher.anyOf( - CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "keySet", "values", "entrySet").parameterCount(0), - CallMatcher.instanceCall("java.util.NavigableMap", "descendingKeySet", "descendingMap", "navigableKeySet").parameterCount(0), - CallMatcher.instanceCall("java.util.NavigableSet", "descendingSet").parameterCount(0) - ); + static { + final Set allCollectionClassesAndInterfaces = new HashSet<>(); + allCollectionClassesAndInterfaces.add("java.util.AbstractCollection"); + allCollectionClassesAndInterfaces.add("java.util.AbstractList"); + allCollectionClassesAndInterfaces.add("java.util.AbstractMap"); + allCollectionClassesAndInterfaces.add("java.util.AbstractQueue"); + allCollectionClassesAndInterfaces.add("java.util.AbstractSequentialList"); + allCollectionClassesAndInterfaces.add("java.util.AbstractSet"); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_ARRAY_LIST); + allCollectionClassesAndInterfaces.add("java.util.ArrayDeque"); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_COLLECTION); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_DICTIONARY); + allCollectionClassesAndInterfaces.add("java.util.EnumMap"); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_HASH_MAP); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_HASH_SET); + allCollectionClassesAndInterfaces.add("java.util.Hashtable"); + allCollectionClassesAndInterfaces.add("java.util.IdentityHashMap"); + allCollectionClassesAndInterfaces.add("java.util.LinkedHashMap"); + allCollectionClassesAndInterfaces.add("java.util.LinkedHashSet"); + allCollectionClassesAndInterfaces.add("java.util.LinkedList"); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_LIST); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_MAP); + allCollectionClassesAndInterfaces.add("java.util.PriorityQueue"); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_QUEUE); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_SET); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_SORTED_MAP); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_SORTED_SET); + allCollectionClassesAndInterfaces.add("java.util.Stack"); + allCollectionClassesAndInterfaces.add("java.util.TreeMap"); + allCollectionClassesAndInterfaces.add("java.util.TreeSet"); + allCollectionClassesAndInterfaces.add("java.util.Vector"); + allCollectionClassesAndInterfaces.add("java.util.WeakHashMap"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ArrayBlockingQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.BlockingDeque"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.BlockingQueue"); + allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_CONCURRENT_HASH_MAP); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentLinkedDeque"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentLinkedQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentMap"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentNavigableMap"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentSkipListMap"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentSkipListSet"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.CopyOnWriteArrayList"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.CopyOnWriteArraySet"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.DelayQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedBlockingDeque"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedBlockingQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedTransferQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.PriorityBlockingQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.SynchronousQueue"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.ArrayList"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Collection"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.HashMap"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.HashSet"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Hashtable"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.LinkedList"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.List"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Map"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Set"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.SortedMap"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.SortedSet"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.TreeMap"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.TreeSet"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Vector"); + s_allCollectionClassesAndInterfaces = Collections.unmodifiableSet(allCollectionClassesAndInterfaces); + s_interfaceForCollection.put("ArrayList", "List"); + s_interfaceForCollection.put("EnumMap", "Map"); + s_interfaceForCollection.put("EnumSet", "Set"); + s_interfaceForCollection.put("HashMap", "Map"); + s_interfaceForCollection.put("HashSet", "Set"); + s_interfaceForCollection.put("Hashtable", "Map"); + s_interfaceForCollection.put("IdentityHashMap", "Map"); + s_interfaceForCollection.put("LinkedHashMap", "Map"); + s_interfaceForCollection.put("LinkedHashSet", "Set"); + s_interfaceForCollection.put("LinkedList", "List"); + s_interfaceForCollection.put("PriorityQueue", "Queue"); + s_interfaceForCollection.put("TreeMap", "Map"); + s_interfaceForCollection.put("TreeSet", "SortedSet"); + s_interfaceForCollection.put("Vector", "List"); + s_interfaceForCollection.put("WeakHashMap", "Map"); + s_interfaceForCollection.put(JavaClassNames.JAVA_UTIL_ARRAY_LIST, JavaClassNames.JAVA_UTIL_LIST); + s_interfaceForCollection.put("java.util.EnumMap", JavaClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.EnumSet", JavaClassNames.JAVA_UTIL_SET); + s_interfaceForCollection.put(JavaClassNames.JAVA_UTIL_HASH_MAP, JavaClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put(JavaClassNames.JAVA_UTIL_HASH_SET, JavaClassNames.JAVA_UTIL_SET); + s_interfaceForCollection.put("java.util.Hashtable", JavaClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.IdentityHashMap", JavaClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.LinkedHashMap", JavaClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.LinkedHashSet", JavaClassNames.JAVA_UTIL_SET); + s_interfaceForCollection.put("java.util.LinkedList", JavaClassNames.JAVA_UTIL_LIST); + s_interfaceForCollection.put("java.util.PriorityQueue", JavaClassNames.JAVA_UTIL_QUEUE); + s_interfaceForCollection.put("java.util.TreeMap", JavaClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.TreeSet", JavaClassNames.JAVA_UTIL_SET); + s_interfaceForCollection.put("java.util.Vector", JavaClassNames.JAVA_UTIL_LIST); + s_interfaceForCollection.put("java.util.WeakHashMap", JavaClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("com.sun.java.util.collections.HashSet", "com.sun.java.util.collections.Set"); + s_interfaceForCollection.put("com.sun.java.util.collections.TreeSet", "com.sun.java.util.collections.Set"); + s_interfaceForCollection.put("com.sun.java.util.collections.Vector", "com.sun.java.util.collections.List"); + s_interfaceForCollection.put("com.sun.java.util.collections.ArrayList", "com.sun.java.util.collections.List"); + s_interfaceForCollection.put("com.sun.java.util.collections.LinkedList", "com.sun.java.util.collections.List"); + s_interfaceForCollection.put("com.sun.java.util.collections.TreeMap", "com.sun.java.util.collections.Map"); + s_interfaceForCollection.put("com.sun.java.util.collections.HashMap", "com.sun.java.util.collections.Map"); + s_interfaceForCollection.put("com.sun.java.util.collections.Hashtable", "com.sun.java.util.collections.Map"); + } - private CollectionUtils() { - super(); - } + /** + * Matches a call which creates collection of the same size as the qualifier collection + */ + public static final CallMatcher DERIVED_COLLECTION = CallMatcher.anyOf( + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "keySet", "values", "entrySet").parameterCount(0), + CallMatcher.instanceCall("java.util.NavigableMap", "descendingKeySet", "descendingMap", "navigableKeySet") + .parameterCount(0), + CallMatcher.instanceCall("java.util.NavigableSet", "descendingSet").parameterCount(0) + ); - public static Set getAllCollectionNames() { - return s_allCollectionClassesAndInterfaces; - } - @Contract("null -> false") - public static boolean isConcreteCollectionClass(@Nullable PsiType type) { - if (!(type instanceof PsiClassType)) { - return false; - } - final PsiClassType classType = (PsiClassType) type; - final PsiClass resolved = classType.resolve(); - if (resolved == null) { - return false; + private CollectionUtils() { + super(); } - return isConcreteCollectionClass(resolved); - } - @Contract("null -> false") - public static boolean isConcreteCollectionClass(PsiClass aClass) { - if (aClass == null || aClass.isEnum() || aClass.isInterface() || aClass.isAnnotationType() || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) { - return false; - } - if (!InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_COLLECTION) && !InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_MAP)) { - return false; + public static Set getAllCollectionNames() { + return s_allCollectionClassesAndInterfaces; } - @NonNls final String name = aClass.getQualifiedName(); - return name != null && name.startsWith("java.util."); - } - public static boolean isCollectionClassOrInterface(@Nullable PsiType type) { - if (!(type instanceof PsiClassType)) { - return false; + @Contract("null -> false") + public static boolean isConcreteCollectionClass(@Nullable PsiType type) { + if (!(type instanceof PsiClassType)) { + return false; + } + final PsiClassType classType = (PsiClassType)type; + final PsiClass resolved = classType.resolve(); + if (resolved == null) { + return false; + } + return isConcreteCollectionClass(resolved); } - final PsiClassType classType = (PsiClassType) type; - final PsiClass resolved = classType.resolve(); - if (resolved == null) { - return false; - } - return InheritanceUtil.isInheritor(resolved, JavaClassNames.JAVA_UTIL_COLLECTION) || - InheritanceUtil.isInheritor(resolved, JavaClassNames.JAVA_UTIL_MAP) || - InheritanceUtil.isInheritor(resolved, "com.google.common.collect.Multimap") || - InheritanceUtil.isInheritor(resolved, "com.google.common.collect.Table"); - } - - public static boolean isCollectionClassOrInterface(PsiClass aClass) { - return isCollectionClassOrInterface(aClass, new HashSet<>()); - } - /** - * alreadyChecked set to avoid infinite loop in constructs like: - * class C extends C {} - */ - private static boolean isCollectionClassOrInterface(PsiClass aClass, Set visitedClasses) { - if (!visitedClasses.add(aClass)) { - return false; + @Contract("null -> false") + public static boolean isConcreteCollectionClass(PsiClass aClass) { + if (aClass == null || aClass.isEnum() || aClass.isInterface() || aClass.isAnnotationType() || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) { + return false; + } + if (!InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_COLLECTION) + && !InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_MAP)) { + return false; + } + @NonNls final String name = aClass.getQualifiedName(); + return name != null && name.startsWith("java.util."); } - final String className = aClass.getQualifiedName(); - if (s_allCollectionClassesAndInterfaces.contains(className)) { - return true; + + public static boolean isCollectionClassOrInterface(@Nullable PsiType type) { + if (!(type instanceof PsiClassType)) { + return false; + } + final PsiClassType classType = (PsiClassType)type; + final PsiClass resolved = classType.resolve(); + if (resolved == null) { + return false; + } + return InheritanceUtil.isInheritor(resolved, JavaClassNames.JAVA_UTIL_COLLECTION) || + InheritanceUtil.isInheritor(resolved, JavaClassNames.JAVA_UTIL_MAP) || + InheritanceUtil.isInheritor(resolved, "com.google.common.collect.Multimap") || + InheritanceUtil.isInheritor(resolved, "com.google.common.collect.Table"); } - final PsiClass[] supers = aClass.getSupers(); - for (PsiClass aSuper : supers) { - if (isCollectionClassOrInterface(aSuper, visitedClasses)) { - return true; - } + + public static boolean isCollectionClassOrInterface(PsiClass aClass) { + return isCollectionClassOrInterface(aClass, new HashSet<>()); } - return false; - } - public static boolean isWeakCollectionClass(@Nullable PsiType type) { - if (!(type instanceof PsiClassType)) { - return false; + /** + * alreadyChecked set to avoid infinite loop in constructs like: + * class C extends C {} + */ + private static boolean isCollectionClassOrInterface(PsiClass aClass, Set visitedClasses) { + if (!visitedClasses.add(aClass)) { + return false; + } + final String className = aClass.getQualifiedName(); + if (s_allCollectionClassesAndInterfaces.contains(className)) { + return true; + } + final PsiClass[] supers = aClass.getSupers(); + for (PsiClass aSuper : supers) { + if (isCollectionClassOrInterface(aSuper, visitedClasses)) { + return true; + } + } + return false; } - final String typeText = type.getCanonicalText(); - return "java.util.WeakHashMap".equals(typeText); - } - public static boolean isConstantEmptyArray(@Nonnull PsiField field) { - if (!field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.FINAL)) { - return false; + public static boolean isWeakCollectionClass(@Nullable PsiType type) { + if (!(type instanceof PsiClassType)) { + return false; + } + final String typeText = type.getCanonicalText(); + return "java.util.WeakHashMap".equals(typeText); } - return isEmptyArray(field); - } - public static boolean isEmptyArray(PsiVariable variable) { - final PsiExpression initializer = variable.getInitializer(); - if (initializer instanceof PsiArrayInitializerExpression) { - final PsiArrayInitializerExpression arrayInitializerExpression = (PsiArrayInitializerExpression) initializer; - final PsiExpression[] initializers = arrayInitializerExpression.getInitializers(); - return initializers.length == 0; + public static boolean isConstantEmptyArray(@Nonnull PsiField field) { + if (!field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.FINAL)) { + return false; + } + return isEmptyArray(field); } - return ConstructionUtils.isEmptyArrayInitializer(initializer); - } - public static boolean isArrayOrCollectionField(@Nonnull PsiField field) { - final PsiType type = field.getType(); - if (isCollectionClassOrInterface(type)) { - return true; + public static boolean isEmptyArray(PsiVariable variable) { + final PsiExpression initializer = variable.getInitializer(); + if (initializer instanceof PsiArrayInitializerExpression) { + final PsiArrayInitializerExpression arrayInitializerExpression = (PsiArrayInitializerExpression)initializer; + final PsiExpression[] initializers = arrayInitializerExpression.getInitializers(); + return initializers.length == 0; + } + return ConstructionUtils.isEmptyArrayInitializer(initializer); } - if (!(type instanceof PsiArrayType)) { - return false; + + public static boolean isArrayOrCollectionField(@Nonnull PsiField field) { + final PsiType type = field.getType(); + if (isCollectionClassOrInterface(type)) { + return true; + } + if (!(type instanceof PsiArrayType)) { + return false; + } + // constant empty arrays are ignored. + return !isConstantEmptyArray(field); } - // constant empty arrays are ignored. - return !isConstantEmptyArray(field); - } - public static String getInterfaceForClass(String name) { - final int parameterStart = name.indexOf((int) '<'); - final String baseName; - if (parameterStart >= 0) { - baseName = name.substring(0, parameterStart).trim(); - } else { - baseName = name; + public static String getInterfaceForClass(String name) { + final int parameterStart = name.indexOf((int)'<'); + final String baseName; + if (parameterStart >= 0) { + baseName = name.substring(0, parameterStart).trim(); + } + else { + baseName = name; + } + return s_interfaceForCollection.get(baseName); } - return s_interfaceForCollection.get(baseName); - } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ConstructionUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ConstructionUtils.java index 26766bb273..d9ba8b6cc4 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ConstructionUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ConstructionUtils.java @@ -32,247 +32,250 @@ * @author Tagir Valeev */ public class ConstructionUtils { - private static final Set GUAVA_UTILITY_CLASSES = Set.of("com.google.common.collect.Maps", "com.google.common.collect.Lists", "com.google.common.collect.Sets"); - private static final CallMatcher ENUM_SET_NONE_OF = CallMatcher.staticCall("java.util.EnumSet", "noneOf").parameterCount(1); + private static final Set GUAVA_UTILITY_CLASSES = + Set.of("com.google.common.collect.Maps", "com.google.common.collect.Lists", "com.google.common.collect.Sets"); + private static final CallMatcher ENUM_SET_NONE_OF = CallMatcher.staticCall("java.util.EnumSet", "noneOf").parameterCount(1); - /** - * Checks that given expression initializes empty StringBuilder or StringBuffer (either with explicit default capacity or not) - * - * @param initializer initializer to check - * @return true if the initializer is empty StringBuilder or StringBuffer initializer - */ - @Contract("null -> false") - public static boolean isEmptyStringBuilderInitializer(PsiExpression initializer) { - return "\"\"".equals(getStringBuilderInitializerText(initializer)); - } - - /** - * Returns a textual representation of an expression which is equivalent to the initial value of newly created StringBuilder or StringBuffer - * - * @param construction StringBuilder/StringBuffer construction expression - * @return a textual representation of an initial value CharSequence or null if supplied expression is not StringBuilder/StringBuffer - * construction expression - */ - @Contract("null -> null") - public static String getStringBuilderInitializerText(PsiExpression construction) { - construction = PsiUtil.skipParenthesizedExprDown(construction); - if (!(construction instanceof PsiNewExpression)) { - return null; - } - final PsiNewExpression newExpression = (PsiNewExpression) construction; - final PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); - if (classReference == null) { - return null; - } - final PsiElement target = classReference.resolve(); - if (!(target instanceof PsiClass)) { - return null; - } - final PsiClass aClass = (PsiClass) target; - final String qualifiedName = aClass.getQualifiedName(); - if (!JavaClassNames.JAVA_LANG_STRING_BUILDER.equals(qualifiedName) && !JavaClassNames.JAVA_LANG_STRING_BUFFER.equals(qualifiedName)) { - return null; - } - final PsiExpressionList argumentList = newExpression.getArgumentList(); - if (argumentList == null) { - return null; - } - final PsiExpression[] arguments = argumentList.getExpressions(); - if (arguments.length == 0) { - return "\"\""; - } - if (arguments.length != 1) { - return null; + /** + * Checks that given expression initializes empty StringBuilder or StringBuffer (either with explicit default capacity or not) + * + * @param initializer initializer to check + * @return true if the initializer is empty StringBuilder or StringBuffer initializer + */ + @Contract("null -> false") + public static boolean isEmptyStringBuilderInitializer(PsiExpression initializer) { + return "\"\"".equals(getStringBuilderInitializerText(initializer)); } - final PsiExpression argument = arguments[0]; - final PsiType argumentType = argument.getType(); - if (PsiType.INT.equals(argumentType)) { - return "\"\""; - } - return argument.getText(); - } - /** - * Checks that given expression initializes empty Collection or Map - * - * @param expression expression to check - * @return true if the expression is the empty Collection or Map initializer - */ - @Contract("null -> false") - public static boolean isEmptyCollectionInitializer(PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiNewExpression) { - PsiExpressionList argumentList = ((PsiNewExpression) expression).getArgumentList(); - if (argumentList != null && argumentList.getExpressions().length == 0) { - PsiType type = expression.getType(); - return InheritanceUtil.isInheritor(type, JavaClassNames.JAVA_UTIL_COLLECTION) || InheritanceUtil.isInheritor(type, JavaClassNames - .JAVA_UTIL_MAP); - } + /** + * Returns a textual representation of an expression which is equivalent to the initial value of newly created StringBuilder or StringBuffer + * + * @param construction StringBuilder/StringBuffer construction expression + * @return a textual representation of an initial value CharSequence or null if supplied expression is not StringBuilder/StringBuffer + * construction expression + */ + @Contract("null -> null") + public static String getStringBuilderInitializerText(PsiExpression construction) { + construction = PsiUtil.skipParenthesizedExprDown(construction); + if (!(construction instanceof PsiNewExpression)) { + return null; + } + final PsiNewExpression newExpression = (PsiNewExpression)construction; + final PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); + if (classReference == null) { + return null; + } + final PsiElement target = classReference.resolve(); + if (!(target instanceof PsiClass)) { + return null; + } + final PsiClass aClass = (PsiClass)target; + final String qualifiedName = aClass.getQualifiedName(); + if (!JavaClassNames.JAVA_LANG_STRING_BUILDER.equals(qualifiedName) && !JavaClassNames.JAVA_LANG_STRING_BUFFER.equals(qualifiedName)) { + return null; + } + final PsiExpressionList argumentList = newExpression.getArgumentList(); + if (argumentList == null) { + return null; + } + final PsiExpression[] arguments = argumentList.getExpressions(); + if (arguments.length == 0) { + return "\"\""; + } + if (arguments.length != 1) { + return null; + } + final PsiExpression argument = arguments[0]; + final PsiType argumentType = argument.getType(); + if (PsiType.INT.equals(argumentType)) { + return "\"\""; + } + return argument.getText(); } - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) expression; - String name = call.getMethodExpression().getReferenceName(); - PsiExpressionList argumentList = call.getArgumentList(); - if (name != null && name.startsWith("new") && argumentList.getExpressions().length == 0) { - PsiMethod method = call.resolveMethod(); - if (method != null && method.getParameterList().getParametersCount() == 0) { - PsiClass aClass = method.getContainingClass(); - if (aClass != null) { - String qualifiedName = aClass.getQualifiedName(); - if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { - return true; + + /** + * Checks that given expression initializes empty Collection or Map + * + * @param expression expression to check + * @return true if the expression is the empty Collection or Map initializer + */ + @Contract("null -> false") + public static boolean isEmptyCollectionInitializer(PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiNewExpression) { + PsiExpressionList argumentList = ((PsiNewExpression)expression).getArgumentList(); + if (argumentList != null && argumentList.getExpressions().length == 0) { + PsiType type = expression.getType(); + return InheritanceUtil.isInheritor(type, JavaClassNames.JAVA_UTIL_COLLECTION) + || InheritanceUtil.isInheritor(type, JavaClassNames.JAVA_UTIL_MAP); } - } } - } + if (expression instanceof PsiMethodCallExpression) { + PsiMethodCallExpression call = (PsiMethodCallExpression)expression; + String name = call.getMethodExpression().getReferenceName(); + PsiExpressionList argumentList = call.getArgumentList(); + if (name != null && name.startsWith("new") && argumentList.getExpressions().length == 0) { + PsiMethod method = call.resolveMethod(); + if (method != null && method.getParameterList().getParametersCount() == 0) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null) { + String qualifiedName = aClass.getQualifiedName(); + if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { + return true; + } + } + } + } + } + return isCustomizedEmptyCollectionInitializer(expression); } - return isCustomizedEmptyCollectionInitializer(expression); - } - /** - * Checks that given expression initializes empty Collection or Map with custom initial capacity or load factor - * - * @param expression expression to check - * @return true if the expression is the empty Collection or Map initializer with custom initial capacity or load factor - */ - @Contract("null -> false") - public static boolean isCustomizedEmptyCollectionInitializer(PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiNewExpression) { - PsiExpressionList argumentList = ((PsiNewExpression) expression).getArgumentList(); - if (argumentList == null || argumentList.getExpressions().length == 0) { - return false; - } - PsiMethod constructor = ((PsiNewExpression) expression).resolveConstructor(); - if (constructor == null) { - return false; - } - PsiClass aClass = constructor.getContainingClass(); - if (aClass != null && (aClass.getQualifiedName() == null || !aClass.getQualifiedName().startsWith("java.util."))) { - return false; - } - if (!InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_COLLECTION) && !InheritanceUtil.isInheritor(aClass, JavaClassNames - .JAVA_UTIL_MAP)) { - return false; - } - Predicate allowedParameterType = t -> t instanceof PsiPrimitiveType || InheritanceUtil.isInheritor(t, JavaClassNames.JAVA_LANG_CLASS); - return Stream.of(constructor.getParameterList().getParameters()).map(PsiParameter::getType).allMatch(allowedParameterType); - } - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) expression; - if (ENUM_SET_NONE_OF.test(call)) { - return true; - } - String name = call.getMethodExpression().getReferenceName(); - PsiExpressionList argumentList = call.getArgumentList(); - if (name != null && name.startsWith("new") && argumentList.getExpressions().length > 0) { - PsiMethod method = call.resolveMethod(); - if (method != null && method.getParameterList().getParametersCount() > 0) { - PsiClass aClass = method.getContainingClass(); - if (aClass != null) { - String qualifiedName = aClass.getQualifiedName(); - if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { - return Stream.of(method.getParameterList().getParameters()).allMatch(p -> p.getType() instanceof PsiPrimitiveType); + /** + * Checks that given expression initializes empty Collection or Map with custom initial capacity or load factor + * + * @param expression expression to check + * @return true if the expression is the empty Collection or Map initializer with custom initial capacity or load factor + */ + @Contract("null -> false") + public static boolean isCustomizedEmptyCollectionInitializer(PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiNewExpression) { + PsiExpressionList argumentList = ((PsiNewExpression)expression).getArgumentList(); + if (argumentList == null || argumentList.getExpressions().length == 0) { + return false; + } + PsiMethod constructor = ((PsiNewExpression)expression).resolveConstructor(); + if (constructor == null) { + return false; + } + PsiClass aClass = constructor.getContainingClass(); + if (aClass != null && (aClass.getQualifiedName() == null || !aClass.getQualifiedName().startsWith("java.util."))) { + return false; + } + if (!InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_COLLECTION) + && !InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_MAP)) { + return false; + } + Predicate allowedParameterType = + t -> t instanceof PsiPrimitiveType || InheritanceUtil.isInheritor(t, JavaClassNames.JAVA_LANG_CLASS); + return Stream.of(constructor.getParameterList().getParameters()).map(PsiParameter::getType).allMatch(allowedParameterType); + } + if (expression instanceof PsiMethodCallExpression) { + PsiMethodCallExpression call = (PsiMethodCallExpression)expression; + if (ENUM_SET_NONE_OF.test(call)) { + return true; + } + String name = call.getMethodExpression().getReferenceName(); + PsiExpressionList argumentList = call.getArgumentList(); + if (name != null && name.startsWith("new") && argumentList.getExpressions().length > 0) { + PsiMethod method = call.resolveMethod(); + if (method != null && method.getParameterList().getParametersCount() > 0) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null) { + String qualifiedName = aClass.getQualifiedName(); + if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { + return Stream.of(method.getParameterList().getParameters()) + .allMatch(p -> p.getType() instanceof PsiPrimitiveType); + } + } + } } - } } - } + return false; } - return false; - } - public static boolean isPrepopulatedCollectionInitializer(PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiNewExpression) { - PsiExpressionList args = ((PsiNewExpression) expression).getArgumentList(); - if (args == null || args.isEmpty()) { - return false; - } - PsiMethod ctor = ((PsiNewExpression) expression).resolveMethod(); - if (ctor == null) { - return false; - } - PsiClass aClass = ctor.getContainingClass(); - if (aClass == null) { - return false; - } - String name = aClass.getQualifiedName(); - if (name == null || !name.startsWith("java.util.")) { - return false; - } - for (PsiParameter parameter : ctor.getParameterList().getParameters()) { - PsiType type = parameter.getType(); - if (type instanceof PsiClassType) { - PsiClassType rawType = ((PsiClassType) type).rawType(); - if (rawType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION) || - rawType.equalsToText(CommonClassNames.JAVA_UTIL_MAP)) { - return true; - } + public static boolean isPrepopulatedCollectionInitializer(PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiNewExpression) { + PsiExpressionList args = ((PsiNewExpression)expression).getArgumentList(); + if (args == null || args.isEmpty()) { + return false; + } + PsiMethod ctor = ((PsiNewExpression)expression).resolveMethod(); + if (ctor == null) { + return false; + } + PsiClass aClass = ctor.getContainingClass(); + if (aClass == null) { + return false; + } + String name = aClass.getQualifiedName(); + if (name == null || !name.startsWith("java.util.")) { + return false; + } + for (PsiParameter parameter : ctor.getParameterList().getParameters()) { + PsiType type = parameter.getType(); + if (type instanceof PsiClassType) { + PsiClassType rawType = ((PsiClassType)type).rawType(); + if (rawType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION) || + rawType.equalsToText(CommonClassNames.JAVA_UTIL_MAP)) { + return true; + } + } + } } - } - } - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) expression; - String name = call.getMethodExpression().getReferenceName(); - PsiExpressionList argumentList = call.getArgumentList(); - if (name != null && name.startsWith("new") && !argumentList.isEmpty()) { - PsiMethod method = call.resolveMethod(); - if (method == null) { - return false; + if (expression instanceof PsiMethodCallExpression) { + PsiMethodCallExpression call = (PsiMethodCallExpression)expression; + String name = call.getMethodExpression().getReferenceName(); + PsiExpressionList argumentList = call.getArgumentList(); + if (name != null && name.startsWith("new") && !argumentList.isEmpty()) { + PsiMethod method = call.resolveMethod(); + if (method == null) { + return false; + } + PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return false; + } + String qualifiedName = aClass.getQualifiedName(); + if (!GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { + return false; + } + for (PsiParameter parameter : method.getParameterList().getParameters()) { + PsiType type = parameter.getType(); + if (type instanceof PsiEllipsisType) { + return true; + } + if (type instanceof PsiClassType) { + PsiClassType rawType = ((PsiClassType)type).rawType(); + if (rawType.equalsToText(CommonClassNames.JAVA_LANG_ITERABLE) || + rawType.equalsToText(CommonClassNames.JAVA_UTIL_ITERATOR)) { + return true; + } + } + } + } } - PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return false; + return false; + } + + /** + * Returns true if given expression is an empty array initializer + * + * @param expression expression to test + * @return true if supplied expression is an empty array initializer + */ + public static boolean isEmptyArrayInitializer(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (!(expression instanceof PsiNewExpression)) { + return false; } - String qualifiedName = aClass.getQualifiedName(); - if (!GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { - return false; + final PsiNewExpression newExpression = (PsiNewExpression)expression; + final PsiExpression[] dimensions = newExpression.getArrayDimensions(); + if (dimensions.length == 0) { + final PsiArrayInitializerExpression arrayInitializer = newExpression.getArrayInitializer(); + if (arrayInitializer == null) { + return false; + } + final PsiExpression[] initializers = arrayInitializer.getInitializers(); + return initializers.length == 0; } - for (PsiParameter parameter : method.getParameterList().getParameters()) { - PsiType type = parameter.getType(); - if (type instanceof PsiEllipsisType) { - return true; - } - if (type instanceof PsiClassType) { - PsiClassType rawType = ((PsiClassType) type).rawType(); - if (rawType.equalsToText(CommonClassNames.JAVA_LANG_ITERABLE) || - rawType.equalsToText(CommonClassNames.JAVA_UTIL_ITERATOR)) { - return true; + for (PsiExpression dimension : dimensions) { + final String dimensionText = dimension.getText(); + if (!"0".equals(dimensionText)) { + return false; } - } } - } - } - return false; - } - - /** - * Returns true if given expression is an empty array initializer - * - * @param expression expression to test - * @return true if supplied expression is an empty array initializer - */ - public static boolean isEmptyArrayInitializer(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (!(expression instanceof PsiNewExpression)) { - return false; - } - final PsiNewExpression newExpression = (PsiNewExpression) expression; - final PsiExpression[] dimensions = newExpression.getArrayDimensions(); - if (dimensions.length == 0) { - final PsiArrayInitializerExpression arrayInitializer = newExpression.getArrayInitializer(); - if (arrayInitializer == null) { - return false; - } - final PsiExpression[] initializers = arrayInitializer.getInitializers(); - return initializers.length == 0; - } - for (PsiExpression dimension : dimensions) { - final String dimensionText = dimension.getText(); - if (!"0".equals(dimensionText)) { - return false; - } + return true; } - return true; - } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/JavaDeprecationUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/JavaDeprecationUtils.java index a54e006dc9..f58218f6bb 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/JavaDeprecationUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/JavaDeprecationUtils.java @@ -16,47 +16,53 @@ import jakarta.annotation.Nullable; public final class JavaDeprecationUtils { - @Nonnull - @RequiredReadAction - private static ThreeState isDeprecatedByAnnotation(@Nonnull PsiModifierListOwner owner, @Nullable PsiElement context) { - PsiAnnotation annotation = AnnotationUtil.findAnnotation(owner, CommonClassNames.JAVA_LANG_DEPRECATED); - if (annotation == null) return ThreeState.UNSURE; - if (context == null) return ThreeState.YES; - String since = null; - PsiAnnotationMemberValue value = annotation.findAttributeValue("since"); - if (value instanceof PsiLiteralExpression) { - since = ObjectUtil.tryCast(((PsiLiteralExpression)value).getValue(), String.class); + @Nonnull + @RequiredReadAction + private static ThreeState isDeprecatedByAnnotation(@Nonnull PsiModifierListOwner owner, @Nullable PsiElement context) { + PsiAnnotation annotation = AnnotationUtil.findAnnotation(owner, CommonClassNames.JAVA_LANG_DEPRECATED); + if (annotation == null) { + return ThreeState.UNSURE; + } + if (context == null) { + return ThreeState.YES; + } + String since = null; + PsiAnnotationMemberValue value = annotation.findAttributeValue("since"); + if (value instanceof PsiLiteralExpression) { + since = ObjectUtil.tryCast(((PsiLiteralExpression)value).getValue(), String.class); + } + if (since == null || ModuleUtilCore.getSdk(owner, JavaModuleExtension.class) == null) { + return ThreeState.YES; + } + LanguageLevel deprecationLevel = LanguageLevel.parse(since); + return ThreeState.fromBoolean(deprecationLevel == null || PsiUtil.getLanguageLevel(context).isAtLeast(deprecationLevel)); } - if (since == null || ModuleUtilCore.getSdk(owner, JavaModuleExtension.class) == null) return ThreeState.YES; - LanguageLevel deprecationLevel = LanguageLevel.parse(since); - return ThreeState.fromBoolean(deprecationLevel == null || PsiUtil.getLanguageLevel(context).isAtLeast(deprecationLevel)); - } - /** - * Checks if the given PSI element is deprecated with annotation or JavaDoc tag, taking the context into account. - *
- * It is suitable for elements other than {@link PsiDocCommentOwner}. - * The deprecation of JDK members may depend on context. E.g., uses if a JDK method is deprecated since Java 19, - * but current module has Java 17 target, than the method is not considered as deprecated. - * - * @param psiElement element to check whether it's deprecated - * @param context context in which the check should be performed - */ - @RequiredReadAction - public static boolean isDeprecated(@Nonnull PsiElement psiElement, @Nullable PsiElement context) { - if (psiElement instanceof PsiModifierListOwner) { - ThreeState byAnnotation = isDeprecatedByAnnotation((PsiModifierListOwner)psiElement, context); - if (byAnnotation != ThreeState.UNSURE) { - return byAnnotation.toBoolean(); - } + /** + * Checks if the given PSI element is deprecated with annotation or JavaDoc tag, taking the context into account. + *
+ * It is suitable for elements other than {@link PsiDocCommentOwner}. + * The deprecation of JDK members may depend on context. E.g., uses if a JDK method is deprecated since Java 19, + * but current module has Java 17 target, than the method is not considered as deprecated. + * + * @param psiElement element to check whether it's deprecated + * @param context context in which the check should be performed + */ + @RequiredReadAction + public static boolean isDeprecated(@Nonnull PsiElement psiElement, @Nullable PsiElement context) { + if (psiElement instanceof PsiModifierListOwner) { + ThreeState byAnnotation = isDeprecatedByAnnotation((PsiModifierListOwner)psiElement, context); + if (byAnnotation != ThreeState.UNSURE) { + return byAnnotation.toBoolean(); + } + } + if (psiElement instanceof PsiDocCommentOwner) { + return ((PsiDocCommentOwner)psiElement).isDeprecated(); + } + if (psiElement instanceof PsiJavaDocumentedElement) { + return PsiImplUtil.isDeprecatedByDocTag((PsiJavaDocumentedElement)psiElement); + } + return false; } - if (psiElement instanceof PsiDocCommentOwner) { - return ((PsiDocCommentOwner)psiElement).isDeprecated(); - } - if (psiElement instanceof PsiJavaDocumentedElement) { - return PsiImplUtil.isDeprecatedByDocTag((PsiJavaDocumentedElement)psiElement); - } - return false; - } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/SideEffectChecker.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/SideEffectChecker.java index 2760d61ccc..6ffd6ad2dd 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/SideEffectChecker.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/SideEffectChecker.java @@ -33,311 +33,321 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; import java.util.function.Predicate; import static consulo.util.lang.ObjectUtil.tryCast; public class SideEffectChecker { - private static final Set ourSideEffectFreeClasses = new HashSet<>(Arrays.asList( - Object.class.getName(), - Short.class.getName(), - Character.class.getName(), - Byte.class.getName(), - Integer.class.getName(), - Long.class.getName(), - Float.class.getName(), - Double.class.getName(), - String.class.getName(), - StringBuffer.class.getName(), - Boolean.class.getName(), - - ArrayList.class.getName(), - Date.class.getName(), - HashMap.class.getName(), - HashSet.class.getName(), - Hashtable.class.getName(), - LinkedHashMap.class.getName(), - LinkedHashSet.class.getName(), - LinkedList.class.getName(), - Stack.class.getName(), - TreeMap.class.getName(), - TreeSet.class.getName(), - Vector.class.getName(), - WeakHashMap.class.getName())); - - private SideEffectChecker() { - } - - public static boolean mayHaveSideEffects(@Nonnull PsiExpression exp) { - final SideEffectsVisitor visitor = new SideEffectsVisitor(null, exp); - exp.accept(visitor); - return visitor.mayHaveSideEffects(); - } - - public static boolean mayHaveSideEffects(@Nonnull PsiElement element, Predicate shouldIgnoreElement) { - final SideEffectsVisitor visitor = new SideEffectsVisitor(null, element, shouldIgnoreElement); - element.accept(visitor); - return visitor.mayHaveSideEffects(); - } - - /** - * Returns true if element execution may cause non-local side-effect. Side-effects like control flow within method; throw/return or - * local variable declaration or update are considered as local side-effects. - * - * @param element element to check - * @return true if element execution may cause non-local side-effect. - */ - public static boolean mayHaveNonLocalSideEffects(@Nonnull PsiElement element) { - return mayHaveSideEffects(element, SideEffectChecker::isLocalSideEffect); - } - - private static boolean isLocalSideEffect(PsiElement e) { - if (e instanceof PsiContinueStatement || - e instanceof PsiReturnStatement || - e instanceof PsiThrowStatement) { - return true; - } - if (e instanceof PsiLocalVariable) { - return true; + private static final Set ourSideEffectFreeClasses = new HashSet<>(Arrays.asList( + Object.class.getName(), + Short.class.getName(), + Character.class.getName(), + Byte.class.getName(), + Integer.class.getName(), + Long.class.getName(), + Float.class.getName(), + Double.class.getName(), + String.class.getName(), + StringBuffer.class.getName(), + Boolean.class.getName(), + + ArrayList.class.getName(), + Date.class.getName(), + HashMap.class.getName(), + HashSet.class.getName(), + Hashtable.class.getName(), + LinkedHashMap.class.getName(), + LinkedHashSet.class.getName(), + LinkedList.class.getName(), + Stack.class.getName(), + TreeMap.class.getName(), + TreeSet.class.getName(), + Vector.class.getName(), + WeakHashMap.class.getName() + )); + + private SideEffectChecker() { } - PsiReferenceExpression ref = null; - if (e instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assignment = (PsiAssignmentExpression) e; - ref = tryCast(PsiUtil.skipParenthesizedExprDown(assignment.getLExpression()), PsiReferenceExpression.class); - } - if (e instanceof PsiUnaryExpression) { - PsiExpression operand = ((PsiUnaryExpression) e).getOperand(); - ref = tryCast(PsiUtil.skipParenthesizedExprDown(operand), PsiReferenceExpression.class); - } - if (ref != null) { - PsiElement target = ref.resolve(); - if (target instanceof PsiLocalVariable || target instanceof PsiParameter) { - return true; - } + public static boolean mayHaveSideEffects(@Nonnull PsiExpression exp) { + final SideEffectsVisitor visitor = new SideEffectsVisitor(null, exp); + exp.accept(visitor); + return visitor.mayHaveSideEffects(); } - return false; - } - - public static boolean checkSideEffects(@Nonnull PsiExpression element, @Nullable List sideEffects) { - return checkSideEffects(element, sideEffects, e -> false); - } - - public static boolean checkSideEffects(@Nonnull PsiExpression element, - @Nullable List sideEffects, - @Nonnull Predicate ignoreElement) { - final SideEffectsVisitor visitor = new SideEffectsVisitor(sideEffects, element, ignoreElement); - element.accept(visitor); - return visitor.mayHaveSideEffects(); - } - - public static List extractSideEffectExpressions(@Nonnull PsiExpression element) { - List list = new SmartList<>(); - element.accept(new SideEffectsVisitor(list, element)); - return StreamEx.of(list).select(PsiExpression.class).toList(); - } - - private static class SideEffectsVisitor extends JavaRecursiveElementWalkingVisitor { - private final - @Nullable - List mySideEffects; - private final - @Nonnull - PsiElement myStartElement; - private final - @Nonnull - Predicate myIgnorePredicate; - boolean found; - - SideEffectsVisitor(@Nullable List sideEffects, @Nonnull PsiElement startElement) { - this(sideEffects, startElement, call -> false); + + public static boolean mayHaveSideEffects(@Nonnull PsiElement element, Predicate shouldIgnoreElement) { + final SideEffectsVisitor visitor = new SideEffectsVisitor(null, element, shouldIgnoreElement); + element.accept(visitor); + return visitor.mayHaveSideEffects(); } - SideEffectsVisitor(@Nullable List sideEffects, @Nonnull PsiElement startElement, @Nonnull Predicate predicate) { - myStartElement = startElement; - myIgnorePredicate = predicate; - mySideEffects = sideEffects; + /** + * Returns true if element execution may cause non-local side-effect. Side-effects like control flow within method; throw/return or + * local variable declaration or update are considered as local side-effects. + * + * @param element element to check + * @return true if element execution may cause non-local side-effect. + */ + public static boolean mayHaveNonLocalSideEffects(@Nonnull PsiElement element) { + return mayHaveSideEffects(element, SideEffectChecker::isLocalSideEffect); } - private boolean addSideEffect(PsiElement element) { - if (myIgnorePredicate.test(element)) { + private static boolean isLocalSideEffect(PsiElement e) { + if (e instanceof PsiContinueStatement || + e instanceof PsiReturnStatement || + e instanceof PsiThrowStatement) { + return true; + } + if (e instanceof PsiLocalVariable) { + return true; + } + + PsiReferenceExpression ref = null; + if (e instanceof PsiAssignmentExpression) { + PsiAssignmentExpression assignment = (PsiAssignmentExpression)e; + ref = tryCast(PsiUtil.skipParenthesizedExprDown(assignment.getLExpression()), PsiReferenceExpression.class); + } + if (e instanceof PsiUnaryExpression) { + PsiExpression operand = ((PsiUnaryExpression)e).getOperand(); + ref = tryCast(PsiUtil.skipParenthesizedExprDown(operand), PsiReferenceExpression.class); + } + if (ref != null) { + PsiElement target = ref.resolve(); + if (target instanceof PsiLocalVariable || target instanceof PsiParameter) { + return true; + } + } return false; - } - found = true; - if (mySideEffects != null) { - mySideEffects.add(element); - } else { - stopWalking(); - } - return true; } - @Override - public void visitAssignmentExpression(@Nonnull PsiAssignmentExpression expression) { - if (addSideEffect(expression)) { - return; - } - super.visitAssignmentExpression(expression); + public static boolean checkSideEffects(@Nonnull PsiExpression element, @Nullable List sideEffects) { + return checkSideEffects(element, sideEffects, e -> false); } - @Override - public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression expression) { - final PsiMethod method = expression.resolveMethod(); - if (!isPure(method)) { - if (addSideEffect(expression)) { - return; - } - } - super.visitMethodCallExpression(expression); + public static boolean checkSideEffects( + @Nonnull PsiExpression element, + @Nullable List sideEffects, + @Nonnull Predicate ignoreElement + ) { + final SideEffectsVisitor visitor = new SideEffectsVisitor(sideEffects, element, ignoreElement); + element.accept(visitor); + return visitor.mayHaveSideEffects(); } - protected boolean isPure(PsiMethod method) { - if (method == null) { - return false; - } - PsiField field = PropertyUtil.getFieldOfGetter(method); - if (field != null) { - return !field.hasModifierProperty(PsiModifier.VOLATILE); - } - return JavaMethodContractUtil.isPure(method) && !mayHaveExceptionalSideEffect(method); + public static List extractSideEffectExpressions(@Nonnull PsiExpression element) { + List list = new SmartList<>(); + element.accept(new SideEffectsVisitor(list, element)); + return StreamEx.of(list).select(PsiExpression.class).toList(); } - @Override - public void visitNewExpression(@Nonnull PsiNewExpression expression) { - if (!expression.isArrayCreation() && !isSideEffectFreeConstructor(expression)) { - if (addSideEffect(expression)) { - return; + private static class SideEffectsVisitor extends JavaRecursiveElementWalkingVisitor { + private final + @Nullable + List mySideEffects; + private final + @Nonnull + PsiElement myStartElement; + private final + @Nonnull + Predicate myIgnorePredicate; + boolean found; + + SideEffectsVisitor(@Nullable List sideEffects, @Nonnull PsiElement startElement) { + this(sideEffects, startElement, call -> false); } - } - super.visitNewExpression(expression); - } - @Override - public void visitUnaryExpression(@Nonnull PsiUnaryExpression expression) { - final IElementType tokenType = expression.getOperationTokenType(); - if (tokenType.equals(JavaTokenType.PLUSPLUS) || tokenType.equals(JavaTokenType.MINUSMINUS)) { - if (addSideEffect(expression)) { - return; + SideEffectsVisitor( + @Nullable List sideEffects, + @Nonnull PsiElement startElement, + @Nonnull Predicate predicate + ) { + myStartElement = startElement; + myIgnorePredicate = predicate; + mySideEffects = sideEffects; } - } - super.visitUnaryExpression(expression); - } - @Override - public void visitVariable(PsiVariable variable) { - if (addSideEffect(variable)) { - return; - } - super.visitVariable(variable); - } + private boolean addSideEffect(PsiElement element) { + if (myIgnorePredicate.test(element)) { + return false; + } + found = true; + if (mySideEffects != null) { + mySideEffects.add(element); + } + else { + stopWalking(); + } + return true; + } - @Override - public void visitBreakStatement(PsiBreakStatement statement) { - PsiStatement exitedStatement = statement.findExitedStatement(); - if (exitedStatement == null || !PsiTreeUtil.isAncestor(myStartElement, exitedStatement, false)) { - if (addSideEffect(statement)) { - return; + @Override + public void visitAssignmentExpression(@Nonnull PsiAssignmentExpression expression) { + if (addSideEffect(expression)) { + return; + } + super.visitAssignmentExpression(expression); } - } - super.visitBreakStatement(statement); - } - @Override - public void visitClass(PsiClass aClass) { - // local or anonymous class declaration is not side effect per se (unless it's instantiated) - } + @Override + public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression expression) { + final PsiMethod method = expression.resolveMethod(); + if (!isPure(method)) { + if (addSideEffect(expression)) { + return; + } + } + super.visitMethodCallExpression(expression); + } - @Override - public void visitContinueStatement(PsiContinueStatement statement) { - PsiStatement exitedStatement = statement.findContinuedStatement(); - if (exitedStatement != null && PsiTreeUtil.isAncestor(myStartElement, exitedStatement, false)) { - return; - } - if (addSideEffect(statement)) { - return; - } - super.visitContinueStatement(statement); - } + protected boolean isPure(PsiMethod method) { + if (method == null) { + return false; + } + PsiField field = PropertyUtil.getFieldOfGetter(method); + if (field != null) { + return !field.hasModifierProperty(PsiModifier.VOLATILE); + } + return JavaMethodContractUtil.isPure(method) && !mayHaveExceptionalSideEffect(method); + } - @Override - public void visitReturnStatement(PsiReturnStatement statement) { - if (addSideEffect(statement)) { - return; - } - super.visitReturnStatement(statement); - } + @Override + public void visitNewExpression(@Nonnull PsiNewExpression expression) { + if (!expression.isArrayCreation() && !isSideEffectFreeConstructor(expression)) { + if (addSideEffect(expression)) { + return; + } + } + super.visitNewExpression(expression); + } - @Override - public void visitThrowStatement(PsiThrowStatement statement) { - if (addSideEffect(statement)) { - return; - } - super.visitThrowStatement(statement); - } + @Override + public void visitUnaryExpression(@Nonnull PsiUnaryExpression expression) { + final IElementType tokenType = expression.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.PLUSPLUS) || tokenType.equals(JavaTokenType.MINUSMINUS)) { + if (addSideEffect(expression)) { + return; + } + } + super.visitUnaryExpression(expression); + } - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - // lambda is not side effect per se (unless it's called) - } + @Override + public void visitVariable(PsiVariable variable) { + if (addSideEffect(variable)) { + return; + } + super.visitVariable(variable); + } - public boolean mayHaveSideEffects() { - return found; - } - } - - /** - * Returns true if given method function is likely to throw an exception (e.g. "assertEquals"). In some cases this means that - * the method call should be preserved in source code even if it's pure (i.e. does not change the program state). - * - * @param method a method to check - * @return true if the method has exceptional side effect - */ - public static boolean mayHaveExceptionalSideEffect(PsiMethod method) { - String name = method.getName(); - if (name.startsWith("assert") || name.startsWith("check") || name.startsWith("require")) { - return true; - } - PsiClass aClass = method.getContainingClass(); - if (InheritanceUtil.isInheritor(aClass, "org.assertj.core.api.Descriptable")) { - // See com.intellij.codeInsight.DefaultInferredAnnotationProvider#getHardcodedContractAnnotation - return true; - } - return JavaMethodContractUtil.getMethodCallContracts(method, null).stream() - .filter(mc -> mc.getConditions().stream().noneMatch(ContractValue::isBoundCheckingCondition)) - .anyMatch(mc -> mc.getReturnValue().isFail()); - } - - private static boolean isSideEffectFreeConstructor(@Nonnull PsiNewExpression newExpression) { - PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); - if (anonymousClass != null && anonymousClass.getInitializers().length == 0) { - PsiClass baseClass = anonymousClass.getBaseClassType().resolve(); - if (baseClass != null && baseClass.isInterface()) { - return true; - } - } - PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); - PsiClass aClass = classReference == null ? null : (PsiClass) classReference.resolve(); - String qualifiedName = aClass == null ? null : aClass.getQualifiedName(); - if (qualifiedName == null) { - return false; + @Override + public void visitBreakStatement(PsiBreakStatement statement) { + PsiStatement exitedStatement = statement.findExitedStatement(); + if (exitedStatement == null || !PsiTreeUtil.isAncestor(myStartElement, exitedStatement, false)) { + if (addSideEffect(statement)) { + return; + } + } + super.visitBreakStatement(statement); + } + + @Override + public void visitClass(PsiClass aClass) { + // local or anonymous class declaration is not side effect per se (unless it's instantiated) + } + + @Override + public void visitContinueStatement(PsiContinueStatement statement) { + PsiStatement exitedStatement = statement.findContinuedStatement(); + if (exitedStatement != null && PsiTreeUtil.isAncestor(myStartElement, exitedStatement, false)) { + return; + } + if (addSideEffect(statement)) { + return; + } + super.visitContinueStatement(statement); + } + + @Override + public void visitReturnStatement(PsiReturnStatement statement) { + if (addSideEffect(statement)) { + return; + } + super.visitReturnStatement(statement); + } + + @Override + public void visitThrowStatement(PsiThrowStatement statement) { + if (addSideEffect(statement)) { + return; + } + super.visitThrowStatement(statement); + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + // lambda is not side effect per se (unless it's called) + } + + public boolean mayHaveSideEffects() { + return found; + } } - if (ourSideEffectFreeClasses.contains(qualifiedName)) { - return true; + + /** + * Returns true if given method function is likely to throw an exception (e.g. "assertEquals"). In some cases this means that + * the method call should be preserved in source code even if it's pure (i.e. does not change the program state). + * + * @param method a method to check + * @return true if the method has exceptional side effect + */ + public static boolean mayHaveExceptionalSideEffect(PsiMethod method) { + String name = method.getName(); + if (name.startsWith("assert") || name.startsWith("check") || name.startsWith("require")) { + return true; + } + PsiClass aClass = method.getContainingClass(); + if (InheritanceUtil.isInheritor(aClass, "org.assertj.core.api.Descriptable")) { + // See com.intellij.codeInsight.DefaultInferredAnnotationProvider#getHardcodedContractAnnotation + return true; + } + return JavaMethodContractUtil.getMethodCallContracts(method, null).stream() + .filter(mc -> mc.getConditions().stream().noneMatch(ContractValue::isBoundCheckingCondition)) + .anyMatch(mc -> mc.getReturnValue().isFail()); } - PsiFile file = aClass.getContainingFile(); - PsiDirectory directory = file.getContainingDirectory(); - PsiPackage classPackage = directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory); - String packageName = classPackage == null ? null : classPackage.getQualifiedName(); - - // all Throwable descendants from java.lang are side effects free - if (CommonClassNames.DEFAULT_PACKAGE.equals(packageName) || "java.io".equals(packageName)) { - PsiClass throwableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass(JavaClassNames.JAVA_LANG_THROWABLE, aClass.getResolveScope()); - if (throwableClass != null && InheritanceUtil.isInheritorOrSelf(aClass, throwableClass, true)) { - return true; - } + private static boolean isSideEffectFreeConstructor(@Nonnull PsiNewExpression newExpression) { + PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); + if (anonymousClass != null && anonymousClass.getInitializers().length == 0) { + PsiClass baseClass = anonymousClass.getBaseClassType().resolve(); + if (baseClass != null && baseClass.isInterface()) { + return true; + } + } + PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); + PsiClass aClass = classReference == null ? null : (PsiClass)classReference.resolve(); + String qualifiedName = aClass == null ? null : aClass.getQualifiedName(); + if (qualifiedName == null) { + return false; + } + if (ourSideEffectFreeClasses.contains(qualifiedName)) { + return true; + } + + PsiFile file = aClass.getContainingFile(); + PsiDirectory directory = file.getContainingDirectory(); + PsiPackage classPackage = directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory); + String packageName = classPackage == null ? null : classPackage.getQualifiedName(); + + // all Throwable descendants from java.lang are side effects free + if (CommonClassNames.DEFAULT_PACKAGE.equals(packageName) || "java.io".equals(packageName)) { + PsiClass throwableClass = + JavaPsiFacade.getInstance(aClass.getProject()).findClass(JavaClassNames.JAVA_LANG_THROWABLE, aClass.getResolveScope()); + if (throwableClass != null && InheritanceUtil.isInheritorOrSelf(aClass, throwableClass, true)) { + return true; + } + } + return false; } - return false; - } } diff --git a/java-language-api/src/main/java/com/intellij/java/language/psi/util/PropertyUtilBase.java b/java-language-api/src/main/java/com/intellij/java/language/psi/util/PropertyUtilBase.java index ad66b278da..ac3a68ef21 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/psi/util/PropertyUtilBase.java +++ b/java-language-api/src/main/java/com/intellij/java/language/psi/util/PropertyUtilBase.java @@ -20,687 +20,745 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import kava.beans.Introspector; + import java.util.*; public class PropertyUtilBase { - @NonNls - protected static final String GET_PREFIX = PropertyKind.GETTER.prefix; - @NonNls - protected static final String IS_PREFIX = PropertyKind.BOOLEAN_GETTER.prefix; - @Nonnull - protected static final String SET_PREFIX = PropertyKind.SETTER.prefix; - - @Nullable - public static String getPropertyName(@NonNls @Nonnull String methodName) { - return StringUtil.getPropertyName(methodName); - } - - @Nonnull - public static Map getAllProperties(@Nonnull final PsiClass psiClass, - final boolean acceptSetters, - final boolean acceptGetters) { - return getAllProperties(psiClass, acceptSetters, acceptGetters, true); - } - - @Nonnull - public static Map getAllProperties(@Nonnull final PsiClass psiClass, - final boolean acceptSetters, - final boolean acceptGetters, - final boolean includeSuperClass) { - return getAllProperties(acceptSetters, acceptGetters, includeSuperClass ? psiClass.getAllMethods() : psiClass.getMethods()); - } - - @Nonnull - public static Map getAllProperties(final boolean acceptSetters, - final boolean acceptGetters, PsiMethod[] methods) { - final Map map = new HashMap<>(); - - for (PsiMethod method : methods) { - if (filterMethods(method)) - continue; - if (acceptSetters && isSimplePropertySetter(method) || - acceptGetters && isSimplePropertyGetter(method)) { - map.put(getPropertyName(method), method); - } - } - return map; - } - - - private static boolean filterMethods(final PsiMethod method) { - if (method.hasModifierProperty(PsiModifier.STATIC) || !method.hasModifierProperty(PsiModifier.PUBLIC)) - return true; - - PsiClass psiClass = method.getContainingClass(); - if (psiClass == null) - return false; - final String className = psiClass.getQualifiedName(); - return CommonClassNames.JAVA_LANG_OBJECT.equals(className); - } - - @Nonnull - public static List getSetters(@Nonnull final PsiClass psiClass, final String propertyName) { - final String setterName = suggestSetterName(propertyName); - final PsiMethod[] psiMethods = psiClass.findMethodsByName(setterName, true); - final ArrayList list = new ArrayList<>(psiMethods.length); - for (PsiMethod method : psiMethods) { - if (filterMethods(method)) - continue; - if (isSimplePropertySetter(method)) { - list.add(method); - } - } - return list; - } - - @Nonnull - public static List getGetters(@Nonnull final PsiClass psiClass, final String propertyName) { - final String[] names = suggestGetterNames(propertyName); - final ArrayList list = new ArrayList<>(); - for (String name : names) { - final PsiMethod[] psiMethods = psiClass.findMethodsByName(name, true); - for (PsiMethod method : psiMethods) { - if (filterMethods(method)) - continue; - if (isSimplePropertyGetter(method)) { - list.add(method); + @NonNls + protected static final String GET_PREFIX = PropertyKind.GETTER.prefix; + @NonNls + protected static final String IS_PREFIX = PropertyKind.BOOLEAN_GETTER.prefix; + @Nonnull + protected static final String SET_PREFIX = PropertyKind.SETTER.prefix; + + @Nullable + public static String getPropertyName(@NonNls @Nonnull String methodName) { + return StringUtil.getPropertyName(methodName); + } + + @Nonnull + public static Map getAllProperties( + @Nonnull final PsiClass psiClass, + final boolean acceptSetters, + final boolean acceptGetters + ) { + return getAllProperties(psiClass, acceptSetters, acceptGetters, true); + } + + @Nonnull + public static Map getAllProperties( + @Nonnull final PsiClass psiClass, + final boolean acceptSetters, + final boolean acceptGetters, + final boolean includeSuperClass + ) { + return getAllProperties(acceptSetters, acceptGetters, includeSuperClass ? psiClass.getAllMethods() : psiClass.getMethods()); + } + + @Nonnull + public static Map getAllProperties( + final boolean acceptSetters, + final boolean acceptGetters, PsiMethod[] methods + ) { + final Map map = new HashMap<>(); + + for (PsiMethod method : methods) { + if (filterMethods(method)) { + continue; + } + if (acceptSetters && isSimplePropertySetter(method) || + acceptGetters && isSimplePropertyGetter(method)) { + map.put(getPropertyName(method), method); + } + } + return map; + } + + + private static boolean filterMethods(final PsiMethod method) { + if (method.hasModifierProperty(PsiModifier.STATIC) || !method.hasModifierProperty(PsiModifier.PUBLIC)) { + return true; + } + + PsiClass psiClass = method.getContainingClass(); + if (psiClass == null) { + return false; + } + final String className = psiClass.getQualifiedName(); + return CommonClassNames.JAVA_LANG_OBJECT.equals(className); + } + + @Nonnull + public static List getSetters(@Nonnull final PsiClass psiClass, final String propertyName) { + final String setterName = suggestSetterName(propertyName); + final PsiMethod[] psiMethods = psiClass.findMethodsByName(setterName, true); + final ArrayList list = new ArrayList<>(psiMethods.length); + for (PsiMethod method : psiMethods) { + if (filterMethods(method)) { + continue; + } + if (isSimplePropertySetter(method)) { + list.add(method); + } + } + return list; + } + + @Nonnull + public static List getGetters(@Nonnull final PsiClass psiClass, final String propertyName) { + final String[] names = suggestGetterNames(propertyName); + final ArrayList list = new ArrayList<>(); + for (String name : names) { + final PsiMethod[] psiMethods = psiClass.findMethodsByName(name, true); + for (PsiMethod method : psiMethods) { + if (filterMethods(method)) { + continue; + } + if (isSimplePropertyGetter(method)) { + list.add(method); + } + } + } + return list; + } + + @Nonnull + public static List getAccessors(@Nonnull final PsiClass psiClass, final String propertyName) { + return ContainerUtil.concat(getGetters(psiClass, propertyName), getSetters(psiClass, propertyName)); + } + + @Nonnull + public static String[] getReadableProperties(@Nonnull PsiClass aClass, boolean includeSuperClass) { + List result = new ArrayList<>(); + + PsiMethod[] methods = includeSuperClass ? aClass.getAllMethods() : aClass.getMethods(); + + for (PsiMethod method : methods) { + if (CommonClassNames.JAVA_LANG_OBJECT.equals(method.getContainingClass().getQualifiedName())) { + continue; + } + + if (isSimplePropertyGetter(method)) { + result.add(getPropertyName(method)); + } } - } + + return ArrayUtil.toStringArray(result); } - return list; - } - @Nonnull - public static List getAccessors(@Nonnull final PsiClass psiClass, final String propertyName) { - return ContainerUtil.concat(getGetters(psiClass, propertyName), getSetters(psiClass, propertyName)); - } + @Nonnull + public static String[] getWritableProperties(@Nonnull PsiClass aClass, boolean includeSuperClass) { + List result = new ArrayList<>(); - @Nonnull - public static String[] getReadableProperties(@Nonnull PsiClass aClass, boolean includeSuperClass) { - List result = new ArrayList<>(); + PsiMethod[] methods = includeSuperClass ? aClass.getAllMethods() : aClass.getMethods(); - PsiMethod[] methods = includeSuperClass ? aClass.getAllMethods() : aClass.getMethods(); + for (PsiMethod method : methods) { + if (CommonClassNames.JAVA_LANG_OBJECT.equals(method.getContainingClass().getQualifiedName())) { + continue; + } + + if (isSimplePropertySetter(method)) { + result.add(getPropertyName(method)); + } + } + + return ArrayUtil.toStringArray(result); + } - for (PsiMethod method : methods) { - if (CommonClassNames.JAVA_LANG_OBJECT.equals(method.getContainingClass().getQualifiedName())) - continue; + @Nullable + public static PsiType getPropertyType(final PsiMember member) { + if (member instanceof PsiField) { + return ((PsiField)member).getType(); + } + if (member instanceof PsiMethod) { + final PsiMethod psiMethod = (PsiMethod)member; + if (isSimplePropertyGetter(psiMethod)) { + return psiMethod.getReturnType(); + } + else if (isSimplePropertySetter(psiMethod)) { + return psiMethod.getParameterList().getParameters()[0].getType(); + } + } + return null; + } + + + @Nullable + public static PsiMethod findPropertySetter( + PsiClass aClass, + @Nonnull String propertyName, + boolean isStatic, + boolean checkSuperClasses + ) { + if (aClass == null) { + return null; + } + String setterName = suggestSetterName(propertyName); + PsiMethod[] methods = aClass.findMethodsByName(setterName, checkSuperClasses); + + for (PsiMethod method : methods) { + if (method.hasModifierProperty(PsiModifier.STATIC) != isStatic) { + continue; + } + + if (isSimplePropertySetter(method)) { + if (getPropertyNameBySetter(method).equals(propertyName)) { + return method; + } + } + } - if (isSimplePropertyGetter(method)) { - result.add(getPropertyName(method)); - } + return null; } - return ArrayUtil.toStringArray(result); - } + @Nullable + public static PsiField findPropertyField(PsiClass aClass, String propertyName, boolean isStatic) { + PsiField[] fields = aClass.getAllFields(); - @Nonnull - public static String[] getWritableProperties(@Nonnull PsiClass aClass, boolean includeSuperClass) { - List result = new ArrayList<>(); + for (PsiField field : fields) { + if (field.hasModifierProperty(PsiModifier.STATIC) != isStatic) { + continue; + } + if (propertyName.equals(suggestPropertyName(field))) { + return field; + } + } - PsiMethod[] methods = includeSuperClass ? aClass.getAllMethods() : aClass.getMethods(); + return null; + } - for (PsiMethod method : methods) { - if (CommonClassNames.JAVA_LANG_OBJECT.equals(method.getContainingClass().getQualifiedName())) - continue; + @Nullable + public static PsiMethod findPropertyGetter( + PsiClass aClass, + @Nonnull String propertyName, + boolean isStatic, + boolean checkSuperClasses + ) { + if (aClass == null) { + return null; + } + String[] getterCandidateNames = suggestGetterNames(propertyName); + + for (String getterCandidateName : getterCandidateNames) { + PsiMethod[] getterCandidates = aClass.findMethodsByName(getterCandidateName, checkSuperClasses); + for (PsiMethod method : getterCandidates) { + if (method.hasModifierProperty(PsiModifier.STATIC) != isStatic) { + continue; + } + + if (isSimplePropertyGetter(method)) { + if (getPropertyNameByGetter(method).equals(propertyName)) { + return method; + } + } + } + } - if (isSimplePropertySetter(method)) { - result.add(getPropertyName(method)); - } + return null; + } + + @Nullable + public static PsiMethod findPropertyGetterWithType( + String propertyName, + boolean isStatic, + PsiType type, + Iterator methods + ) { + while (methods.hasNext()) { + PsiMethod method = methods.next(); + if (method.hasModifierProperty(PsiModifier.STATIC) != isStatic) { + continue; + } + if (isSimplePropertyGetter(method)) { + if (getPropertyNameByGetter(method).equals(propertyName)) { + if (type.equals(method.getReturnType())) { + return method; + } + } + } + } + return null; + } + + public static boolean isSimplePropertyAccessor(PsiMethod method) { + return isSimplePropertyGetter(method) || isSimplePropertySetter(method); + } + + @Nullable + public static PsiMethod findPropertySetterWithType( + String propertyName, + boolean isStatic, + PsiType type, + Iterator methods + ) { + while (methods.hasNext()) { + PsiMethod method = methods.next(); + if (method.hasModifierProperty(PsiModifier.STATIC) != isStatic) { + continue; + } + + if (isSimplePropertySetter(method)) { + if (getPropertyNameBySetter(method).equals(propertyName)) { + PsiType methodType = method.getParameterList().getParameters()[0].getType(); + if (type.equals(methodType)) { + return method; + } + } + } + } + return null; } - return ArrayUtil.toStringArray(result); - } + public enum GetterFlavour { + BOOLEAN, + GENERIC, + NOT_A_GETTER + } - @Nullable - public static PsiType getPropertyType(final PsiMember member) { - if (member instanceof PsiField) { - return ((PsiField) member).getType(); + @Nonnull + public static GetterFlavour getMethodNameGetterFlavour(@Nonnull String methodName) { + if (checkPrefix(methodName, GET_PREFIX)) { + return GetterFlavour.GENERIC; + } + else if (checkPrefix(methodName, IS_PREFIX)) { + return GetterFlavour.BOOLEAN; + } + return GetterFlavour.NOT_A_GETTER; } - if (member instanceof PsiMethod) { - final PsiMethod psiMethod = (PsiMethod) member; - if (isSimplePropertyGetter(psiMethod)) { - return psiMethod.getReturnType(); - } else if (isSimplePropertySetter(psiMethod)) { - return psiMethod.getParameterList().getParameters()[0].getType(); - } + + + @Contract("null -> false") + public static boolean isSimplePropertyGetter(@Nullable PsiMethod method) { + return hasGetterName(method) && method.getParameterList().isEmpty(); } - return null; - } - @Nullable - public static PsiMethod findPropertySetter(PsiClass aClass, - @Nonnull String propertyName, - boolean isStatic, - boolean checkSuperClasses) { - if (aClass == null) - return null; - String setterName = suggestSetterName(propertyName); - PsiMethod[] methods = aClass.findMethodsByName(setterName, checkSuperClasses); + public static boolean hasGetterName(final PsiMethod method) { + if (method == null) { + return false; + } - for (PsiMethod method : methods) { - if (method.hasModifierProperty(PsiModifier.STATIC) != isStatic) - continue; + if (method.isConstructor()) { + return false; + } - if (isSimplePropertySetter(method)) { - if (getPropertyNameBySetter(method).equals(propertyName)) { - return method; + String methodName = method.getName(); + GetterFlavour flavour = getMethodNameGetterFlavour(methodName); + switch (flavour) { + case GENERIC: + PsiType returnType = method.getReturnType(); + return returnType == null || !PsiType.VOID.equals(returnType); + case BOOLEAN: + return isBoolean(method.getReturnType()); + case NOT_A_GETTER: + default: + return false; } - } } - return null; - } - @Nullable - public static PsiField findPropertyField(PsiClass aClass, String propertyName, boolean isStatic) { - PsiField[] fields = aClass.getAllFields(); + private static boolean isBoolean(@Nullable PsiType propertyType) { + return PsiType.BOOLEAN.equals(propertyType); + } + - for (PsiField field : fields) { - if (field.hasModifierProperty(PsiModifier.STATIC) != isStatic) - continue; - if (propertyName.equals(suggestPropertyName(field))) - return field; + public static String suggestPropertyName(@Nonnull PsiField field) { + return suggestPropertyName(field, field.getName()); } - return null; - } + @Nonnull + public static String suggestPropertyName(@Nonnull PsiField field, @Nonnull String fieldName) { + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(field.getProject()); + VariableKind kind = codeStyleManager.getVariableKind(field); + String name = codeStyleManager.variableNameToPropertyName(fieldName, kind); + if (!field.hasModifierProperty(PsiModifier.STATIC) && isBoolean(field.getType())) { + if (name.startsWith(IS_PREFIX) && name.length() > IS_PREFIX.length() && Character.isUpperCase(name.charAt(IS_PREFIX.length()))) { + name = Introspector.decapitalize(name.substring(IS_PREFIX.length())); + } + } + return name; + } - @Nullable - public static PsiMethod findPropertyGetter(PsiClass aClass, - @Nonnull String propertyName, - boolean isStatic, - boolean checkSuperClasses) { - if (aClass == null) - return null; - String[] getterCandidateNames = suggestGetterNames(propertyName); + public static String suggestGetterName(PsiField field) { + String propertyName = suggestPropertyName(field); + return suggestGetterName(propertyName, field.getType()); + } - for (String getterCandidateName : getterCandidateNames) { - PsiMethod[] getterCandidates = aClass.findMethodsByName(getterCandidateName, checkSuperClasses); - for (PsiMethod method : getterCandidates) { - if (method.hasModifierProperty(PsiModifier.STATIC) != isStatic) - continue; + public static String suggestSetterName(PsiField field) { + String propertyName = suggestPropertyName(field); + return suggestSetterName(propertyName); + } - if (isSimplePropertyGetter(method)) { - if (getPropertyNameByGetter(method).equals(propertyName)) { - return method; - } + @Nullable + public static String getPropertyName(final PsiMember member) { + if (member instanceof PsiMethod) { + return getPropertyName((PsiMethod)member); } - } + if (member instanceof PsiField) { + return member.getName(); + } + return null; + } + + + public static boolean isSimplePropertySetter(@Nullable PsiMethod method) { + if (method == null) { + return false; + } + + if (method.isConstructor()) { + return false; + } + + String methodName = method.getName(); + + if (!isSetterName(methodName)) { + return false; + } + + if (method.getParameterList().getParametersCount() != 1) { + return false; + } + + final PsiType returnType = method.getReturnType(); + + if (returnType == null || PsiType.VOID.equals(returnType)) { + return true; + } + + return Comparing.equal(PsiUtil.resolveClassInType(TypeConversionUtil.erasure(returnType)), method.getContainingClass()); } - return null; - } - - @Nullable - public static PsiMethod findPropertyGetterWithType(String propertyName, boolean isStatic, PsiType type, Iterator methods) { - while (methods.hasNext()) { - PsiMethod method = methods.next(); - if (method.hasModifierProperty(PsiModifier.STATIC) != isStatic) - continue; - if (isSimplePropertyGetter(method)) { - if (getPropertyNameByGetter(method).equals(propertyName)) { - if (type.equals(method.getReturnType())) - return method; - } - } - } - return null; - } - - public static boolean isSimplePropertyAccessor(PsiMethod method) { - return isSimplePropertyGetter(method) || isSimplePropertySetter(method); - } - - @Nullable - public static PsiMethod findPropertySetterWithType(String propertyName, boolean isStatic, PsiType type, Iterator methods) { - while (methods.hasNext()) { - PsiMethod method = methods.next(); - if (method.hasModifierProperty(PsiModifier.STATIC) != isStatic) - continue; + public static boolean isSetterName(@Nonnull String methodName) { + return checkPrefix(methodName, SET_PREFIX); + } - if (isSimplePropertySetter(method)) { - if (getPropertyNameBySetter(method).equals(propertyName)) { - PsiType methodType = method.getParameterList().getParameters()[0].getType(); - if (type.equals(methodType)) - return method; + @Nullable + public static String getPropertyName(@Nonnull PsiMethod method) { + if (isSimplePropertyGetter(method)) { + return getPropertyNameByGetter(method); } - } + if (isSimplePropertySetter(method)) { + return getPropertyNameBySetter(method); + } + return null; } - return null; - } - public enum GetterFlavour { - BOOLEAN, - GENERIC, - NOT_A_GETTER - } + @Nonnull + public static String getPropertyNameByGetter(PsiMethod getterMethod) { + @NonNls String methodName = getterMethod.getName(); + if (methodName.startsWith(GET_PREFIX)) { + return StringUtil.decapitalize(methodName.substring(3)); + } + if (methodName.startsWith(IS_PREFIX)) { + return StringUtil.decapitalize(methodName.substring(2)); + } + return methodName; + } - @Nonnull - public static GetterFlavour getMethodNameGetterFlavour(@Nonnull String methodName) { - if (checkPrefix(methodName, GET_PREFIX)) { - return GetterFlavour.GENERIC; - } else if (checkPrefix(methodName, IS_PREFIX)) { - return GetterFlavour.BOOLEAN; + @Nonnull + public static String getPropertyNameBySetter(@Nonnull PsiMethod setterMethod) { + String methodName = setterMethod.getName(); + return Introspector.decapitalize(methodName.substring(3)); } - return GetterFlavour.NOT_A_GETTER; - } + private static boolean checkPrefix(@Nonnull String methodName, @Nonnull String prefix) { + boolean hasPrefix = methodName.startsWith(prefix) && methodName.length() > prefix.length(); + return hasPrefix && !(Character.isLowerCase(methodName.charAt(prefix.length())) && + (methodName.length() == prefix.length() + 1 || Character.isLowerCase(methodName.charAt(prefix.length() + 1)))); + } - @Contract("null -> false") - public static boolean isSimplePropertyGetter(@Nullable PsiMethod method) { - return hasGetterName(method) && method.getParameterList().isEmpty(); - } + @NonNls + @Nonnull + public static String[] suggestGetterNames(@Nonnull String propertyName) { + final String str = StringUtil.capitalizeWithJavaBeanConvention(StringUtil.sanitizeJavaIdentifier(propertyName)); + return new String[]{ + IS_PREFIX + str, + GET_PREFIX + str + }; + } + public static String suggestGetterName(@NonNls @Nonnull String propertyName, @Nullable PsiType propertyType) { + return suggestGetterName(propertyName, propertyType, null); + } + + public static String suggestGetterName( + @Nonnull String propertyName, + @Nullable PsiType propertyType, + @NonNls String existingGetterName + ) { + @NonNls StringBuilder name = + new StringBuilder(StringUtil.capitalizeWithJavaBeanConvention(StringUtil.sanitizeJavaIdentifier(propertyName))); + if (isBoolean(propertyType)) { + if (existingGetterName == null || !existingGetterName.startsWith(GET_PREFIX)) { + name.insert(0, IS_PREFIX); + } + else { + name.insert(0, GET_PREFIX); + } + } + else { + name.insert(0, GET_PREFIX); + } - public static boolean hasGetterName(final PsiMethod method) { - if (method == null) - return false; + return name.toString(); + } - if (method.isConstructor()) - return false; - String methodName = method.getName(); - GetterFlavour flavour = getMethodNameGetterFlavour(methodName); - switch (flavour) { - case GENERIC: - PsiType returnType = method.getReturnType(); - return returnType == null || !PsiType.VOID.equals(returnType); - case BOOLEAN: - return isBoolean(method.getReturnType()); - case NOT_A_GETTER: - default: - return false; + public static String suggestSetterName(@NonNls @Nonnull String propertyName) { + return suggestSetterName(propertyName, SET_PREFIX); } - } + public static String suggestSetterName(@NonNls @Nonnull String propertyName, String setterPrefix) { + final String sanitizeJavaIdentifier = StringUtil.sanitizeJavaIdentifier(propertyName); + if (StringUtil.isEmpty(setterPrefix)) { + return sanitizeJavaIdentifier; + } + @NonNls StringBuilder name = new StringBuilder(StringUtil.capitalizeWithJavaBeanConvention(sanitizeJavaIdentifier)); + name.insert(0, setterPrefix); + return name.toString(); + } + + /** + * Consider using {@link GenerateMembersUtil#generateGetterPrototype(PsiField)} or + * {@link GenerateMembersUtil#generateSimpleGetterPrototype(PsiField)} + * to add @Override annotation + */ + @Nonnull + public static PsiMethod generateGetterPrototype(@Nonnull PsiField field) { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(field.getProject()); + Project project = field.getProject(); + String name = field.getName(); + String getName = suggestGetterName(field); + PsiMethod getMethod = factory.createMethod(getName, field.getType()); + PsiUtil.setModifierProperty(getMethod, PsiModifier.PUBLIC, true); + if (field.hasModifierProperty(PsiModifier.STATIC)) { + PsiUtil.setModifierProperty(getMethod, PsiModifier.STATIC, true); + } - private static boolean isBoolean(@Nullable PsiType propertyType) { - return PsiType.BOOLEAN.equals(propertyType); - } + NullableNotNullManager.getInstance(project).copyNullableOrNotNullAnnotation(field, getMethod); + + PsiCodeBlock body = factory.createCodeBlockFromText("{\nreturn " + name + ";\n}", null); + Objects.requireNonNull(getMethod.getBody()).replace(body); + getMethod = (PsiMethod)CodeStyleManager.getInstance(project).reformat(getMethod); + return getMethod; + } + + /** + * Consider using {@link GenerateMembersUtil#generateSetterPrototype(PsiField)} + * or {@link GenerateMembersUtil#generateSimpleSetterPrototype(PsiField)} + * to add @Override annotation + */ + @Nonnull + public static PsiMethod generateSetterPrototype(@Nonnull PsiField field) { + return generateSetterPrototype(field, field.getContainingClass()); + } + + /** + * Consider using {@link GenerateMembersUtil#generateSetterPrototype(PsiField)} + * or {@link GenerateMembersUtil#generateSimpleSetterPrototype(PsiField)} + * to add @Override annotation + */ + @Nonnull + public static PsiMethod generateSetterPrototype(@Nonnull PsiField field, @Nonnull PsiClass containingClass) { + return generateSetterPrototype(field, containingClass, false); + } + + /** + * Consider using {@link GenerateMembersUtil#generateSetterPrototype(PsiField)} + * or {@link GenerateMembersUtil#generateSimpleSetterPrototype(PsiField)} + * to add @Override annotation + */ + @Nonnull + public static PsiMethod generateSetterPrototype(@Nonnull PsiField field, @Nonnull PsiClass containingClass, boolean returnSelf) { + Project project = field.getProject(); + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(field.getProject()); + + String name = field.getName(); + boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); + VariableKind kind = codeStyleManager.getVariableKind(field); + String propertyName = codeStyleManager.variableNameToPropertyName(name, kind); + String setName = suggestSetterName(field); + PsiMethod setMethod = factory.createMethodFromText( + factory.createMethod(setName, returnSelf ? factory.createType(containingClass) : PsiType.VOID).getText(), + field + ); + String parameterName = codeStyleManager.propertyNameToVariableName(propertyName, VariableKind.PARAMETER); + PsiParameter param = factory.createParameter(parameterName, field.getType()); + + NullableNotNullManager.getInstance(project).copyNullableOrNotNullAnnotation(field, param); + + setMethod.getParameterList().add(param); + PsiUtil.setModifierProperty(setMethod, PsiModifier.PUBLIC, true); + PsiUtil.setModifierProperty(setMethod, PsiModifier.STATIC, isStatic); + + @NonNls StringBuilder buffer = new StringBuilder(); + buffer.append("{\n"); + if (name.equals(parameterName)) { + if (!isStatic) { + buffer.append("this."); + } + else { + String className = containingClass.getName(); + if (className != null) { + buffer.append(className); + buffer.append("."); + } + } + } + buffer.append(name); + buffer.append("="); + buffer.append(parameterName); + buffer.append(";\n"); + if (returnSelf) { + buffer.append("return this;\n"); + } + buffer.append("}"); + PsiCodeBlock body = factory.createCodeBlockFromText(buffer.toString(), null); + Objects.requireNonNull(setMethod.getBody()).replace(body); + setMethod = (PsiMethod)CodeStyleManager.getInstance(project).reformat(setMethod); + return setMethod; + } + @Nullable + public static PsiTypeElement getPropertyTypeElement(final PsiMember member) { + if (member instanceof PsiField) { + return ((PsiField)member).getTypeElement(); + } + if (member instanceof PsiMethod) { + final PsiMethod psiMethod = (PsiMethod)member; + if (isSimplePropertyGetter(psiMethod)) { + return psiMethod.getReturnTypeElement(); + } + else if (isSimplePropertySetter(psiMethod)) { + return psiMethod.getParameterList().getParameters()[0].getTypeElement(); + } + } + return null; + } + + @Nullable + public static PsiIdentifier getPropertyNameIdentifier(final PsiMember member) { + if (member instanceof PsiField) { + return ((PsiField)member).getNameIdentifier(); + } + if (member instanceof PsiMethod) { + return ((PsiMethod)member).getNameIdentifier(); + } + return null; + } - public static String suggestPropertyName(@Nonnull PsiField field) { - return suggestPropertyName(field, field.getName()); - } + @Nullable + public static PsiField findPropertyFieldByMember(final PsiMember psiMember) { + if (psiMember instanceof PsiField) { + return (PsiField)psiMember; + } + if (psiMember instanceof PsiMethod) { + final PsiMethod psiMethod = (PsiMethod)psiMember; + final PsiType returnType = psiMethod.getReturnType(); + if (returnType == null) { + return null; + } + final PsiCodeBlock body = psiMethod.getBody(); + final PsiStatement[] statements = body == null ? null : body.getStatements(); + final PsiStatement statement = statements == null || statements.length != 1 ? null : statements[0]; + final PsiElement target; + if (PsiType.VOID.equals(returnType)) { + final PsiExpression expression = + statement instanceof PsiExpressionStatement ? ((PsiExpressionStatement)statement).getExpression() : null; + target = expression instanceof PsiAssignmentExpression ? ((PsiAssignmentExpression)expression).getLExpression() : null; + } + else { + target = statement instanceof PsiReturnStatement ? ((PsiReturnStatement)statement).getReturnValue() : null; + } + final PsiElement resolved = target instanceof PsiReferenceExpression ? ((PsiReferenceExpression)target).resolve() : null; + if (resolved instanceof PsiField) { + final PsiField field = (PsiField)resolved; + PsiClass memberClass = psiMember.getContainingClass(); + PsiClass fieldClass = field.getContainingClass(); + if (memberClass != null && fieldClass != null && (memberClass == fieldClass || memberClass.isInheritor(fieldClass, true))) { + return field; + } + } + } + return null; + } - @Nonnull - public static String suggestPropertyName(@Nonnull PsiField field, @Nonnull String fieldName) { - JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(field.getProject()); - VariableKind kind = codeStyleManager.getVariableKind(field); - String name = codeStyleManager.variableNameToPropertyName(fieldName, kind); - if (!field.hasModifierProperty(PsiModifier.STATIC) && isBoolean(field.getType())) { - if (name.startsWith(IS_PREFIX) && name.length() > IS_PREFIX.length() && Character.isUpperCase(name.charAt(IS_PREFIX.length()))) { - name = Introspector.decapitalize(name.substring(IS_PREFIX.length())); - } - } - return name; - } - - public static String suggestGetterName(PsiField field) { - String propertyName = suggestPropertyName(field); - return suggestGetterName(propertyName, field.getType()); - } - - public static String suggestSetterName(PsiField field) { - String propertyName = suggestPropertyName(field); - return suggestSetterName(propertyName); - } - - @Nullable - public static String getPropertyName(final PsiMember member) { - if (member instanceof PsiMethod) { - return getPropertyName((PsiMethod) member); + public static PsiMethod findSetterForField(PsiField field) { + final PsiClass containingClass = field.getContainingClass(); + final String propertyName = suggestPropertyName(field); + final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); + return findPropertySetter(containingClass, propertyName, isStatic, true); } - if (member instanceof PsiField) { - return member.getName(); - } - return null; - } + public static PsiMethod findGetterForField(PsiField field) { + final PsiClass containingClass = field.getContainingClass(); + final String propertyName = suggestPropertyName(field); + final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); + return findPropertyGetter(containingClass, propertyName, isStatic, true); + } - public static boolean isSimplePropertySetter(@Nullable PsiMethod method) { - if (method == null) - return false; + /** + * If the name of the method looks like a getter and the body consists of a single return statement, + * returns the returned expression. Otherwise, returns null. + * + * @param method the method to check + * @return the return value, or null if it doesn't match the conditions. + */ + @Nullable + public static PsiExpression getGetterReturnExpression(@Nullable PsiMethod method) { + return method != null && hasGetterSignature(method) ? getSingleReturnValue(method) : null; + } - if (method.isConstructor()) - return false; + private static boolean hasGetterSignature(@Nonnull PsiMethod method) { + return isSimplePropertyGetter(method); + } - String methodName = method.getName(); - - if (!isSetterName(methodName)) - return false; - - if (method.getParameterList().getParametersCount() != 1) { - return false; - } - - final PsiType returnType = method.getReturnType(); - - if (returnType == null || PsiType.VOID.equals(returnType)) { - return true; - } - - return Comparing.equal(PsiUtil.resolveClassInType(TypeConversionUtil.erasure(returnType)), method.getContainingClass()); - } - - public static boolean isSetterName(@Nonnull String methodName) { - return checkPrefix(methodName, SET_PREFIX); - } - - @Nullable - public static String getPropertyName(@Nonnull PsiMethod method) { - if (isSimplePropertyGetter(method)) { - return getPropertyNameByGetter(method); - } - if (isSimplePropertySetter(method)) { - return getPropertyNameBySetter(method); - } - return null; - } - - @Nonnull - public static String getPropertyNameByGetter(PsiMethod getterMethod) { - @NonNls String methodName = getterMethod.getName(); - if (methodName.startsWith(GET_PREFIX)) - return StringUtil.decapitalize(methodName.substring(3)); - if (methodName.startsWith(IS_PREFIX)) - return StringUtil.decapitalize(methodName.substring(2)); - return methodName; - } - - @Nonnull - public static String getPropertyNameBySetter(@Nonnull PsiMethod setterMethod) { - String methodName = setterMethod.getName(); - return Introspector.decapitalize(methodName.substring(3)); - } - - private static boolean checkPrefix(@Nonnull String methodName, @Nonnull String prefix) { - boolean hasPrefix = methodName.startsWith(prefix) && methodName.length() > prefix.length(); - return hasPrefix && !(Character.isLowerCase(methodName.charAt(prefix.length())) && - (methodName.length() == prefix.length() + 1 || Character.isLowerCase(methodName.charAt(prefix.length() + 1)))); - } - - @NonNls - @Nonnull - public static String[] suggestGetterNames(@Nonnull String propertyName) { - final String str = StringUtil.capitalizeWithJavaBeanConvention(StringUtil.sanitizeJavaIdentifier(propertyName)); - return new String[]{ - IS_PREFIX + str, - GET_PREFIX + str - }; - } - - public static String suggestGetterName(@NonNls @Nonnull String propertyName, @Nullable PsiType propertyType) { - return suggestGetterName(propertyName, propertyType, null); - } - - public static String suggestGetterName(@Nonnull String propertyName, @Nullable PsiType propertyType, @NonNls String existingGetterName) { - @NonNls StringBuilder name = - new StringBuilder(StringUtil.capitalizeWithJavaBeanConvention(StringUtil.sanitizeJavaIdentifier(propertyName))); - if (isBoolean(propertyType)) { - if (existingGetterName == null || !existingGetterName.startsWith(GET_PREFIX)) { - name.insert(0, IS_PREFIX); - } else { - name.insert(0, GET_PREFIX); - } - } else { - name.insert(0, GET_PREFIX); - } - - return name.toString(); - } - - - public static String suggestSetterName(@NonNls @Nonnull String propertyName) { - return suggestSetterName(propertyName, SET_PREFIX); - } - - public static String suggestSetterName(@NonNls @Nonnull String propertyName, String setterPrefix) { - final String sanitizeJavaIdentifier = StringUtil.sanitizeJavaIdentifier(propertyName); - if (StringUtil.isEmpty(setterPrefix)) { - return sanitizeJavaIdentifier; - } - @NonNls StringBuilder name = new StringBuilder(StringUtil.capitalizeWithJavaBeanConvention(sanitizeJavaIdentifier)); - name.insert(0, setterPrefix); - return name.toString(); - } - - /** - * Consider using {@link GenerateMembersUtil#generateGetterPrototype(PsiField)} or - * {@link GenerateMembersUtil#generateSimpleGetterPrototype(PsiField)} - * to add @Override annotation - */ - @Nonnull - public static PsiMethod generateGetterPrototype(@Nonnull PsiField field) { - PsiElementFactory factory = JavaPsiFacade.getElementFactory(field.getProject()); - Project project = field.getProject(); - String name = field.getName(); - String getName = suggestGetterName(field); - PsiMethod getMethod = factory.createMethod(getName, field.getType()); - PsiUtil.setModifierProperty(getMethod, PsiModifier.PUBLIC, true); - if (field.hasModifierProperty(PsiModifier.STATIC)) { - PsiUtil.setModifierProperty(getMethod, PsiModifier.STATIC, true); - } - - NullableNotNullManager.getInstance(project).copyNullableOrNotNullAnnotation(field, getMethod); - - PsiCodeBlock body = factory.createCodeBlockFromText("{\nreturn " + name + ";\n}", null); - Objects.requireNonNull(getMethod.getBody()).replace(body); - getMethod = (PsiMethod) CodeStyleManager.getInstance(project).reformat(getMethod); - return getMethod; - } - - /** - * Consider using {@link GenerateMembersUtil#generateSetterPrototype(PsiField)} - * or {@link GenerateMembersUtil#generateSimpleSetterPrototype(PsiField)} - * to add @Override annotation - */ - @Nonnull - public static PsiMethod generateSetterPrototype(@Nonnull PsiField field) { - return generateSetterPrototype(field, field.getContainingClass()); - } - - /** - * Consider using {@link GenerateMembersUtil#generateSetterPrototype(PsiField)} - * or {@link GenerateMembersUtil#generateSimpleSetterPrototype(PsiField)} - * to add @Override annotation - */ - @Nonnull - public static PsiMethod generateSetterPrototype(@Nonnull PsiField field, @Nonnull PsiClass containingClass) { - return generateSetterPrototype(field, containingClass, false); - } - - /** - * Consider using {@link GenerateMembersUtil#generateSetterPrototype(PsiField)} - * or {@link GenerateMembersUtil#generateSimpleSetterPrototype(PsiField)} - * to add @Override annotation - */ - @Nonnull - public static PsiMethod generateSetterPrototype(@Nonnull PsiField field, @Nonnull PsiClass containingClass, boolean returnSelf) { - Project project = field.getProject(); - JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); - PsiElementFactory factory = JavaPsiFacade.getElementFactory(field.getProject()); - - String name = field.getName(); - boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); - VariableKind kind = codeStyleManager.getVariableKind(field); - String propertyName = codeStyleManager.variableNameToPropertyName(name, kind); - String setName = suggestSetterName(field); - PsiMethod setMethod = factory - .createMethodFromText(factory.createMethod(setName, returnSelf ? factory.createType(containingClass) : PsiType.VOID).getText(), - field); - String parameterName = codeStyleManager.propertyNameToVariableName(propertyName, VariableKind.PARAMETER); - PsiParameter param = factory.createParameter(parameterName, field.getType()); - - NullableNotNullManager.getInstance(project).copyNullableOrNotNullAnnotation(field, param); - - setMethod.getParameterList().add(param); - PsiUtil.setModifierProperty(setMethod, PsiModifier.PUBLIC, true); - PsiUtil.setModifierProperty(setMethod, PsiModifier.STATIC, isStatic); - - @NonNls StringBuilder buffer = new StringBuilder(); - buffer.append("{\n"); - if (name.equals(parameterName)) { - if (!isStatic) { - buffer.append("this."); - } else { - String className = containingClass.getName(); - if (className != null) { - buffer.append(className); - buffer.append("."); - } - } - } - buffer.append(name); - buffer.append("="); - buffer.append(parameterName); - buffer.append(";\n"); - if (returnSelf) { - buffer.append("return this;\n"); - } - buffer.append("}"); - PsiCodeBlock body = factory.createCodeBlockFromText(buffer.toString(), null); - Objects.requireNonNull(setMethod.getBody()).replace(body); - setMethod = (PsiMethod) CodeStyleManager.getInstance(project).reformat(setMethod); - return setMethod; - } - - @Nullable - public static PsiTypeElement getPropertyTypeElement(final PsiMember member) { - if (member instanceof PsiField) { - return ((PsiField) member).getTypeElement(); - } - if (member instanceof PsiMethod) { - final PsiMethod psiMethod = (PsiMethod) member; - if (isSimplePropertyGetter(psiMethod)) { - return psiMethod.getReturnTypeElement(); - } else if (isSimplePropertySetter(psiMethod)) { - return psiMethod.getParameterList().getParameters()[0].getTypeElement(); - } - } - return null; - } - - @Nullable - public static PsiIdentifier getPropertyNameIdentifier(final PsiMember member) { - if (member instanceof PsiField) { - return ((PsiField) member).getNameIdentifier(); - } - if (member instanceof PsiMethod) { - return ((PsiMethod) member).getNameIdentifier(); - } - return null; - } - - @Nullable - public static PsiField findPropertyFieldByMember(final PsiMember psiMember) { - if (psiMember instanceof PsiField) { - return (PsiField) psiMember; - } - if (psiMember instanceof PsiMethod) { - final PsiMethod psiMethod = (PsiMethod) psiMember; - final PsiType returnType = psiMethod.getReturnType(); - if (returnType == null) + @Nullable + public static PsiExpression getSingleReturnValue(@Nonnull PsiMethod method) { + final PsiCodeBlock body = method.getBody(); + if (body == null) { + return null; + } + final PsiStatement[] statements = body.getStatements(); + final PsiStatement statement = statements.length != 1 ? null : statements[0]; + return statement instanceof PsiReturnStatement ? ((PsiReturnStatement)statement).getReturnValue() : null; + } + + @Contract(pure = true) + @Nullable + public static PropertyKind getPropertyKind(@Nonnull String accessorName) { + for (PropertyKind kind : PropertyKind.values()) { + String prefix = kind.prefix; + int prefixLength = prefix.length(); + if (accessorName.startsWith(prefix) && accessorName.length() > prefixLength) { + return kind; + } + } return null; - final PsiCodeBlock body = psiMethod.getBody(); - final PsiStatement[] statements = body == null ? null : body.getStatements(); - final PsiStatement statement = statements == null || statements.length != 1 ? null : statements[0]; - final PsiElement target; - if (PsiType.VOID.equals(returnType)) { - final PsiExpression expression = - statement instanceof PsiExpressionStatement ? ((PsiExpressionStatement) statement).getExpression() : null; - target = expression instanceof PsiAssignmentExpression ? ((PsiAssignmentExpression) expression).getLExpression() : null; - } else { - target = statement instanceof PsiReturnStatement ? ((PsiReturnStatement) statement).getReturnValue() : null; - } - final PsiElement resolved = target instanceof PsiReferenceExpression ? ((PsiReferenceExpression) target).resolve() : null; - if (resolved instanceof PsiField) { - final PsiField field = (PsiField) resolved; - PsiClass memberClass = psiMember.getContainingClass(); - PsiClass fieldClass = field.getContainingClass(); - if (memberClass != null && fieldClass != null && (memberClass == fieldClass || memberClass.isInheritor(fieldClass, true))) { - return field; - } - } - } - return null; - } - - public static PsiMethod findSetterForField(PsiField field) { - final PsiClass containingClass = field.getContainingClass(); - final String propertyName = suggestPropertyName(field); - final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); - return findPropertySetter(containingClass, propertyName, isStatic, true); - } - - public static PsiMethod findGetterForField(PsiField field) { - final PsiClass containingClass = field.getContainingClass(); - final String propertyName = suggestPropertyName(field); - final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); - return findPropertyGetter(containingClass, propertyName, isStatic, true); - } - - /** - * If the name of the method looks like a getter and the body consists of a single return statement, - * returns the returned expression. Otherwise, returns null. - * - * @param method the method to check - * @return the return value, or null if it doesn't match the conditions. - */ - @Nullable - public static PsiExpression getGetterReturnExpression(@Nullable PsiMethod method) { - return method != null && hasGetterSignature(method) ? getSingleReturnValue(method) : null; - } - - private static boolean hasGetterSignature(@Nonnull PsiMethod method) { - return isSimplePropertyGetter(method); - } - - @Nullable - public static PsiExpression getSingleReturnValue(@Nonnull PsiMethod method) { - final PsiCodeBlock body = method.getBody(); - if (body == null) { - return null; - } - final PsiStatement[] statements = body.getStatements(); - final PsiStatement statement = statements.length != 1 ? null : statements[0]; - return statement instanceof PsiReturnStatement ? ((PsiReturnStatement) statement).getReturnValue() : null; - } - - @Contract(pure = true) - @Nullable - public static PropertyKind getPropertyKind(@Nonnull String accessorName) { - for (PropertyKind kind : PropertyKind.values()) { - String prefix = kind.prefix; - int prefixLength = prefix.length(); - if (accessorName.startsWith(prefix) && accessorName.length() > prefixLength) { - return kind; - } - } - return null; - } - - /** - * @see StringUtil#getPropertyName(String) - * @see Introspector - */ - @Contract(pure = true) - @Nullable - public static Pair getPropertyNameAndKind(@Nonnull String accessorName) { - PropertyKind kind = getPropertyKind(accessorName); - if (kind == null) { - return null; - } - String baseName = accessorName.substring(kind.prefix.length()); - String propertyName = Introspector.decapitalize(baseName); - return Pair.create(propertyName, kind); - } - - @Contract(pure = true) - @Nonnull - public static String getAccessorName(@Nonnull String propertyName, @Nonnull PropertyKind kind) { - return kind.prefix + StringUtil.capitalizeWithJavaBeanConvention(propertyName); - } + } + + /** + * @see StringUtil#getPropertyName(String) + * @see Introspector + */ + @Contract(pure = true) + @Nullable + public static Pair getPropertyNameAndKind(@Nonnull String accessorName) { + PropertyKind kind = getPropertyKind(accessorName); + if (kind == null) { + return null; + } + String baseName = accessorName.substring(kind.prefix.length()); + String propertyName = Introspector.decapitalize(baseName); + return Pair.create(propertyName, kind); + } + + @Contract(pure = true) + @Nonnull + public static String getAccessorName(@Nonnull String propertyName, @Nonnull PropertyKind kind) { + return kind.prefix + StringUtil.capitalizeWithJavaBeanConvention(propertyName); + } } diff --git a/java-language-api/src/main/java/com/intellij/java/language/psi/util/PsiPrecedenceUtil.java b/java-language-api/src/main/java/com/intellij/java/language/psi/util/PsiPrecedenceUtil.java index 4aab1c66d2..2b9f4decb7 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/psi/util/PsiPrecedenceUtil.java +++ b/java-language-api/src/main/java/com/intellij/java/language/psi/util/PsiPrecedenceUtil.java @@ -11,253 +11,267 @@ import java.util.Map; public class PsiPrecedenceUtil { - public static final int PARENTHESIZED_PRECEDENCE = 0; - public static final int LITERAL_PRECEDENCE = 0; - public static final int METHOD_CALL_PRECEDENCE = 1; - public static final int POSTFIX_PRECEDENCE = 2; - public static final int PREFIX_PRECEDENCE = 3; - public static final int TYPE_CAST_PRECEDENCE = 4; - public static final int MULTIPLICATIVE_PRECEDENCE = 5; - public static final int ADDITIVE_PRECEDENCE = 6; - public static final int SHIFT_PRECEDENCE = 7; - public static final int RELATIONAL_PRECEDENCE = 8; - public static final int EQUALITY_PRECEDENCE = 9; - public static final int BINARY_AND_PRECEDENCE = 10; - public static final int BINARY_XOR_PRECEDENCE = 11; - public static final int BINARY_OR_PRECEDENCE = 12; - public static final int AND_PRECEDENCE = 13; - public static final int OR_PRECEDENCE = 14; - public static final int CONDITIONAL_PRECEDENCE = 15; - public static final int ASSIGNMENT_PRECEDENCE = 16; - public static final int LAMBDA_PRECEDENCE = 17; // jls-15.2 - public static final int NUM_PRECEDENCES = 18; + public static final int PARENTHESIZED_PRECEDENCE = 0; + public static final int LITERAL_PRECEDENCE = 0; + public static final int METHOD_CALL_PRECEDENCE = 1; + public static final int POSTFIX_PRECEDENCE = 2; + public static final int PREFIX_PRECEDENCE = 3; + public static final int TYPE_CAST_PRECEDENCE = 4; + public static final int MULTIPLICATIVE_PRECEDENCE = 5; + public static final int ADDITIVE_PRECEDENCE = 6; + public static final int SHIFT_PRECEDENCE = 7; + public static final int RELATIONAL_PRECEDENCE = 8; + public static final int EQUALITY_PRECEDENCE = 9; + public static final int BINARY_AND_PRECEDENCE = 10; + public static final int BINARY_XOR_PRECEDENCE = 11; + public static final int BINARY_OR_PRECEDENCE = 12; + public static final int AND_PRECEDENCE = 13; + public static final int OR_PRECEDENCE = 14; + public static final int CONDITIONAL_PRECEDENCE = 15; + public static final int ASSIGNMENT_PRECEDENCE = 16; + public static final int LAMBDA_PRECEDENCE = 17; // jls-15.2 + public static final int NUM_PRECEDENCES = 18; - private static final Map s_binaryOperatorPrecedence = new HashMap<>(NUM_PRECEDENCES); + private static final Map s_binaryOperatorPrecedence = new HashMap<>(NUM_PRECEDENCES); - static { - s_binaryOperatorPrecedence.put(JavaTokenType.PLUS, ADDITIVE_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.MINUS, ADDITIVE_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.ASTERISK, MULTIPLICATIVE_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.DIV, MULTIPLICATIVE_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.PERC, MULTIPLICATIVE_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.ANDAND, AND_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.OROR, OR_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.AND, BINARY_AND_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.OR, BINARY_OR_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.XOR, BINARY_XOR_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.LTLT, SHIFT_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.GTGT, SHIFT_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.GTGTGT, SHIFT_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.GT, RELATIONAL_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.GE, RELATIONAL_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.LT, RELATIONAL_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.LE, RELATIONAL_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.EQEQ, EQUALITY_PRECEDENCE); - s_binaryOperatorPrecedence.put(JavaTokenType.NE, EQUALITY_PRECEDENCE); - } - - public static boolean isCommutativeOperator(@Nonnull IElementType token) { - return token == JavaTokenType.PLUS || token == JavaTokenType.ASTERISK || - token == JavaTokenType.EQEQ || token == JavaTokenType.NE || - token == JavaTokenType.AND || token == JavaTokenType.OR || token == JavaTokenType.XOR; - } - - public static boolean isCommutativeOperation(PsiPolyadicExpression expression) { - final IElementType tokenType = expression.getOperationTokenType(); - if (!isCommutativeOperator(tokenType)) { - return false; + static { + s_binaryOperatorPrecedence.put(JavaTokenType.PLUS, ADDITIVE_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.MINUS, ADDITIVE_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.ASTERISK, MULTIPLICATIVE_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.DIV, MULTIPLICATIVE_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.PERC, MULTIPLICATIVE_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.ANDAND, AND_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.OROR, OR_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.AND, BINARY_AND_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.OR, BINARY_OR_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.XOR, BINARY_XOR_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.LTLT, SHIFT_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.GTGT, SHIFT_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.GTGTGT, SHIFT_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.GT, RELATIONAL_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.GE, RELATIONAL_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.LT, RELATIONAL_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.LE, RELATIONAL_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.EQEQ, EQUALITY_PRECEDENCE); + s_binaryOperatorPrecedence.put(JavaTokenType.NE, EQUALITY_PRECEDENCE); } - final PsiType type = expression.getType(); - return type != null && !type.equalsToText(CommonClassNames.JAVA_LANG_STRING); - } - public static boolean isAssociativeOperation(PsiPolyadicExpression expression) { - final IElementType tokenType = expression.getOperationTokenType(); - final PsiType type = expression.getType(); - final PsiPrimitiveType primitiveType; - if (type instanceof PsiClassType) { - primitiveType = PsiPrimitiveType.getUnboxedType(type); - if (primitiveType == null) { - return false; - } - } else if (type instanceof PsiPrimitiveType) { - primitiveType = (PsiPrimitiveType) type; - } else { - return false; + public static boolean isCommutativeOperator(@Nonnull IElementType token) { + return token == JavaTokenType.PLUS || token == JavaTokenType.ASTERISK || + token == JavaTokenType.EQEQ || token == JavaTokenType.NE || + token == JavaTokenType.AND || token == JavaTokenType.OR || token == JavaTokenType.XOR; } - if (JavaTokenType.PLUS == tokenType || JavaTokenType.ASTERISK == tokenType) { - return !PsiType.FLOAT.equals(primitiveType) && !PsiType.DOUBLE.equals(primitiveType); - } else if (JavaTokenType.EQEQ == tokenType || JavaTokenType.NE == tokenType) { - return PsiType.BOOLEAN.equals(primitiveType); - } else if (JavaTokenType.AND == tokenType || JavaTokenType.OR == tokenType || JavaTokenType.XOR == tokenType) { - return true; - } else if (JavaTokenType.OROR == tokenType || JavaTokenType.ANDAND == tokenType) { - return true; + public static boolean isCommutativeOperation(PsiPolyadicExpression expression) { + final IElementType tokenType = expression.getOperationTokenType(); + if (!isCommutativeOperator(tokenType)) { + return false; + } + final PsiType type = expression.getType(); + return type != null && !type.equalsToText(CommonClassNames.JAVA_LANG_STRING); } - return false; - } - public static int getPrecedence(PsiExpression expression) { - if (expression instanceof PsiThisExpression || - expression instanceof PsiLiteralExpression || - expression instanceof PsiSuperExpression || - expression instanceof PsiClassObjectAccessExpression || - expression instanceof PsiArrayAccessExpression || - expression instanceof PsiArrayInitializerExpression) { - return LITERAL_PRECEDENCE; - } - if (expression instanceof PsiReferenceExpression) { - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) expression; - if (referenceExpression.getQualifier() != null) { - return METHOD_CALL_PRECEDENCE; - } else { - return LITERAL_PRECEDENCE; - } - } - if (expression instanceof PsiMethodCallExpression || expression instanceof PsiNewExpression) { - return METHOD_CALL_PRECEDENCE; - } - if (expression instanceof PsiTypeCastExpression) { - return TYPE_CAST_PRECEDENCE; - } - if (expression instanceof PsiPrefixExpression) { - return PREFIX_PRECEDENCE; - } - if (expression instanceof PsiPostfixExpression || expression instanceof PsiSwitchExpression) { - return POSTFIX_PRECEDENCE; - } - if (expression instanceof PsiPolyadicExpression) { - final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression) expression; - return getPrecedenceForOperator(polyadicExpression.getOperationTokenType()); - } - if (expression instanceof PsiInstanceOfExpression) { - return RELATIONAL_PRECEDENCE; - } - if (expression instanceof PsiConditionalExpression) { - return CONDITIONAL_PRECEDENCE; - } - if (expression instanceof PsiAssignmentExpression) { - return ASSIGNMENT_PRECEDENCE; - } - if (expression instanceof PsiParenthesizedExpression) { - return PARENTHESIZED_PRECEDENCE; - } - if (expression instanceof PsiLambdaExpression) { - return LAMBDA_PRECEDENCE; - } - return -1; - } + public static boolean isAssociativeOperation(PsiPolyadicExpression expression) { + final IElementType tokenType = expression.getOperationTokenType(); + final PsiType type = expression.getType(); + final PsiPrimitiveType primitiveType; + if (type instanceof PsiClassType) { + primitiveType = PsiPrimitiveType.getUnboxedType(type); + if (primitiveType == null) { + return false; + } + } + else if (type instanceof PsiPrimitiveType) { + primitiveType = (PsiPrimitiveType)type; + } + else { + return false; + } - public static int getPrecedenceForOperator(@Nonnull IElementType operator) { - final Integer precedence = s_binaryOperatorPrecedence.get(operator); - if (precedence == null) { - throw new IllegalArgumentException("unknown operator: " + operator); + if (JavaTokenType.PLUS == tokenType || JavaTokenType.ASTERISK == tokenType) { + return !PsiType.FLOAT.equals(primitiveType) && !PsiType.DOUBLE.equals(primitiveType); + } + else if (JavaTokenType.EQEQ == tokenType || JavaTokenType.NE == tokenType) { + return PsiType.BOOLEAN.equals(primitiveType); + } + else if (JavaTokenType.AND == tokenType || JavaTokenType.OR == tokenType || JavaTokenType.XOR == tokenType) { + return true; + } + else if (JavaTokenType.OROR == tokenType || JavaTokenType.ANDAND == tokenType) { + return true; + } + return false; } - return precedence.intValue(); - } - public static boolean areParenthesesNeeded(PsiParenthesizedExpression expression, boolean ignoreClarifyingParentheses) { - final PsiElement parent = expression.getParent(); - if (!(parent instanceof PsiExpression)) { - return false; + public static int getPrecedence(PsiExpression expression) { + if (expression instanceof PsiThisExpression || + expression instanceof PsiLiteralExpression || + expression instanceof PsiSuperExpression || + expression instanceof PsiClassObjectAccessExpression || + expression instanceof PsiArrayAccessExpression || + expression instanceof PsiArrayInitializerExpression) { + return LITERAL_PRECEDENCE; + } + if (expression instanceof PsiReferenceExpression) { + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)expression; + if (referenceExpression.getQualifier() != null) { + return METHOD_CALL_PRECEDENCE; + } + else { + return LITERAL_PRECEDENCE; + } + } + if (expression instanceof PsiMethodCallExpression || expression instanceof PsiNewExpression) { + return METHOD_CALL_PRECEDENCE; + } + if (expression instanceof PsiTypeCastExpression) { + return TYPE_CAST_PRECEDENCE; + } + if (expression instanceof PsiPrefixExpression) { + return PREFIX_PRECEDENCE; + } + if (expression instanceof PsiPostfixExpression || expression instanceof PsiSwitchExpression) { + return POSTFIX_PRECEDENCE; + } + if (expression instanceof PsiPolyadicExpression) { + final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)expression; + return getPrecedenceForOperator(polyadicExpression.getOperationTokenType()); + } + if (expression instanceof PsiInstanceOfExpression) { + return RELATIONAL_PRECEDENCE; + } + if (expression instanceof PsiConditionalExpression) { + return CONDITIONAL_PRECEDENCE; + } + if (expression instanceof PsiAssignmentExpression) { + return ASSIGNMENT_PRECEDENCE; + } + if (expression instanceof PsiParenthesizedExpression) { + return PARENTHESIZED_PRECEDENCE; + } + if (expression instanceof PsiLambdaExpression) { + return LAMBDA_PRECEDENCE; + } + return -1; } - final PsiExpression child = expression.getExpression(); - return child == null || areParenthesesNeeded(child, (PsiExpression) parent, ignoreClarifyingParentheses); - } - public static boolean areParenthesesNeeded(PsiExpression expression, - PsiExpression parentExpression, - boolean ignoreClarifyingParentheses) { - if (parentExpression instanceof PsiParenthesizedExpression || parentExpression instanceof PsiArrayInitializerExpression) { - return false; - } - if (parentExpression instanceof PsiArrayAccessExpression) { - final PsiArrayAccessExpression arrayAccessExpression = (PsiArrayAccessExpression) parentExpression; - return PsiTreeUtil.isAncestor(arrayAccessExpression.getArrayExpression(), expression, false); + public static int getPrecedenceForOperator(@Nonnull IElementType operator) { + final Integer precedence = s_binaryOperatorPrecedence.get(operator); + if (precedence == null) { + throw new IllegalArgumentException("unknown operator: " + operator); + } + return precedence.intValue(); } - final int parentPrecedence = getPrecedence(parentExpression); - final int childPrecedence = getPrecedence(expression); - if (parentPrecedence > childPrecedence) { - if (ignoreClarifyingParentheses) { - if (expression instanceof PsiPolyadicExpression) { - if (parentExpression instanceof PsiPolyadicExpression || - parentExpression instanceof PsiConditionalExpression || - parentExpression instanceof PsiInstanceOfExpression) { - return true; - } - } else if (expression instanceof PsiInstanceOfExpression) { - return true; + + public static boolean areParenthesesNeeded(PsiParenthesizedExpression expression, boolean ignoreClarifyingParentheses) { + final PsiElement parent = expression.getParent(); + if (!(parent instanceof PsiExpression)) { + return false; } - } - return false; + final PsiExpression child = expression.getExpression(); + return child == null || areParenthesesNeeded(child, (PsiExpression)parent, ignoreClarifyingParentheses); } - if (parentExpression instanceof PsiPolyadicExpression && expression instanceof PsiPolyadicExpression) { - final PsiPolyadicExpression parentPolyadicExpression = (PsiPolyadicExpression) parentExpression; - final PsiType parentType = parentPolyadicExpression.getType(); - if (parentType == null) { - return true; - } - final PsiPolyadicExpression childPolyadicExpression = (PsiPolyadicExpression) expression; - final PsiType childType = childPolyadicExpression.getType(); - if (!parentType.equals(childType)) { - return true; - } - if (childType.equalsToText(CommonClassNames.JAVA_LANG_STRING) && - !PsiTreeUtil.isAncestor(parentPolyadicExpression.getOperands()[0], childPolyadicExpression, true)) { - final PsiExpression[] operands = childPolyadicExpression.getOperands(); - for (PsiExpression operand : operands) { - if (!childType.equals(operand.getType())) { - return true; - } + + public static boolean areParenthesesNeeded( + PsiExpression expression, + PsiExpression parentExpression, + boolean ignoreClarifyingParentheses + ) { + if (parentExpression instanceof PsiParenthesizedExpression || parentExpression instanceof PsiArrayInitializerExpression) { + return false; } - } else if (childType.equals(PsiType.BOOLEAN)) { - final PsiExpression[] operands = childPolyadicExpression.getOperands(); - for (PsiExpression operand : operands) { - if (!PsiType.BOOLEAN.equals(operand.getType())) { - return true; - } + if (parentExpression instanceof PsiArrayAccessExpression) { + final PsiArrayAccessExpression arrayAccessExpression = (PsiArrayAccessExpression)parentExpression; + return PsiTreeUtil.isAncestor(arrayAccessExpression.getArrayExpression(), expression, false); } - } - final IElementType parentOperator = parentPolyadicExpression.getOperationTokenType(); - final IElementType childOperator = childPolyadicExpression.getOperationTokenType(); - if (ignoreClarifyingParentheses) { - if (!childOperator.equals(parentOperator)) { - return true; + final int parentPrecedence = getPrecedence(parentExpression); + final int childPrecedence = getPrecedence(expression); + if (parentPrecedence > childPrecedence) { + if (ignoreClarifyingParentheses) { + if (expression instanceof PsiPolyadicExpression) { + if (parentExpression instanceof PsiPolyadicExpression + || parentExpression instanceof PsiConditionalExpression + || parentExpression instanceof PsiInstanceOfExpression) { + return true; + } + } + else if (expression instanceof PsiInstanceOfExpression) { + return true; + } + } + return false; } - } - final PsiExpression[] parentOperands = parentPolyadicExpression.getOperands(); - if (!PsiTreeUtil.isAncestor(parentOperands[0], expression, false)) { - if (!isAssociativeOperation(parentPolyadicExpression) || - JavaTokenType.DIV == childOperator || JavaTokenType.PERC == childOperator) { - return true; + if (parentExpression instanceof PsiPolyadicExpression && expression instanceof PsiPolyadicExpression) { + final PsiPolyadicExpression parentPolyadicExpression = (PsiPolyadicExpression)parentExpression; + final PsiType parentType = parentPolyadicExpression.getType(); + if (parentType == null) { + return true; + } + final PsiPolyadicExpression childPolyadicExpression = (PsiPolyadicExpression)expression; + final PsiType childType = childPolyadicExpression.getType(); + if (!parentType.equals(childType)) { + return true; + } + if (childType.equalsToText(CommonClassNames.JAVA_LANG_STRING) && + !PsiTreeUtil.isAncestor(parentPolyadicExpression.getOperands()[0], childPolyadicExpression, true)) { + final PsiExpression[] operands = childPolyadicExpression.getOperands(); + for (PsiExpression operand : operands) { + if (!childType.equals(operand.getType())) { + return true; + } + } + } + else if (childType.equals(PsiType.BOOLEAN)) { + final PsiExpression[] operands = childPolyadicExpression.getOperands(); + for (PsiExpression operand : operands) { + if (!PsiType.BOOLEAN.equals(operand.getType())) { + return true; + } + } + } + final IElementType parentOperator = parentPolyadicExpression.getOperationTokenType(); + final IElementType childOperator = childPolyadicExpression.getOperationTokenType(); + if (ignoreClarifyingParentheses) { + if (!childOperator.equals(parentOperator)) { + return true; + } + } + final PsiExpression[] parentOperands = parentPolyadicExpression.getOperands(); + if (!PsiTreeUtil.isAncestor(parentOperands[0], expression, false)) { + if (!isAssociativeOperation(parentPolyadicExpression) || + JavaTokenType.DIV == childOperator || JavaTokenType.PERC == childOperator) { + return true; + } + } } - } - } else if (parentExpression instanceof PsiConditionalExpression && expression instanceof PsiConditionalExpression) { - final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression) parentExpression; - final PsiExpression condition = conditionalExpression.getCondition(); - return PsiTreeUtil.isAncestor(condition, expression, true); - } else if (expression instanceof PsiLambdaExpression) { // jls-15.16 - if (parentExpression instanceof PsiTypeCastExpression) { - return false; - } else if (parentExpression instanceof PsiConditionalExpression) { // jls-15.25 - final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression) parentExpression; - return PsiTreeUtil.isAncestor(conditionalExpression.getCondition(), expression, true); - } + else if (parentExpression instanceof PsiConditionalExpression && expression instanceof PsiConditionalExpression) { + final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)parentExpression; + final PsiExpression condition = conditionalExpression.getCondition(); + return PsiTreeUtil.isAncestor(condition, expression, true); + } + else if (expression instanceof PsiLambdaExpression) { // jls-15.16 + if (parentExpression instanceof PsiTypeCastExpression) { + return false; + } + else if (parentExpression instanceof PsiConditionalExpression) { // jls-15.25 + final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)parentExpression; + return PsiTreeUtil.isAncestor(conditionalExpression.getCondition(), expression, true); + } + } + return parentPrecedence < childPrecedence; } - return parentPrecedence < childPrecedence; - } - public static boolean areParenthesesNeeded(PsiJavaToken compoundAssignmentToken, PsiExpression rhs) { - if (rhs instanceof PsiPolyadicExpression) { - final PsiPolyadicExpression binaryExpression = (PsiPolyadicExpression) rhs; - final int precedence1 = getPrecedenceForOperator(binaryExpression.getOperationTokenType()); - final IElementType signTokenType = compoundAssignmentToken.getTokenType(); - final IElementType newOperatorToken = TypeConversionUtil.convertEQtoOperation(signTokenType); - final int precedence2 = getPrecedenceForOperator(newOperatorToken); - return precedence1 >= precedence2 || !isCommutativeOperator(newOperatorToken); - } else { - return rhs instanceof PsiConditionalExpression; + public static boolean areParenthesesNeeded(PsiJavaToken compoundAssignmentToken, PsiExpression rhs) { + if (rhs instanceof PsiPolyadicExpression) { + final PsiPolyadicExpression binaryExpression = (PsiPolyadicExpression)rhs; + final int precedence1 = getPrecedenceForOperator(binaryExpression.getOperationTokenType()); + final IElementType signTokenType = compoundAssignmentToken.getTokenType(); + final IElementType newOperatorToken = TypeConversionUtil.convertEQtoOperation(signTokenType); + final int precedence2 = getPrecedenceForOperator(newOperatorToken); + return precedence1 >= precedence2 || !isCommutativeOperator(newOperatorToken); + } + else { + return rhs instanceof PsiConditionalExpression; + } } - } } diff --git a/java-language-api/src/main/java/consulo/java/language/module/util/JavaClassNames.java b/java-language-api/src/main/java/consulo/java/language/module/util/JavaClassNames.java index e9174d5322..6dbe05ceb4 100644 --- a/java-language-api/src/main/java/consulo/java/language/module/util/JavaClassNames.java +++ b/java-language-api/src/main/java/consulo/java/language/module/util/JavaClassNames.java @@ -86,6 +86,7 @@ public interface JavaClassNames { String JAVA_UTIL_COMPARATOR = "java.util.Comparator"; String JAVA_UTIL_DATE = "java.util.Date"; String JAVA_UTIL_DICTIONARY = "java.util.Dictionary"; + String JAVA_UTIL_ENUM_SET = "java.util.EnumSet"; String JAVA_UTIL_FUNCTION_BI_FUNCTION = "java.util.function.BiFunction"; String JAVA_UTIL_FUNCTION_PREDICATE = "java.util.function.Predicate"; String JAVA_UTIL_FUNCTION_CONSUMER = "java.util.function.Consumer"; diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/controlFlow/ControlFlowAnalyzer.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/controlFlow/ControlFlowAnalyzer.java index a21b558bc0..1cbc56ad5e 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/controlFlow/ControlFlowAnalyzer.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/controlFlow/ControlFlowAnalyzer.java @@ -22,1864 +22,1918 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; class ControlFlowAnalyzer extends JavaElementVisitor { - private static final Logger LOG = Logger.getInstance(ControlFlowAnalyzer.class); - - private final PsiElement myCodeFragment; - private final ControlFlowPolicy myPolicy; - - private ControlFlowImpl myCurrentFlow; - private final Stack myCatchParameters = new Stack<>();// stack of PsiParameter for 'catch' - private final Stack myCatchBlocks = new Stack<>(); - - private final Stack myFinallyBlocks = new Stack<>(); - private final Stack myUnhandledExceptionCatchBlocks = new Stack<>(); - - // element to jump to from inner (sub)expression in "jump to begin" situation. - // E.g. we should jump to "then" branch if condition expression evaluated to true inside if statement - private final StatementStack myStartStatementStack = new StatementStack(); - // element to jump to from inner (sub)expression in "jump to end" situation. - // E.g. we should jump to "else" branch if condition expression evaluated to false inside if statement - private final StatementStack myEndStatementStack = new StatementStack(); - - private final Stack myStartJumpRoles = new Stack<>(); - private final Stack myEndJumpRoles = new Stack<>(); - - private final - @Nonnull - ControlFlowOptions myOptions; - private final boolean myAssignmentTargetsAreElements; - - private final Stack intArrayPool = new Stack<>(); - // map: PsiElement element -> TIntArrayList instructionOffsetsToPatch with getStartOffset(element) - private final Map offsetsAddElementStart = new HashMap<>(); - // map: PsiElement element -> TIntArrayList instructionOffsetsToPatch with getEndOffset(element) - private final Map offsetsAddElementEnd = new HashMap<>(); - private final ControlFlowFactory myControlFlowFactory; - private final List mySubRanges = new ArrayList<>(); - private final PsiConstantEvaluationHelper myConstantEvaluationHelper; - private final Map myImplicitCompactConstructorAssignments; - - ControlFlowAnalyzer(@Nonnull PsiElement codeFragment, - @Nonnull ControlFlowPolicy policy, - @Nonnull ControlFlowOptions options) { - this(codeFragment, policy, options, false); - } - - private ControlFlowAnalyzer(@Nonnull PsiElement codeFragment, - @Nonnull ControlFlowPolicy policy, - @Nonnull ControlFlowOptions options, - boolean assignmentTargetsAreElements) { - myCodeFragment = codeFragment; - myPolicy = policy; - myOptions = options; - myAssignmentTargetsAreElements = assignmentTargetsAreElements; - Project project = codeFragment.getProject(); - myControlFlowFactory = ControlFlowFactory.getInstance(project); - myConstantEvaluationHelper = JavaPsiFacade.getInstance(project).getConstantEvaluationHelper(); - myImplicitCompactConstructorAssignments = getImplicitCompactConstructorAssignmentsMap(); - } - - private Map getImplicitCompactConstructorAssignmentsMap() { - PsiMethod ctor = ObjectUtil.tryCast(myCodeFragment.getParent(), PsiMethod.class); - if (ctor == null || !JavaPsiRecordUtil.isCompactConstructor(ctor)) { - return Collections.emptyMap(); - } - PsiClass containingClass = ctor.getContainingClass(); - if (containingClass == null) { - return Collections.emptyMap(); - } - PsiParameter[] parameters = ctor.getParameterList().getParameters(); - PsiRecordComponent[] components = containingClass.getRecordComponents(); - Map map = new HashMap<>(); - for (int i = 0; i < Math.min(components.length, parameters.length); i++) { - PsiRecordComponent component = components[i]; - PsiField field = JavaPsiRecordUtil.getFieldForComponent(component); - PsiParameter parameter = parameters[i]; - map.put(field, parameter); - } - return map; - } - - @Nonnull - ControlFlow buildControlFlow() throws AnalysisCanceledException { - // push guard outer statement offsets in case when nested expression is incorrect - myStartJumpRoles.push(BranchingInstruction.Role.END); - myEndJumpRoles.push(BranchingInstruction.Role.END); - - myCurrentFlow = new ControlFlowImpl(); - - // guard elements - myStartStatementStack.pushStatement(myCodeFragment, false); - myEndStatementStack.pushStatement(myCodeFragment, false); - - try { - myCodeFragment.accept(this); - return cleanup(); - } catch (AnalysisCanceledSoftException e) { - throw new AnalysisCanceledException(e.getErrorElement()); - } - } - - private void generateCompactConstructorAssignments() { - myImplicitCompactConstructorAssignments.values().stream().filter(myPolicy::isParameterAccepted).forEach(this::generateReadInstruction); - } - - private static class StatementStack { - private final Stack myStatements = new Stack<>(); - private final IntList myAtStart = IntLists.newArrayList(); - - private void popStatement() { - myAtStart.removeByIndex(myAtStart.size() - 1); - myStatements.pop(); + private static final Logger LOG = Logger.getInstance(ControlFlowAnalyzer.class); + + private final PsiElement myCodeFragment; + private final ControlFlowPolicy myPolicy; + + private ControlFlowImpl myCurrentFlow; + private final Stack myCatchParameters = new Stack<>();// stack of PsiParameter for 'catch' + private final Stack myCatchBlocks = new Stack<>(); + + private final Stack myFinallyBlocks = new Stack<>(); + private final Stack myUnhandledExceptionCatchBlocks = new Stack<>(); + + // element to jump to from inner (sub)expression in "jump to begin" situation. + // E.g. we should jump to "then" branch if condition expression evaluated to true inside if statement + private final StatementStack myStartStatementStack = new StatementStack(); + // element to jump to from inner (sub)expression in "jump to end" situation. + // E.g. we should jump to "else" branch if condition expression evaluated to false inside if statement + private final StatementStack myEndStatementStack = new StatementStack(); + + private final Stack myStartJumpRoles = new Stack<>(); + private final Stack myEndJumpRoles = new Stack<>(); + + private final + @Nonnull + ControlFlowOptions myOptions; + private final boolean myAssignmentTargetsAreElements; + + private final Stack intArrayPool = new Stack<>(); + // map: PsiElement element -> TIntArrayList instructionOffsetsToPatch with getStartOffset(element) + private final Map offsetsAddElementStart = new HashMap<>(); + // map: PsiElement element -> TIntArrayList instructionOffsetsToPatch with getEndOffset(element) + private final Map offsetsAddElementEnd = new HashMap<>(); + private final ControlFlowFactory myControlFlowFactory; + private final List mySubRanges = new ArrayList<>(); + private final PsiConstantEvaluationHelper myConstantEvaluationHelper; + private final Map myImplicitCompactConstructorAssignments; + + ControlFlowAnalyzer( + @Nonnull PsiElement codeFragment, + @Nonnull ControlFlowPolicy policy, + @Nonnull ControlFlowOptions options + ) { + this(codeFragment, policy, options, false); + } + + private ControlFlowAnalyzer( + @Nonnull PsiElement codeFragment, + @Nonnull ControlFlowPolicy policy, + @Nonnull ControlFlowOptions options, + boolean assignmentTargetsAreElements + ) { + myCodeFragment = codeFragment; + myPolicy = policy; + myOptions = options; + myAssignmentTargetsAreElements = assignmentTargetsAreElements; + Project project = codeFragment.getProject(); + myControlFlowFactory = ControlFlowFactory.getInstance(project); + myConstantEvaluationHelper = JavaPsiFacade.getInstance(project).getConstantEvaluationHelper(); + myImplicitCompactConstructorAssignments = getImplicitCompactConstructorAssignmentsMap(); + } + + private Map getImplicitCompactConstructorAssignmentsMap() { + PsiMethod ctor = ObjectUtil.tryCast(myCodeFragment.getParent(), PsiMethod.class); + if (ctor == null || !JavaPsiRecordUtil.isCompactConstructor(ctor)) { + return Collections.emptyMap(); + } + PsiClass containingClass = ctor.getContainingClass(); + if (containingClass == null) { + return Collections.emptyMap(); + } + PsiParameter[] parameters = ctor.getParameterList().getParameters(); + PsiRecordComponent[] components = containingClass.getRecordComponents(); + Map map = new HashMap<>(); + for (int i = 0; i < Math.min(components.length, parameters.length); i++) { + PsiRecordComponent component = components[i]; + PsiField field = JavaPsiRecordUtil.getFieldForComponent(component); + PsiParameter parameter = parameters[i]; + map.put(field, parameter); + } + return map; } @Nonnull - private PsiElement peekElement() { - return myStatements.peek(); + ControlFlow buildControlFlow() throws AnalysisCanceledException { + // push guard outer statement offsets in case when nested expression is incorrect + myStartJumpRoles.push(BranchingInstruction.Role.END); + myEndJumpRoles.push(BranchingInstruction.Role.END); + + myCurrentFlow = new ControlFlowImpl(); + + // guard elements + myStartStatementStack.pushStatement(myCodeFragment, false); + myEndStatementStack.pushStatement(myCodeFragment, false); + + try { + myCodeFragment.accept(this); + return cleanup(); + } + catch (AnalysisCanceledSoftException e) { + throw new AnalysisCanceledException(e.getErrorElement()); + } } - private boolean peekAtStart() { - return myAtStart.get(myAtStart.size() - 1) == 1; + private void generateCompactConstructorAssignments() { + myImplicitCompactConstructorAssignments.values() + .stream() + .filter(myPolicy::isParameterAccepted) + .forEach(this::generateReadInstruction); } - private void pushStatement(@Nonnull PsiElement statement, boolean atStart) { - myStatements.push(statement); - myAtStart.add(atStart ? 1 : 0); - } - } + private static class StatementStack { + private final Stack myStatements = new Stack<>(); + private final IntList myAtStart = IntLists.newArrayList(); - @Nonnull - private IntList getEmptyIntArray() { - if (intArrayPool.isEmpty()) { - return IntLists.newArrayList(1); - } - IntList list = intArrayPool.pop(); - list.clear(); - return list; - } + private void popStatement() { + myAtStart.removeByIndex(myAtStart.size() - 1); + myStatements.pop(); + } - private void poolIntArray(@Nonnull IntList list) { - intArrayPool.add(list); - } + @Nonnull + private PsiElement peekElement() { + return myStatements.peek(); + } - // patch instruction currently added to control flow so that its jump offset corrected on getStartOffset(element) or getEndOffset(element) - // when corresponding element offset become available - private void addElementOffsetLater(@Nonnull PsiElement element, boolean atStart) { - Map offsetsAddElement = atStart ? offsetsAddElementStart : offsetsAddElementEnd; - IntList offsets = offsetsAddElement.get(element); - if (offsets == null) { - offsets = getEmptyIntArray(); - offsetsAddElement.put(element, offsets); - } - int offset = myCurrentFlow.getSize() - 1; - offsets.add(offset); - if (myCurrentFlow.getEndOffset(element) != -1) { - patchInstructionOffsets(element); + private boolean peekAtStart() { + return myAtStart.get(myAtStart.size() - 1) == 1; + } + + private void pushStatement(@Nonnull PsiElement statement, boolean atStart) { + myStatements.push(statement); + myAtStart.add(atStart ? 1 : 0); + } } - } + @Nonnull + private IntList getEmptyIntArray() { + if (intArrayPool.isEmpty()) { + return IntLists.newArrayList(1); + } + IntList list = intArrayPool.pop(); + list.clear(); + return list; + } - private void patchInstructionOffsets(@Nonnull PsiElement element) { - patchInstructionOffsets(offsetsAddElementStart.get(element), myCurrentFlow.getStartOffset(element)); - offsetsAddElementStart.put(element, null); - patchInstructionOffsets(offsetsAddElementEnd.get(element), myCurrentFlow.getEndOffset(element)); - offsetsAddElementEnd.put(element, null); - } + private void poolIntArray(@Nonnull IntList list) { + intArrayPool.add(list); + } - private void patchInstructionOffsets(@Nullable IntList offsets, int add) { - if (offsets == null) { - return; + // patch instruction currently added to control flow so that its jump offset corrected on getStartOffset(element) or getEndOffset(element) + // when corresponding element offset become available + private void addElementOffsetLater(@Nonnull PsiElement element, boolean atStart) { + Map offsetsAddElement = atStart ? offsetsAddElementStart : offsetsAddElementEnd; + IntList offsets = offsetsAddElement.get(element); + if (offsets == null) { + offsets = getEmptyIntArray(); + offsetsAddElement.put(element, offsets); + } + int offset = myCurrentFlow.getSize() - 1; + offsets.add(offset); + if (myCurrentFlow.getEndOffset(element) != -1) { + patchInstructionOffsets(element); + } } - for (int i = 0; i < offsets.size(); i++) { - int offset = offsets.get(i); - BranchingInstruction instruction = (BranchingInstruction) myCurrentFlow.getInstructions().get(offset); - instruction.offset += add; - LOG.assertTrue(instruction.offset >= 0); + + + private void patchInstructionOffsets(@Nonnull PsiElement element) { + patchInstructionOffsets(offsetsAddElementStart.get(element), myCurrentFlow.getStartOffset(element)); + offsetsAddElementStart.put(element, null); + patchInstructionOffsets(offsetsAddElementEnd.get(element), myCurrentFlow.getEndOffset(element)); + offsetsAddElementEnd.put(element, null); } - poolIntArray(offsets); - } - private ControlFlow cleanup() { - // make all non patched goto instructions jump to the end of control flow - for (IntList offsets : offsetsAddElementStart.values()) { - patchInstructionOffsets(offsets, myCurrentFlow.getEndOffset(myCodeFragment)); + private void patchInstructionOffsets(@Nullable IntList offsets, int add) { + if (offsets == null) { + return; + } + for (int i = 0; i < offsets.size(); i++) { + int offset = offsets.get(i); + BranchingInstruction instruction = (BranchingInstruction)myCurrentFlow.getInstructions().get(offset); + instruction.offset += add; + LOG.assertTrue(instruction.offset >= 0); + } + poolIntArray(offsets); } - for (IntList offsets : offsetsAddElementEnd.values()) { - patchInstructionOffsets(offsets, myCurrentFlow.getEndOffset(myCodeFragment)); + + private ControlFlow cleanup() { + // make all non patched goto instructions jump to the end of control flow + for (IntList offsets : offsetsAddElementStart.values()) { + patchInstructionOffsets(offsets, myCurrentFlow.getEndOffset(myCodeFragment)); + } + for (IntList offsets : offsetsAddElementEnd.values()) { + patchInstructionOffsets(offsets, myCurrentFlow.getEndOffset(myCodeFragment)); + } + + ControlFlow result = myCurrentFlow.immutableCopy(); + + // register all sub ranges + for (SubRangeInfo info : mySubRanges) { + ProgressManager.checkCanceled(); + myControlFlowFactory.registerSubRange( + info.myElement, + new ControlFlowSubRange(result, info.myStart, info.myEnd), + myOptions, + myPolicy + ); + } + return result; } - ControlFlow result = myCurrentFlow.immutableCopy(); + private void startElement(@Nonnull PsiElement element) { + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + ProgressManager.checkCanceled(); + if (child instanceof PsiErrorElement + && !Comparing.strEqual(((PsiErrorElement)child).getErrorDescription(), JavaPsiBundle.message("expected.semicolon"))) { + // do not perform control flow analysis for incomplete code + throw new AnalysisCanceledSoftException(element); + } + } + ProgressManager.checkCanceled(); + myCurrentFlow.startElement(element); - // register all sub ranges - for (SubRangeInfo info : mySubRanges) { - ProgressManager.checkCanceled(); - myControlFlowFactory.registerSubRange(info.myElement, new ControlFlowSubRange(result, info.myStart, info.myEnd), myOptions, myPolicy); + generateUncheckedExceptionJumpsIfNeeded(element, true); } - return result; - } - private void startElement(@Nonnull PsiElement element) { - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - ProgressManager.checkCanceled(); - if (child instanceof PsiErrorElement && !Comparing.strEqual(((PsiErrorElement) child).getErrorDescription(), JavaPsiBundle.message("expected.semicolon"))) { - // do not perform control flow analysis for incomplete code - throw new AnalysisCanceledSoftException(element); - } + private void generateUncheckedExceptionJumpsIfNeeded(@Nonnull PsiElement element, boolean atStart) { + if (!myOptions.isExceptionAfterAssignment() && !atStart) { + if (element instanceof PsiExpression) { + PsiElement parent = PsiUtil.skipParenthesizedExprUp(element.getParent()); + if (parent instanceof PsiAssignmentExpression && parent.getParent() instanceof PsiExpressionStatement) { + generateUncheckedExceptionJumps(element, false); + return; + } + } + if (element instanceof PsiCodeBlock || + element instanceof PsiExpressionStatement && + ((PsiExpressionStatement)element).getExpression() instanceof PsiAssignmentExpression) { + return; + } + } + // optimization: reduce number of instructions + boolean isGeneratingStatement = element instanceof PsiStatement && !(element instanceof PsiSwitchLabelStatement); + boolean isGeneratingCodeBlock = element instanceof PsiCodeBlock && !(element.getParent() instanceof PsiSwitchStatement); + if (isGeneratingStatement || isGeneratingCodeBlock) { + generateUncheckedExceptionJumps(element, atStart); + } } - ProgressManager.checkCanceled(); - myCurrentFlow.startElement(element); - generateUncheckedExceptionJumpsIfNeeded(element, true); - } + private void finishElement(@Nonnull PsiElement element) { + generateUncheckedExceptionJumpsIfNeeded(element, false); - private void generateUncheckedExceptionJumpsIfNeeded(@Nonnull PsiElement element, boolean atStart) { - if (!myOptions.isExceptionAfterAssignment() && !atStart) { - if (element instanceof PsiExpression) { - PsiElement parent = PsiUtil.skipParenthesizedExprUp(element.getParent()); - if (parent instanceof PsiAssignmentExpression && parent.getParent() instanceof PsiExpressionStatement) { - generateUncheckedExceptionJumps(element, false); - return; - } - } - if (element instanceof PsiCodeBlock || - element instanceof PsiExpressionStatement && - ((PsiExpressionStatement) element).getExpression() instanceof PsiAssignmentExpression) { - return; - } - } - // optimization: reduce number of instructions - boolean isGeneratingStatement = element instanceof PsiStatement && !(element instanceof PsiSwitchLabelStatement); - boolean isGeneratingCodeBlock = element instanceof PsiCodeBlock && !(element.getParent() instanceof PsiSwitchStatement); - if (isGeneratingStatement || isGeneratingCodeBlock) { - generateUncheckedExceptionJumps(element, atStart); - } - } - - private void finishElement(@Nonnull PsiElement element) { - generateUncheckedExceptionJumpsIfNeeded(element, false); - - myCurrentFlow.finishElement(element); - patchInstructionOffsets(element); - } - - - private void generateUncheckedExceptionJumps(@Nonnull PsiElement element, boolean atStart) { - // optimization: if we just generated all necessary jumps, do not generate it once again - if (atStart - && element instanceof PsiStatement - && element.getParent() instanceof PsiCodeBlock && element.getPrevSibling() != null) { - return; + myCurrentFlow.finishElement(element); + patchInstructionOffsets(element); } - for (int i = myUnhandledExceptionCatchBlocks.size() - 1; i >= 0; i--) { - ProgressManager.checkCanceled(); - PsiElement block = myUnhandledExceptionCatchBlocks.get(i); - // cannot jump to outer catch blocks (belonging to outer try stmt) if current try{} has 'finally' block - if (block == null) { + + private void generateUncheckedExceptionJumps(@Nonnull PsiElement element, boolean atStart) { + // optimization: if we just generated all necessary jumps, do not generate it once again + if (atStart + && element instanceof PsiStatement + && element.getParent() instanceof PsiCodeBlock && element.getPrevSibling() != null) { + return; + } + + for (int i = myUnhandledExceptionCatchBlocks.size() - 1; i >= 0; i--) { + ProgressManager.checkCanceled(); + PsiElement block = myUnhandledExceptionCatchBlocks.get(i); + // cannot jump to outer catch blocks (belonging to outer try stmt) if current try{} has 'finally' block + if (block == null) { + if (!myFinallyBlocks.isEmpty()) { + break; + } + else { + continue; + } + } + ConditionalThrowToInstruction throwToInstruction = new ConditionalThrowToInstruction(-1); // -1 for init parameter + myCurrentFlow.addInstruction(throwToInstruction); + if (!patchUncheckedThrowInstructionIfInsideFinally(throwToInstruction, element, block)) { + addElementOffsetLater(block, true); + } + } + + // generate a jump to the top 'finally' block if (!myFinallyBlocks.isEmpty()) { - break; - } else { - continue; - } - } - ConditionalThrowToInstruction throwToInstruction = new ConditionalThrowToInstruction(-1); // -1 for init parameter - myCurrentFlow.addInstruction(throwToInstruction); - if (!patchUncheckedThrowInstructionIfInsideFinally(throwToInstruction, element, block)) { - addElementOffsetLater(block, true); - } - } - - // generate a jump to the top 'finally' block - if (!myFinallyBlocks.isEmpty()) { - final PsiElement finallyBlock = myFinallyBlocks.peek().getElement(); - ConditionalThrowToInstruction throwToInstruction = new ConditionalThrowToInstruction(-2); - myCurrentFlow.addInstruction(throwToInstruction); - if (!patchUncheckedThrowInstructionIfInsideFinally(throwToInstruction, element, finallyBlock)) { - addElementOffsetLater(finallyBlock, true); - } - } - } - - private void generateCheckedExceptionJumps(@Nonnull PsiElement element) { - //generate jumps to all handled exception handlers - generateExceptionJumps(element, ExceptionUtil.collectUnhandledExceptions(element, element.getParent())); - } - - private void generateExceptionJumps(@Nonnull PsiElement element, Collection unhandledExceptions) { - for (PsiClassType unhandledException : unhandledExceptions) { - ProgressManager.checkCanceled(); - generateThrow(unhandledException, element); - } - } - - private void generateThrow(@Nonnull PsiClassType unhandledException, @Nonnull PsiElement throwingElement) { - final List catchBlocks = findThrowToBlocks(unhandledException); - for (PsiElement block : catchBlocks) { - ProgressManager.checkCanceled(); - ConditionalThrowToInstruction instruction = new ConditionalThrowToInstruction(0); - myCurrentFlow.addInstruction(instruction); - if (!patchCheckedThrowInstructionIfInsideFinally(instruction, throwingElement, block)) { - if (block == null) { - addElementOffsetLater(myCodeFragment, false); - } else { - instruction.offset--; // -1 for catch block param init - addElementOffsetLater(block, true); - } - } - } - } - - private final Map> finallyBlockToUnhandledExceptions = new HashMap<>(); - - private boolean patchCheckedThrowInstructionIfInsideFinally(@Nonnull ConditionalThrowToInstruction instruction, - @Nonnull PsiElement throwingElement, - PsiElement elementToJumpTo) { - final PsiElement finallyBlock = findEnclosingFinallyBlockElement(throwingElement, elementToJumpTo); - if (finallyBlock == null) { - return false; - } - - List unhandledExceptionCatchBlocks = - finallyBlockToUnhandledExceptions.computeIfAbsent(finallyBlock, k -> new ArrayList<>()); - int index = unhandledExceptionCatchBlocks.indexOf(elementToJumpTo); - if (index == -1) { - index = unhandledExceptionCatchBlocks.size(); - unhandledExceptionCatchBlocks.add(elementToJumpTo); - } - // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. - instruction.offset = 3 + index; - addElementOffsetLater(finallyBlock, false); - - return true; - } - - private boolean patchUncheckedThrowInstructionIfInsideFinally(@Nonnull ConditionalThrowToInstruction instruction, - @Nonnull PsiElement throwingElement, - @Nonnull PsiElement elementToJumpTo) { - final PsiElement finallyBlock = findEnclosingFinallyBlockElement(throwingElement, elementToJumpTo); - if (finallyBlock == null) { - return false; - } - - // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. - instruction.offset = 2; - addElementOffsetLater(finallyBlock, false); - - return true; - } - - @Override - public void visitCodeFragment(JavaCodeFragment codeFragment) { - startElement(codeFragment); - int prevOffset = myCurrentFlow.getSize(); - PsiElement[] children = codeFragment.getChildren(); - for (PsiElement child : children) { - ProgressManager.checkCanceled(); - child.accept(this); - } - - finishElement(codeFragment); - registerSubRange(codeFragment, prevOffset); - } - - private void registerSubRange(@Nonnull PsiElement codeFragment, final int startOffset) { - mySubRanges.add(new SubRangeInfo(codeFragment, startOffset, myCurrentFlow.getSize())); - } - - @Override - public void visitCodeBlock(PsiCodeBlock block) { - startElement(block); - int prevOffset = myCurrentFlow.getSize(); - PsiStatement[] statements = block.getStatements(); - for (PsiStatement statement : statements) { - ProgressManager.checkCanceled(); - statement.accept(this); - } - - //each statement should contain at least one instruction in order to getElement(offset) work - int nextOffset = myCurrentFlow.getSize(); - if (!(block.getParent() instanceof PsiSwitchStatement) && prevOffset == nextOffset) { - emitEmptyInstruction(); - } - if (block == myCodeFragment) { - generateCompactConstructorAssignments(); - } - - finishElement(block); - if (prevOffset != 0) { - registerSubRange(block, prevOffset); - } - } - - private void emitEmptyInstruction() { - myCurrentFlow.addInstruction(EmptyInstruction.INSTANCE); - } - - @Override - public void visitFile(@Nonnull PsiFile file) { - visitChildren(file); - } - - @Override - public void visitBlockStatement(PsiBlockStatement statement) { - startElement(statement); - final PsiCodeBlock codeBlock = statement.getCodeBlock(); - codeBlock.accept(this); - finishElement(statement); - } - - @Override - public void visitBreakStatement(PsiBreakStatement statement) { - generateYieldInstructions(statement, null, statement.findExitedStatement()); - } - - @Override - public void visitYieldStatement(PsiYieldStatement statement) { - generateYieldInstructions(statement, statement.getExpression(), statement.findEnclosingExpression()); - } - - private void generateYieldInstructions(PsiStatement statement, PsiExpression valueExpression, PsiElement exitedStatement) { - startElement(statement); - generateExpressionInstructions(valueExpression); - - if (exitedStatement != null) { - callFinallyBlocksOnExit(exitedStatement); - - final Instruction instruction; - final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, exitedStatement); - final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); - if (finallyBlock != null && finallyStartOffset != -1) { - // go out of finally, use return - CallInstruction callInstruction = (CallInstruction) myCurrentFlow.getInstructions().get(finallyStartOffset - 2); - instruction = new ReturnInstruction(0, callInstruction); - } else { - instruction = new GoToInstruction(0, BranchingInstruction.Role.END, PsiTreeUtil.isAncestor(exitedStatement, myCodeFragment, true)); - } - myCurrentFlow.addInstruction(instruction); - // exited statement might be out of control flow analyzed - addElementOffsetLater(exitedStatement, false); - } - finishElement(statement); - } - - private void callFinallyBlocksOnExit(PsiElement exitedStatement) { - for (final ListIterator it = myFinallyBlocks.listIterator(myFinallyBlocks.size()); it.hasPrevious(); ) { - final FinallyBlockSubroutine finallyBlockSubroutine = it.previous(); - PsiElement finallyBlock = finallyBlockSubroutine.getElement(); - final PsiElement enclosingTryStatement = finallyBlock.getParent(); - if (enclosingTryStatement == null || !PsiTreeUtil.isAncestor(exitedStatement, enclosingTryStatement, false)) { - break; - } - CallInstruction instruction = new CallInstruction(0, 0); - finallyBlockSubroutine.addCall(instruction); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(finallyBlock, true); - } - } - - private PsiElement findEnclosingFinallyBlockElement(@Nonnull PsiElement sourceElement, @Nullable PsiElement jumpElement) { - PsiElement element = sourceElement; - while (element != null && !(element instanceof PsiFile)) { - if (element instanceof PsiCodeBlock - && element.getParent() instanceof PsiTryStatement - && ((PsiTryStatement) element.getParent()).getFinallyBlock() == element) { - // element maybe out of scope to be analyzed - if (myCurrentFlow.getStartOffset(element.getParent()) == -1) { - return null; - } - if (jumpElement == null || !PsiTreeUtil.isAncestor(element, jumpElement, false)) { - return element; - } - } - element = element.getParent(); - } - return null; - } - - @Override - public void visitContinueStatement(PsiContinueStatement statement) { - startElement(statement); - PsiStatement continuedStatement = statement.findContinuedStatement(); - if (continuedStatement != null) { - PsiElement body = null; - if (continuedStatement instanceof PsiLoopStatement) { - body = ((PsiLoopStatement) continuedStatement).getBody(); - } - if (body == null) { - body = myCodeFragment; - } - callFinallyBlocksOnExit(continuedStatement); - - final Instruction instruction; - final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, continuedStatement); - final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); - if (finallyBlock != null && finallyStartOffset != -1) { - // go out of finally, use return - CallInstruction callInstruction = (CallInstruction) myCurrentFlow.getInstructions().get(finallyStartOffset - 2); - instruction = new ReturnInstruction(0, callInstruction); - } else { - instruction = new GoToInstruction(0, BranchingInstruction.Role.END, PsiTreeUtil.isAncestor(body, myCodeFragment, true)); - } - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(body, false); - } - finishElement(statement); - } - - @Override - public void visitDeclarationStatement(PsiDeclarationStatement statement) { - startElement(statement); - int pc = myCurrentFlow.getSize(); - PsiElement[] elements = statement.getDeclaredElements(); - for (PsiElement element : elements) { - ProgressManager.checkCanceled(); - if (element instanceof PsiClass) { - element.accept(this); - } else if (element instanceof PsiVariable) { - processVariable((PsiVariable) element); - } - } - if (pc == myCurrentFlow.getSize()) { - // generate at least one instruction for declaration - emitEmptyInstruction(); - } - finishElement(statement); - } - - private void processVariable(@Nonnull PsiVariable element) { - final PsiExpression initializer = element.getInitializer(); - generateExpressionInstructions(initializer); - - if (element instanceof PsiLocalVariable && initializer != null || - element instanceof PsiField) { - if (element instanceof PsiLocalVariable && !myPolicy.isLocalVariableAccepted((PsiLocalVariable) element)) { - return; - } - - if (myAssignmentTargetsAreElements) { - startElement(element); - } + final PsiElement finallyBlock = myFinallyBlocks.peek().getElement(); + ConditionalThrowToInstruction throwToInstruction = new ConditionalThrowToInstruction(-2); + myCurrentFlow.addInstruction(throwToInstruction); + if (!patchUncheckedThrowInstructionIfInsideFinally(throwToInstruction, element, finallyBlock)) { + addElementOffsetLater(finallyBlock, true); + } + } + } - generateWriteInstruction(element); + private void generateCheckedExceptionJumps(@Nonnull PsiElement element) { + //generate jumps to all handled exception handlers + generateExceptionJumps(element, ExceptionUtil.collectUnhandledExceptions(element, element.getParent())); + } - if (myAssignmentTargetsAreElements) { - finishElement(element); - } + private void generateExceptionJumps(@Nonnull PsiElement element, Collection unhandledExceptions) { + for (PsiClassType unhandledException : unhandledExceptions) { + ProgressManager.checkCanceled(); + generateThrow(unhandledException, element); + } + } + + private void generateThrow(@Nonnull PsiClassType unhandledException, @Nonnull PsiElement throwingElement) { + final List catchBlocks = findThrowToBlocks(unhandledException); + for (PsiElement block : catchBlocks) { + ProgressManager.checkCanceled(); + ConditionalThrowToInstruction instruction = new ConditionalThrowToInstruction(0); + myCurrentFlow.addInstruction(instruction); + if (!patchCheckedThrowInstructionIfInsideFinally(instruction, throwingElement, block)) { + if (block == null) { + addElementOffsetLater(myCodeFragment, false); + } + else { + instruction.offset--; // -1 for catch block param init + addElementOffsetLater(block, true); + } + } + } } - } - @Override - public void visitDoWhileStatement(PsiDoWhileStatement statement) { - startElement(statement); - PsiStatement body = statement.getBody(); - myStartStatementStack.pushStatement(body == null ? statement : body, true); - myEndStatementStack.pushStatement(statement, false); + private final Map> finallyBlockToUnhandledExceptions = new HashMap<>(); + + private boolean patchCheckedThrowInstructionIfInsideFinally( + @Nonnull ConditionalThrowToInstruction instruction, + @Nonnull PsiElement throwingElement, + PsiElement elementToJumpTo + ) { + final PsiElement finallyBlock = findEnclosingFinallyBlockElement(throwingElement, elementToJumpTo); + if (finallyBlock == null) { + return false; + } + + List unhandledExceptionCatchBlocks = + finallyBlockToUnhandledExceptions.computeIfAbsent(finallyBlock, k -> new ArrayList<>()); + int index = unhandledExceptionCatchBlocks.indexOf(elementToJumpTo); + if (index == -1) { + index = unhandledExceptionCatchBlocks.size(); + unhandledExceptionCatchBlocks.add(elementToJumpTo); + } + // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. + instruction.offset = 3 + index; + addElementOffsetLater(finallyBlock, false); - if (body != null) { - body.accept(this); + return true; } - PsiExpression condition = statement.getCondition(); - if (condition != null) { - condition.accept(this); + private boolean patchUncheckedThrowInstructionIfInsideFinally( + @Nonnull ConditionalThrowToInstruction instruction, + @Nonnull PsiElement throwingElement, + @Nonnull PsiElement elementToJumpTo + ) { + final PsiElement finallyBlock = findEnclosingFinallyBlockElement(throwingElement, elementToJumpTo); + if (finallyBlock == null) { + return false; + } + + // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. + instruction.offset = 2; + addElementOffsetLater(finallyBlock, false); + + return true; } - int offset = myCurrentFlow.getStartOffset(statement); + @Override + public void visitCodeFragment(JavaCodeFragment codeFragment) { + startElement(codeFragment); + int prevOffset = myCurrentFlow.getSize(); + PsiElement[] children = codeFragment.getChildren(); + for (PsiElement child : children) { + ProgressManager.checkCanceled(); + child.accept(this); + } - Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(statement.getCondition()); - if (loopCondition instanceof Boolean) { - if (((Boolean) loopCondition).booleanValue()) { - myCurrentFlow.addInstruction(new GoToInstruction(offset)); - } else { - emitEmptyInstruction(); - } - } else { - Instruction instruction = new ConditionalGoToInstruction(offset, statement.getCondition()); - myCurrentFlow.addInstruction(instruction); - } - - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - finishElement(statement); - } - - @Override - public void visitEmptyStatement(PsiEmptyStatement statement) { - startElement(statement); - emitEmptyInstruction(); - - finishElement(statement); - } - - @Override - public void visitExpressionStatement(PsiExpressionStatement statement) { - startElement(statement); - final PsiExpression expression = statement.getExpression(); - expression.accept(this); - - for (PsiParameter catchParameter : myCatchParameters) { - ProgressManager.checkCanceled(); - if (myUnhandledExceptionCatchBlocks.contains(((PsiCatchSection) catchParameter.getDeclarationScope()).getCatchBlock())) { - continue; - } - PsiType type = catchParameter.getType(); - List types = - type instanceof PsiDisjunctionType ? ((PsiDisjunctionType) type).getDisjunctions() : Collections.singletonList(type); - for (PsiType subType : types) { - if (subType instanceof PsiClassType) { - generateThrow((PsiClassType) subType, statement); - } - } - } - finishElement(statement); - } - - @Override - public void visitExpressionListStatement(PsiExpressionListStatement statement) { - startElement(statement); - PsiExpression[] expressions = statement.getExpressionList().getExpressions(); - for (PsiExpression expr : expressions) { - ProgressManager.checkCanceled(); - expr.accept(this); - } - finishElement(statement); - } - - @Override - public void visitField(PsiField field) { - final PsiExpression initializer = field.getInitializer(); - if (initializer != null) { - startElement(field); - initializer.accept(this); - finishElement(field); - } - } - - @Override - public void visitForStatement(PsiForStatement statement) { - startElement(statement); - PsiStatement body = statement.getBody(); - myStartStatementStack.pushStatement(body == null ? statement : body, false); - myEndStatementStack.pushStatement(statement, false); - - PsiStatement initialization = statement.getInitialization(); - if (initialization != null) { - initialization.accept(this); - } - - PsiExpression condition = statement.getCondition(); - if (condition != null) { - condition.accept(this); - } - - - Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(condition); - if (loopCondition instanceof Boolean || condition == null) { - boolean value = condition == null || ((Boolean) loopCondition).booleanValue(); - if (value) { - emitEmptyInstruction(); - } else { - myCurrentFlow.addInstruction(new GoToInstruction(0)); - addElementOffsetLater(statement, false); - } - } else { - Instruction instruction = new ConditionalGoToInstruction(0, statement.getCondition()); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(statement, false); - } - - if (body != null) { - body.accept(this); - } - - PsiStatement update = statement.getUpdate(); - if (update != null) { - update.accept(this); - } - - int offset = initialization != null - ? myCurrentFlow.getEndOffset(initialization) - : myCurrentFlow.getStartOffset(statement); - Instruction instruction = new GoToInstruction(offset); - myCurrentFlow.addInstruction(instruction); - - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - finishElement(statement); - } - - @Override - public void visitForeachStatement(PsiForeachStatement statement) { - startElement(statement); - final PsiStatement body = statement.getBody(); - myStartStatementStack.pushStatement(body == null ? statement : body, false); - myEndStatementStack.pushStatement(statement, false); - final PsiExpression iteratedValue = statement.getIteratedValue(); - if (iteratedValue != null) { - iteratedValue.accept(this); - } - - final int gotoTarget = myCurrentFlow.getSize(); - Instruction instruction = new ConditionalGoToInstruction(0, statement.getIteratedValue()); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(statement, false); - - final PsiParameter iterationParameter = statement.getIterationParameter(); - if (myPolicy.isParameterAccepted(iterationParameter)) { - generateWriteInstruction(iterationParameter); - } - if (body != null) { - body.accept(this); - } - - final GoToInstruction gotoInstruction = new GoToInstruction(gotoTarget); - myCurrentFlow.addInstruction(gotoInstruction); - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - finishElement(statement); - } - - @Override - public void visitIfStatement(PsiIfStatement statement) { - startElement(statement); - - final PsiStatement elseBranch = statement.getElseBranch(); - final PsiStatement thenBranch = statement.getThenBranch(); - PsiExpression conditionExpression = statement.getCondition(); - - generateConditionalStatementInstructions(statement, conditionExpression, thenBranch, elseBranch); - - finishElement(statement); - } - - private void generateConditionalStatementInstructions(@Nonnull PsiElement statement, - @Nullable PsiExpression conditionExpression, - final PsiElement thenBranch, - final PsiElement elseBranch) { - if (thenBranch == null) { - myStartStatementStack.pushStatement(statement, false); - } else { - myStartStatementStack.pushStatement(thenBranch, true); - } - if (elseBranch == null) { - myEndStatementStack.pushStatement(statement, false); - } else { - myEndStatementStack.pushStatement(elseBranch, true); - } - - myEndJumpRoles.push(elseBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.ELSE); - myStartJumpRoles.push(thenBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.THEN); - - if (conditionExpression != null) { - conditionExpression.accept(this); - } - - boolean thenReachable = true; - boolean generateConditionalJump = true; - /* - * if() statement generated instructions outline: - * 'if (C) { A } [ else { B } ]' : - * generate (C) - * cond_goto else - * generate (A) - * [ goto end ] - * :else - * [ generate (B) ] - * :end - */ - if (myOptions.shouldEvaluateConstantIfCondition()) { - final Object value = myConstantEvaluationHelper.computeConstantExpression(conditionExpression); - if (value instanceof Boolean) { - thenReachable = ((Boolean) value).booleanValue(); - generateConditionalJump = false; - myCurrentFlow.setConstantConditionOccurred(true); - } - } - if (generateConditionalJump || !thenReachable) { - BranchingInstruction.Role role = elseBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.ELSE; - Instruction instruction = generateConditionalJump ? new ConditionalGoToInstruction(0, role, conditionExpression) : - new GoToInstruction(0, role); - myCurrentFlow.addInstruction(instruction); - if (elseBranch == null) { - addElementOffsetLater(statement, false); - } else { - addElementOffsetLater(elseBranch, true); - } - } - if (thenBranch != null) { - thenBranch.accept(this); - } - if (elseBranch != null) { - Instruction instruction = new GoToInstruction(0); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(statement, false); - elseBranch.accept(this); - } - - myStartJumpRoles.pop(); - myEndJumpRoles.pop(); - - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - } - - @Override - public void visitLabeledStatement(PsiLabeledStatement statement) { - startElement(statement); - final PsiStatement innerStatement = statement.getStatement(); - if (innerStatement != null) { - innerStatement.accept(this); - } - finishElement(statement); - } - - @Override - public void visitReturnStatement(PsiReturnStatement statement) { - startElement(statement); - PsiExpression returnValue = statement.getReturnValue(); - - if (returnValue != null) { - myStartStatementStack.pushStatement(returnValue, false); - myEndStatementStack.pushStatement(returnValue, false); - returnValue.accept(this); - } - addReturnInstruction(statement); - if (returnValue != null) { - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - } - - finishElement(statement); - } - - private void addReturnInstruction(@Nonnull PsiElement statement) { - BranchingInstruction instruction; - final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, null); - final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); - if (finallyBlock != null && finallyStartOffset != -1) { - // go out of finally, go to 2nd return after finally block - // second return is for return statement called completion - instruction = new GoToInstruction(1, BranchingInstruction.Role.END, true); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(finallyBlock, false); - } else { - instruction = new GoToInstruction(0, BranchingInstruction.Role.END, true); - myCurrentFlow.addInstruction(instruction); - if (myFinallyBlocks.isEmpty()) { - addElementOffsetLater(myCodeFragment, false); - } else { - instruction.offset = -4; // -4 for return - addElementOffsetLater(myFinallyBlocks.peek().getElement(), true); - } - } - } - - @Override - public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) { - startElement(statement); - generateCaseValueInstructions(statement.getCaseValues()); - finishElement(statement); - } - - @Override - public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) { - startElement(statement); - - generateCaseValueInstructions(statement.getCaseValues()); - - PsiStatement body = statement.getBody(); - if (body != null) { - body.accept(this); - } - - PsiSwitchBlock switchBlock = statement.getEnclosingSwitchBlock(); - if (switchBlock != null) { - Instruction instruction = - new GoToInstruction(0, BranchingInstruction.Role.END, PsiTreeUtil.isAncestor(switchBlock, myCodeFragment, true)); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(switchBlock, false); - } - - finishElement(statement); - } - - private void generateCaseValueInstructions(@Nullable PsiExpressionList values) { - if (values != null) { - for (PsiExpression caseValue : values.getExpressions()) { - ProgressManager.checkCanceled(); - generateExpressionInstructions(caseValue); - } + finishElement(codeFragment); + registerSubRange(codeFragment, prevOffset); } - } - @Override - public void visitSwitchStatement(PsiSwitchStatement statement) { - generateSwitchBlockInstructions(statement); - } + private void registerSubRange(@Nonnull PsiElement codeFragment, final int startOffset) { + mySubRanges.add(new SubRangeInfo(codeFragment, startOffset, myCurrentFlow.getSize())); + } - @Override - public void visitSwitchExpression(PsiSwitchExpression expression) { - generateSwitchBlockInstructions(expression); - } + @Override + public void visitCodeBlock(PsiCodeBlock block) { + startElement(block); + int prevOffset = myCurrentFlow.getSize(); + PsiStatement[] statements = block.getStatements(); + for (PsiStatement statement : statements) { + ProgressManager.checkCanceled(); + statement.accept(this); + } - public void generateSwitchBlockInstructions(PsiSwitchBlock statement) { - startElement(statement); + //each statement should contain at least one instruction in order to getElement(offset) work + int nextOffset = myCurrentFlow.getSize(); + if (!(block.getParent() instanceof PsiSwitchStatement) && prevOffset == nextOffset) { + emitEmptyInstruction(); + } + if (block == myCodeFragment) { + generateCompactConstructorAssignments(); + } - PsiExpression expr = statement.getExpression(); - if (expr != null) { - expr.accept(this); + finishElement(block); + if (prevOffset != 0) { + registerSubRange(block, prevOffset); + } } - PsiCodeBlock body = statement.getBody(); - if (body != null) { - PsiStatement[] statements = body.getStatements(); - PsiSwitchLabelStatementBase defaultLabel = null; - for (PsiStatement aStatement : statements) { - ProgressManager.checkCanceled(); - if (aStatement instanceof PsiSwitchLabelStatementBase) { - if (((PsiSwitchLabelStatementBase) aStatement).isDefaultCase()) { - defaultLabel = (PsiSwitchLabelStatementBase) aStatement; - } - Instruction instruction = new ConditionalGoToInstruction(0, expr); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(aStatement, true); - } - } - if (defaultLabel == null) { - Instruction instruction = new GoToInstruction(0); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(body, false); - } + private void emitEmptyInstruction() { + myCurrentFlow.addInstruction(EmptyInstruction.INSTANCE); + } - body.accept(this); + @Override + public void visitFile(@Nonnull PsiFile file) { + visitChildren(file); } - finishElement(statement); - } + @Override + public void visitBlockStatement(PsiBlockStatement statement) { + startElement(statement); + final PsiCodeBlock codeBlock = statement.getCodeBlock(); + codeBlock.accept(this); + finishElement(statement); + } - @Override - public void visitSynchronizedStatement(PsiSynchronizedStatement statement) { - startElement(statement); + @Override + public void visitBreakStatement(PsiBreakStatement statement) { + generateYieldInstructions(statement, null, statement.findExitedStatement()); + } - PsiExpression lock = statement.getLockExpression(); - if (lock != null) { - lock.accept(this); + @Override + public void visitYieldStatement(PsiYieldStatement statement) { + generateYieldInstructions(statement, statement.getExpression(), statement.findEnclosingExpression()); } - PsiCodeBlock body = statement.getBody(); - if (body != null) { - body.accept(this); + private void generateYieldInstructions(PsiStatement statement, PsiExpression valueExpression, PsiElement exitedStatement) { + startElement(statement); + generateExpressionInstructions(valueExpression); + + if (exitedStatement != null) { + callFinallyBlocksOnExit(exitedStatement); + + final Instruction instruction; + final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, exitedStatement); + final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); + if (finallyBlock != null && finallyStartOffset != -1) { + // go out of finally, use return + CallInstruction callInstruction = (CallInstruction)myCurrentFlow.getInstructions().get(finallyStartOffset - 2); + instruction = new ReturnInstruction(0, callInstruction); + } + else { + instruction = + new GoToInstruction(0, BranchingInstruction.Role.END, PsiTreeUtil.isAncestor(exitedStatement, myCodeFragment, true)); + } + myCurrentFlow.addInstruction(instruction); + // exited statement might be out of control flow analyzed + addElementOffsetLater(exitedStatement, false); + } + finishElement(statement); } - finishElement(statement); - } + private void callFinallyBlocksOnExit(PsiElement exitedStatement) { + for (final ListIterator it = myFinallyBlocks.listIterator(myFinallyBlocks.size()); it.hasPrevious(); ) { + final FinallyBlockSubroutine finallyBlockSubroutine = it.previous(); + PsiElement finallyBlock = finallyBlockSubroutine.getElement(); + final PsiElement enclosingTryStatement = finallyBlock.getParent(); + if (enclosingTryStatement == null || !PsiTreeUtil.isAncestor(exitedStatement, enclosingTryStatement, false)) { + break; + } + CallInstruction instruction = new CallInstruction(0, 0); + finallyBlockSubroutine.addCall(instruction); + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(finallyBlock, true); + } + } - @Override - public void visitThrowStatement(PsiThrowStatement statement) { - startElement(statement); + private PsiElement findEnclosingFinallyBlockElement(@Nonnull PsiElement sourceElement, @Nullable PsiElement jumpElement) { + PsiElement element = sourceElement; + while (element != null && !(element instanceof PsiFile)) { + if (element instanceof PsiCodeBlock + && element.getParent() instanceof PsiTryStatement + && ((PsiTryStatement)element.getParent()).getFinallyBlock() == element) { + // element maybe out of scope to be analyzed + if (myCurrentFlow.getStartOffset(element.getParent()) == -1) { + return null; + } + if (jumpElement == null || !PsiTreeUtil.isAncestor(element, jumpElement, false)) { + return element; + } + } + element = element.getParent(); + } + return null; + } - PsiExpression exception = statement.getException(); - if (exception != null) { - exception.accept(this); + @Override + public void visitContinueStatement(PsiContinueStatement statement) { + startElement(statement); + PsiStatement continuedStatement = statement.findContinuedStatement(); + if (continuedStatement != null) { + PsiElement body = null; + if (continuedStatement instanceof PsiLoopStatement) { + body = ((PsiLoopStatement)continuedStatement).getBody(); + } + if (body == null) { + body = myCodeFragment; + } + callFinallyBlocksOnExit(continuedStatement); + + final Instruction instruction; + final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, continuedStatement); + final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); + if (finallyBlock != null && finallyStartOffset != -1) { + // go out of finally, use return + CallInstruction callInstruction = (CallInstruction)myCurrentFlow.getInstructions().get(finallyStartOffset - 2); + instruction = new ReturnInstruction(0, callInstruction); + } + else { + instruction = new GoToInstruction(0, BranchingInstruction.Role.END, PsiTreeUtil.isAncestor(body, myCodeFragment, true)); + } + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(body, false); + } + finishElement(statement); + } + + @Override + public void visitDeclarationStatement(PsiDeclarationStatement statement) { + startElement(statement); + int pc = myCurrentFlow.getSize(); + PsiElement[] elements = statement.getDeclaredElements(); + for (PsiElement element : elements) { + ProgressManager.checkCanceled(); + if (element instanceof PsiClass) { + element.accept(this); + } + else if (element instanceof PsiVariable) { + processVariable((PsiVariable)element); + } + } + if (pc == myCurrentFlow.getSize()) { + // generate at least one instruction for declaration + emitEmptyInstruction(); + } + finishElement(statement); } - final List blocks = findThrowToBlocks(statement); - addThrowInstructions(blocks); - finishElement(statement); - } + private void processVariable(@Nonnull PsiVariable element) { + final PsiExpression initializer = element.getInitializer(); + generateExpressionInstructions(initializer); - private void addThrowInstructions(@Nonnull List blocks) { - PsiElement element; - if (blocks.isEmpty() || blocks.get(0) == null) { - ThrowToInstruction instruction = new ThrowToInstruction(0); - myCurrentFlow.addInstruction(instruction); - if (myFinallyBlocks.isEmpty()) { - element = myCodeFragment; - addElementOffsetLater(element, false); - } else { - instruction.offset = -2; // -2 to rethrow exception - element = myFinallyBlocks.peek().getElement(); - addElementOffsetLater(element, true); - } - } else { - for (int i = 0; i < blocks.size(); i++) { - ProgressManager.checkCanceled(); - element = blocks.get(i); - BranchingInstruction instruction = i == blocks.size() - 1 - ? new ThrowToInstruction(0) - : new ConditionalThrowToInstruction(0); - myCurrentFlow.addInstruction(instruction); - instruction.offset = -1; // -1 to init catch param - addElementOffsetLater(element, true); - } - } - } - - /** - * Find offsets of catch(es) corresponding to this throw statement - * myCatchParameters and myCatchBlocks arrays should be sorted in ascending scope order (from outermost to innermost) - * - * @return list of targets or list of single null element if no appropriate targets found - */ - @Nonnull - private List findThrowToBlocks(@Nonnull PsiThrowStatement statement) { - final PsiExpression exceptionExpr = statement.getException(); - if (exceptionExpr == null) { - return Collections.emptyList(); - } - final PsiType throwType = exceptionExpr.getType(); - if (!(throwType instanceof PsiClassType)) { - return Collections.emptyList(); - } - return findThrowToBlocks((PsiClassType) throwType); - } - - @Nonnull - private List findThrowToBlocks(@Nonnull PsiClassType throwType) { - List blocks = new ArrayList<>(); - for (int i = myCatchParameters.size() - 1; i >= 0; i--) { - ProgressManager.checkCanceled(); - PsiParameter parameter = myCatchParameters.get(i); - PsiType catchType = parameter.getType(); - if (ControlFlowUtil.isCaughtExceptionType(throwType, catchType)) { - blocks.add(myCatchBlocks.get(i)); - } - } - if (blocks.isEmpty()) { - // consider it as throw at the end of the control flow - blocks.add(null); - } - return blocks; - } - - @Override - public void visitAssertStatement(PsiAssertStatement statement) { - startElement(statement); - - myStartStatementStack.pushStatement(statement, false); - myEndStatementStack.pushStatement(statement, false); - Instruction passByWhenAssertionsDisabled = new ConditionalGoToInstruction(0, BranchingInstruction.Role.END, null); - myCurrentFlow.addInstruction(passByWhenAssertionsDisabled); - addElementOffsetLater(statement, false); - - final PsiExpression condition = statement.getAssertCondition(); - boolean generateCondition = true; - boolean throwReachable = true; - if (myOptions.shouldEvaluateConstantIfCondition()) { - Object conditionValue = myConstantEvaluationHelper.computeConstantExpression(condition); - if (conditionValue instanceof Boolean) { - throwReachable = !((Boolean) conditionValue); - generateCondition = false; + if (element instanceof PsiLocalVariable && initializer != null || + element instanceof PsiField) { + if (element instanceof PsiLocalVariable && !myPolicy.isLocalVariableAccepted((PsiLocalVariable)element)) { + return; + } + + if (myAssignmentTargetsAreElements) { + startElement(element); + } + + generateWriteInstruction(element); + + if (myAssignmentTargetsAreElements) { + finishElement(element); + } + } + } + + @Override + public void visitDoWhileStatement(PsiDoWhileStatement statement) { + startElement(statement); + PsiStatement body = statement.getBody(); + myStartStatementStack.pushStatement(body == null ? statement : body, true); + myEndStatementStack.pushStatement(statement, false); + + if (body != null) { + body.accept(this); + } + + PsiExpression condition = statement.getCondition(); + if (condition != null) { + condition.accept(this); + } + + int offset = myCurrentFlow.getStartOffset(statement); + + Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(statement.getCondition()); + if (loopCondition instanceof Boolean) { + if (((Boolean)loopCondition).booleanValue()) { + myCurrentFlow.addInstruction(new GoToInstruction(offset)); + } + else { + emitEmptyInstruction(); + } + } + else { + Instruction instruction = new ConditionalGoToInstruction(offset, statement.getCondition()); + myCurrentFlow.addInstruction(instruction); + } + + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + finishElement(statement); + } + + @Override + public void visitEmptyStatement(PsiEmptyStatement statement) { + startElement(statement); emitEmptyInstruction(); - } + + finishElement(statement); } - if (generateCondition) { - if (condition != null) { - myStartStatementStack.pushStatement(statement, false); + @Override + public void visitExpressionStatement(PsiExpressionStatement statement) { + startElement(statement); + final PsiExpression expression = statement.getExpression(); + expression.accept(this); + + for (PsiParameter catchParameter : myCatchParameters) { + ProgressManager.checkCanceled(); + if (myUnhandledExceptionCatchBlocks.contains(((PsiCatchSection)catchParameter.getDeclarationScope()).getCatchBlock())) { + continue; + } + PsiType type = catchParameter.getType(); + List types = + type instanceof PsiDisjunctionType ? ((PsiDisjunctionType)type).getDisjunctions() : Collections.singletonList(type); + for (PsiType subType : types) { + if (subType instanceof PsiClassType) { + generateThrow((PsiClassType)subType, statement); + } + } + } + finishElement(statement); + } + + @Override + public void visitExpressionListStatement(PsiExpressionListStatement statement) { + startElement(statement); + PsiExpression[] expressions = statement.getExpressionList().getExpressions(); + for (PsiExpression expr : expressions) { + ProgressManager.checkCanceled(); + expr.accept(this); + } + finishElement(statement); + } + + @Override + public void visitField(PsiField field) { + final PsiExpression initializer = field.getInitializer(); + if (initializer != null) { + startElement(field); + initializer.accept(this); + finishElement(field); + } + } + + @Override + public void visitForStatement(PsiForStatement statement) { + startElement(statement); + PsiStatement body = statement.getBody(); + myStartStatementStack.pushStatement(body == null ? statement : body, false); myEndStatementStack.pushStatement(statement, false); - myEndJumpRoles.push(BranchingInstruction.Role.END); - myStartJumpRoles.push(BranchingInstruction.Role.END); + PsiStatement initialization = statement.getInitialization(); + if (initialization != null) { + initialization.accept(this); + } - condition.accept(this); + PsiExpression condition = statement.getCondition(); + if (condition != null) { + condition.accept(this); + } - myStartJumpRoles.pop(); - myEndJumpRoles.pop(); + + Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(condition); + if (loopCondition instanceof Boolean || condition == null) { + boolean value = condition == null || ((Boolean)loopCondition).booleanValue(); + if (value) { + emitEmptyInstruction(); + } + else { + myCurrentFlow.addInstruction(new GoToInstruction(0)); + addElementOffsetLater(statement, false); + } + } + else { + Instruction instruction = new ConditionalGoToInstruction(0, statement.getCondition()); + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(statement, false); + } + + if (body != null) { + body.accept(this); + } + + PsiStatement update = statement.getUpdate(); + if (update != null) { + update.accept(this); + } + + int offset = initialization != null + ? myCurrentFlow.getEndOffset(initialization) + : myCurrentFlow.getStartOffset(statement); + Instruction instruction = new GoToInstruction(offset); + myCurrentFlow.addInstruction(instruction); myStartStatementStack.popStatement(); myEndStatementStack.popStatement(); - } - Instruction ifTrue = new ConditionalGoToInstruction(0, BranchingInstruction.Role.END, statement.getAssertCondition()); - myCurrentFlow.addInstruction(ifTrue); - addElementOffsetLater(statement, false); - } else { - if (!throwReachable) { - myCurrentFlow.addInstruction(new GoToInstruction(0, BranchingInstruction.Role.END)); - addElementOffsetLater(statement, false); - } - } - PsiExpression description = statement.getAssertDescription(); - if (description != null) { - description.accept(this); - } - // if description is evaluated, the 'assert' statement cannot complete normally - // though non-necessarily AssertionError will be thrown (description may throw something, or AssertionError ctor, etc.) - PsiClassType exceptionClass = JavaPsiFacade.getElementFactory(statement.getProject()).createTypeByFQClassName( - CommonClassNames.JAVA_LANG_THROWABLE, statement.getResolveScope()); - addThrowInstructions(findThrowToBlocks(exceptionClass)); - - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - - finishElement(statement); - } - - @Override - public void visitTryStatement(PsiTryStatement statement) { - startElement(statement); - - PsiCodeBlock[] catchBlocks = statement.getCatchBlocks(); - PsiParameter[] catchBlockParameters = statement.getCatchBlockParameters(); - int catchNum = Math.min(catchBlocks.length, catchBlockParameters.length); - myUnhandledExceptionCatchBlocks.push(null); - for (int i = catchNum - 1; i >= 0; i--) { - ProgressManager.checkCanceled(); - myCatchParameters.push(catchBlockParameters[i]); - myCatchBlocks.push(catchBlocks[i]); - - final PsiType type = catchBlockParameters[i].getType(); - // todo cast param - if (type instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType) type)) { - myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); - } else if (type instanceof PsiDisjunctionType) { - final PsiType lub = ((PsiDisjunctionType) type).getLeastUpperBound(); - if (lub instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType) lub)) { - myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); - } else if (lub instanceof PsiIntersectionType) { - for (PsiType conjunct : ((PsiIntersectionType) lub).getConjuncts()) { - if (conjunct instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType) conjunct)) { - myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); - break; - } - } - } - } - } - - PsiCodeBlock finallyBlock = statement.getFinallyBlock(); - - FinallyBlockSubroutine finallyBlockSubroutine = null; - if (finallyBlock != null) { - finallyBlockSubroutine = new FinallyBlockSubroutine(finallyBlock); - myFinallyBlocks.push(finallyBlockSubroutine); - } - - PsiResourceList resourceList = statement.getResourceList(); - if (resourceList != null) { - generateCheckedExceptionJumps(resourceList); - resourceList.accept(this); - } - PsiCodeBlock tryBlock = statement.getTryBlock(); - if (tryBlock != null) { - // javac works as if all checked exceptions can occur at the top of the block - generateCheckedExceptionJumps(tryBlock); - tryBlock.accept(this); - } - - //noinspection StatementWithEmptyBody - while (myUnhandledExceptionCatchBlocks.pop() != null) { - ; - } - - myCurrentFlow.addInstruction(new GoToInstruction(finallyBlock == null ? 0 : -6)); - if (finallyBlock == null) { - addElementOffsetLater(statement, false); - } else { - addElementOffsetLater(finallyBlock, true); - } - - for (int i = 0; i < catchNum; i++) { - myCatchParameters.pop(); - myCatchBlocks.pop(); - } - - for (int i = catchNum - 1; i >= 0; i--) { - ProgressManager.checkCanceled(); - if (myPolicy.isParameterAccepted(catchBlockParameters[i])) { - generateWriteInstruction(catchBlockParameters[i]); - } - PsiCodeBlock catchBlock = catchBlocks[i]; - if (catchBlock != null) { - catchBlock.accept(this); - } else { - LOG.error("Catch body is null (" + i + ") " + statement.getText()); - } - - myCurrentFlow.addInstruction(new GoToInstruction(finallyBlock == null ? 0 : -6)); - if (finallyBlock == null) { + finishElement(statement); + } + + @Override + public void visitForeachStatement(PsiForeachStatement statement) { + startElement(statement); + final PsiStatement body = statement.getBody(); + myStartStatementStack.pushStatement(body == null ? statement : body, false); + myEndStatementStack.pushStatement(statement, false); + final PsiExpression iteratedValue = statement.getIteratedValue(); + if (iteratedValue != null) { + iteratedValue.accept(this); + } + + final int gotoTarget = myCurrentFlow.getSize(); + Instruction instruction = new ConditionalGoToInstruction(0, statement.getIteratedValue()); + myCurrentFlow.addInstruction(instruction); addElementOffsetLater(statement, false); - } else { - addElementOffsetLater(finallyBlock, true); - } - } - - if (finallyBlock != null) { - myFinallyBlocks.pop(); - } - - if (finallyBlock != null) { - // normal completion, call finally block and proceed - CallInstruction normalCompletion = new CallInstruction(0, 0); - finallyBlockSubroutine.addCall(normalCompletion); - myCurrentFlow.addInstruction(normalCompletion); - addElementOffsetLater(finallyBlock, true); - myCurrentFlow.addInstruction(new GoToInstruction(0)); - addElementOffsetLater(statement, false); - // return completion, call finally block and return - CallInstruction returnCompletion = new CallInstruction(0, 0); - finallyBlockSubroutine.addCall(returnCompletion); - myCurrentFlow.addInstruction(returnCompletion); - addElementOffsetLater(finallyBlock, true); - addReturnInstruction(statement); - // throw exception completion, call finally block and rethrow - CallInstruction throwExceptionCompletion = new CallInstruction(0, 0); - finallyBlockSubroutine.addCall(throwExceptionCompletion); - myCurrentFlow.addInstruction(throwExceptionCompletion); - addElementOffsetLater(finallyBlock, true); - final GoToInstruction gotoUncheckedRethrow = new GoToInstruction(0); - myCurrentFlow.addInstruction(gotoUncheckedRethrow); - addElementOffsetLater(finallyBlock, false); - - finallyBlock.accept(this); - final int procStart = myCurrentFlow.getStartOffset(finallyBlock); - final int procEnd = myCurrentFlow.getEndOffset(finallyBlock); - for (CallInstruction callInstruction : finallyBlockSubroutine.getCalls()) { - callInstruction.procBegin = procStart; - callInstruction.procEnd = procEnd; - } - - // generate return instructions - // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. - - // normal completion - myCurrentFlow.addInstruction(new ReturnInstruction(0, normalCompletion)); - - // return statement call completion - myCurrentFlow.addInstruction(new ReturnInstruction(procStart - 3, returnCompletion)); - - // unchecked exception throwing completion - myCurrentFlow.addInstruction(new ReturnInstruction(procStart - 1, throwExceptionCompletion)); - - // checked exception throwing completion; need to dispatch to the correct catch clause - final List unhandledExceptionCatchBlocks = finallyBlockToUnhandledExceptions.remove(finallyBlock); - for (int i = 0; unhandledExceptionCatchBlocks != null && i < unhandledExceptionCatchBlocks.size(); i++) { - ProgressManager.checkCanceled(); - PsiElement catchBlock = unhandledExceptionCatchBlocks.get(i); - final ReturnInstruction returnInstruction = new ReturnInstruction(0, throwExceptionCompletion); - returnInstruction.setRethrowFromFinally(); - myCurrentFlow.addInstruction(returnInstruction); - if (catchBlock == null) { - // dispatch to rethrowing exception code - returnInstruction.offset = procStart - 1; - } else { - // dispatch to catch clause - returnInstruction.offset--; // -1 for catch block init parameter instruction - addElementOffsetLater(catchBlock, true); + final PsiParameter iterationParameter = statement.getIterationParameter(); + if (myPolicy.isParameterAccepted(iterationParameter)) { + generateWriteInstruction(iterationParameter); + } + if (body != null) { + body.accept(this); } - } - // here generated rethrowing code for unchecked exceptions - gotoUncheckedRethrow.offset = myCurrentFlow.getSize(); - generateUncheckedExceptionJumps(statement, false); - // just in case - myCurrentFlow.addInstruction(new ThrowToInstruction(0)); - addElementOffsetLater(myCodeFragment, false); + final GoToInstruction gotoInstruction = new GoToInstruction(gotoTarget); + myCurrentFlow.addInstruction(gotoInstruction); + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + finishElement(statement); } - finishElement(statement); - } + @Override + public void visitIfStatement(PsiIfStatement statement) { + startElement(statement); - @Override - public void visitResourceList(final PsiResourceList resourceList) { - startElement(resourceList); + final PsiStatement elseBranch = statement.getElseBranch(); + final PsiStatement thenBranch = statement.getThenBranch(); + PsiExpression conditionExpression = statement.getCondition(); - for (PsiResourceListElement resource : resourceList) { - ProgressManager.checkCanceled(); - if (resource instanceof PsiResourceVariable) { - processVariable((PsiVariable) resource); - } else if (resource instanceof PsiResourceExpression) { - ((PsiResourceExpression) resource).getExpression().accept(this); - } + generateConditionalStatementInstructions(statement, conditionExpression, thenBranch, elseBranch); + + finishElement(statement); } - finishElement(resourceList); - } + private void generateConditionalStatementInstructions( + @Nonnull PsiElement statement, + @Nullable PsiExpression conditionExpression, + final PsiElement thenBranch, + final PsiElement elseBranch + ) { + if (thenBranch == null) { + myStartStatementStack.pushStatement(statement, false); + } + else { + myStartStatementStack.pushStatement(thenBranch, true); + } + if (elseBranch == null) { + myEndStatementStack.pushStatement(statement, false); + } + else { + myEndStatementStack.pushStatement(elseBranch, true); + } + + myEndJumpRoles.push(elseBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.ELSE); + myStartJumpRoles.push(thenBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.THEN); + + if (conditionExpression != null) { + conditionExpression.accept(this); + } + + boolean thenReachable = true; + boolean generateConditionalJump = true; + /* + * if() statement generated instructions outline: + * 'if (C) { A } [ else { B } ]' : + * generate (C) + * cond_goto else + * generate (A) + * [ goto end ] + * :else + * [ generate (B) ] + * :end + */ + if (myOptions.shouldEvaluateConstantIfCondition()) { + final Object value = myConstantEvaluationHelper.computeConstantExpression(conditionExpression); + if (value instanceof Boolean) { + thenReachable = ((Boolean)value).booleanValue(); + generateConditionalJump = false; + myCurrentFlow.setConstantConditionOccurred(true); + } + } + if (generateConditionalJump || !thenReachable) { + BranchingInstruction.Role role = elseBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.ELSE; + Instruction instruction = generateConditionalJump ? new ConditionalGoToInstruction(0, role, conditionExpression) : + new GoToInstruction(0, role); + myCurrentFlow.addInstruction(instruction); + if (elseBranch == null) { + addElementOffsetLater(statement, false); + } + else { + addElementOffsetLater(elseBranch, true); + } + } + if (thenBranch != null) { + thenBranch.accept(this); + } + if (elseBranch != null) { + Instruction instruction = new GoToInstruction(0); + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(statement, false); + elseBranch.accept(this); + } + + myStartJumpRoles.pop(); + myEndJumpRoles.pop(); - @Override - public void visitWhileStatement(PsiWhileStatement statement) { - startElement(statement); - PsiStatement body = statement.getBody(); - if (body == null) { - myStartStatementStack.pushStatement(statement, false); - } else { - myStartStatementStack.pushStatement(body, true); + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); } - myEndStatementStack.pushStatement(statement, false); - PsiExpression condition = statement.getCondition(); - if (condition != null) { - condition.accept(this); + @Override + public void visitLabeledStatement(PsiLabeledStatement statement) { + startElement(statement); + final PsiStatement innerStatement = statement.getStatement(); + if (innerStatement != null) { + innerStatement.accept(this); + } + finishElement(statement); } + @Override + public void visitReturnStatement(PsiReturnStatement statement) { + startElement(statement); + PsiExpression returnValue = statement.getReturnValue(); - Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(statement.getCondition()); - if (loopCondition instanceof Boolean) { - boolean value = ((Boolean) loopCondition).booleanValue(); - if (value) { - emitEmptyInstruction(); - } else { - myCurrentFlow.addInstruction(new GoToInstruction(0)); - addElementOffsetLater(statement, false); - } - } else { - Instruction instruction = new ConditionalGoToInstruction(0, statement.getCondition()); - myCurrentFlow.addInstruction(instruction); - addElementOffsetLater(statement, false); + if (returnValue != null) { + myStartStatementStack.pushStatement(returnValue, false); + myEndStatementStack.pushStatement(returnValue, false); + returnValue.accept(this); + } + addReturnInstruction(statement); + if (returnValue != null) { + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + } + + finishElement(statement); + } + + private void addReturnInstruction(@Nonnull PsiElement statement) { + BranchingInstruction instruction; + final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, null); + final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); + if (finallyBlock != null && finallyStartOffset != -1) { + // go out of finally, go to 2nd return after finally block + // second return is for return statement called completion + instruction = new GoToInstruction(1, BranchingInstruction.Role.END, true); + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(finallyBlock, false); + } + else { + instruction = new GoToInstruction(0, BranchingInstruction.Role.END, true); + myCurrentFlow.addInstruction(instruction); + if (myFinallyBlocks.isEmpty()) { + addElementOffsetLater(myCodeFragment, false); + } + else { + instruction.offset = -4; // -4 for return + addElementOffsetLater(myFinallyBlocks.peek().getElement(), true); + } + } } - if (body != null) { - body.accept(this); + @Override + public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) { + startElement(statement); + generateCaseValueInstructions(statement.getCaseValues()); + finishElement(statement); } - int offset = myCurrentFlow.getStartOffset(statement); - Instruction instruction = new GoToInstruction(offset); - myCurrentFlow.addInstruction(instruction); - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - finishElement(statement); - } + @Override + public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) { + startElement(statement); + + generateCaseValueInstructions(statement.getCaseValues()); + + PsiStatement body = statement.getBody(); + if (body != null) { + body.accept(this); + } + + PsiSwitchBlock switchBlock = statement.getEnclosingSwitchBlock(); + if (switchBlock != null) { + Instruction instruction = + new GoToInstruction(0, BranchingInstruction.Role.END, PsiTreeUtil.isAncestor(switchBlock, myCodeFragment, true)); + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(switchBlock, false); + } - @Override - public void visitExpressionList(PsiExpressionList list) { - PsiExpression[] expressions = list.getExpressions(); - for (final PsiExpression expression : expressions) { - ProgressManager.checkCanceled(); - generateExpressionInstructions(expression); + finishElement(statement); } - } - private void generateExpressionInstructions(@Nullable PsiExpression expression) { - if (expression != null) { - // handle short circuit - myStartStatementStack.pushStatement(expression, false); - myEndStatementStack.pushStatement(expression, false); + private void generateCaseValueInstructions(@Nullable PsiExpressionList values) { + if (values != null) { + for (PsiExpression caseValue : values.getExpressions()) { + ProgressManager.checkCanceled(); + generateExpressionInstructions(caseValue); + } + } + } + + @Override + public void visitSwitchStatement(PsiSwitchStatement statement) { + generateSwitchBlockInstructions(statement); + } - expression.accept(this); - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); + @Override + public void visitSwitchExpression(PsiSwitchExpression expression) { + generateSwitchBlockInstructions(expression); } - } - @Override - public void visitArrayAccessExpression(PsiArrayAccessExpression expression) { - startElement(expression); + public void generateSwitchBlockInstructions(PsiSwitchBlock statement) { + startElement(statement); - expression.getArrayExpression().accept(this); - final PsiExpression indexExpression = expression.getIndexExpression(); - if (indexExpression != null) { - indexExpression.accept(this); + PsiExpression expr = statement.getExpression(); + if (expr != null) { + expr.accept(this); + } + + PsiCodeBlock body = statement.getBody(); + if (body != null) { + PsiStatement[] statements = body.getStatements(); + PsiSwitchLabelStatementBase defaultLabel = null; + for (PsiStatement aStatement : statements) { + ProgressManager.checkCanceled(); + if (aStatement instanceof PsiSwitchLabelStatementBase) { + if (((PsiSwitchLabelStatementBase)aStatement).isDefaultCase()) { + defaultLabel = (PsiSwitchLabelStatementBase)aStatement; + } + Instruction instruction = new ConditionalGoToInstruction(0, expr); + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(aStatement, true); + } + } + if (defaultLabel == null) { + Instruction instruction = new GoToInstruction(0); + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(body, false); + } + + body.accept(this); + } + + finishElement(statement); } - finishElement(expression); - } + @Override + public void visitSynchronizedStatement(PsiSynchronizedStatement statement) { + startElement(statement); - @Override - public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) { - startElement(expression); + PsiExpression lock = statement.getLockExpression(); + if (lock != null) { + lock.accept(this); + } + + PsiCodeBlock body = statement.getBody(); + if (body != null) { + body.accept(this); + } - PsiExpression[] initializers = expression.getInitializers(); - for (PsiExpression initializer : initializers) { - ProgressManager.checkCanceled(); - initializer.accept(this); + finishElement(statement); } - finishElement(expression); - } + @Override + public void visitThrowStatement(PsiThrowStatement statement) { + startElement(statement); - @Override - public void visitAssignmentExpression(PsiAssignmentExpression expression) { - startElement(expression); + PsiExpression exception = statement.getException(); + if (exception != null) { + exception.accept(this); + } + final List blocks = findThrowToBlocks(statement); + addThrowInstructions(blocks); - PsiExpression rExpr = expression.getRExpression(); - myStartStatementStack.pushStatement(rExpr == null ? expression : rExpr, false); - myEndStatementStack.pushStatement(rExpr == null ? expression : rExpr, false); + finishElement(statement); + } - boolean generatedWriteInstruction = false; - PsiExpression lExpr = PsiUtil.skipParenthesizedExprDown(expression.getLExpression()); - if (lExpr instanceof PsiReferenceExpression) { - if (!myImplicitCompactConstructorAssignments.isEmpty()) { - PsiElement target = ((PsiReferenceExpression) lExpr).resolve(); - if (target instanceof PsiField) { - myImplicitCompactConstructorAssignments.remove(target); + private void addThrowInstructions(@Nonnull List blocks) { + PsiElement element; + if (blocks.isEmpty() || blocks.get(0) == null) { + ThrowToInstruction instruction = new ThrowToInstruction(0); + myCurrentFlow.addInstruction(instruction); + if (myFinallyBlocks.isEmpty()) { + element = myCodeFragment; + addElementOffsetLater(element, false); + } + else { + instruction.offset = -2; // -2 to rethrow exception + element = myFinallyBlocks.peek().getElement(); + addElementOffsetLater(element, true); + } } - } - PsiVariable variable = getUsedVariable((PsiReferenceExpression) lExpr); - if (variable != null) { - if (myAssignmentTargetsAreElements) { - startElement(lExpr); + else { + for (int i = 0; i < blocks.size(); i++) { + ProgressManager.checkCanceled(); + element = blocks.get(i); + BranchingInstruction instruction = i == blocks.size() - 1 + ? new ThrowToInstruction(0) + : new ConditionalThrowToInstruction(0); + myCurrentFlow.addInstruction(instruction); + instruction.offset = -1; // -1 to init catch param + addElementOffsetLater(element, true); + } } + } - PsiExpression qualifier = ((PsiReferenceExpression) lExpr).getQualifierExpression(); - if (qualifier != null) { - qualifier.accept(this); + /** + * Find offsets of catch(es) corresponding to this throw statement + * myCatchParameters and myCatchBlocks arrays should be sorted in ascending scope order (from outermost to innermost) + * + * @return list of targets or list of single null element if no appropriate targets found + */ + @Nonnull + private List findThrowToBlocks(@Nonnull PsiThrowStatement statement) { + final PsiExpression exceptionExpr = statement.getException(); + if (exceptionExpr == null) { + return Collections.emptyList(); + } + final PsiType throwType = exceptionExpr.getType(); + if (!(throwType instanceof PsiClassType)) { + return Collections.emptyList(); } + return findThrowToBlocks((PsiClassType)throwType); + } - if (expression.getOperationTokenType() != JavaTokenType.EQ) { - generateReadInstruction(variable); + @Nonnull + private List findThrowToBlocks(@Nonnull PsiClassType throwType) { + List blocks = new ArrayList<>(); + for (int i = myCatchParameters.size() - 1; i >= 0; i--) { + ProgressManager.checkCanceled(); + PsiParameter parameter = myCatchParameters.get(i); + PsiType catchType = parameter.getType(); + if (ControlFlowUtil.isCaughtExceptionType(throwType, catchType)) { + blocks.add(myCatchBlocks.get(i)); + } } - if (rExpr != null) { - rExpr.accept(this); + if (blocks.isEmpty()) { + // consider it as throw at the end of the control flow + blocks.add(null); + } + return blocks; + } + + @Override + public void visitAssertStatement(PsiAssertStatement statement) { + startElement(statement); + + myStartStatementStack.pushStatement(statement, false); + myEndStatementStack.pushStatement(statement, false); + Instruction passByWhenAssertionsDisabled = new ConditionalGoToInstruction(0, BranchingInstruction.Role.END, null); + myCurrentFlow.addInstruction(passByWhenAssertionsDisabled); + addElementOffsetLater(statement, false); + + final PsiExpression condition = statement.getAssertCondition(); + boolean generateCondition = true; + boolean throwReachable = true; + if (myOptions.shouldEvaluateConstantIfCondition()) { + Object conditionValue = myConstantEvaluationHelper.computeConstantExpression(condition); + if (conditionValue instanceof Boolean) { + throwReachable = !((Boolean)conditionValue); + generateCondition = false; + emitEmptyInstruction(); + } } - generateWriteInstruction(variable); - generatedWriteInstruction = true; - if (myAssignmentTargetsAreElements) { - finishElement(lExpr); + if (generateCondition) { + if (condition != null) { + myStartStatementStack.pushStatement(statement, false); + myEndStatementStack.pushStatement(statement, false); + + myEndJumpRoles.push(BranchingInstruction.Role.END); + myStartJumpRoles.push(BranchingInstruction.Role.END); + + condition.accept(this); + + myStartJumpRoles.pop(); + myEndJumpRoles.pop(); + + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + } + Instruction ifTrue = new ConditionalGoToInstruction(0, BranchingInstruction.Role.END, statement.getAssertCondition()); + myCurrentFlow.addInstruction(ifTrue); + addElementOffsetLater(statement, false); } - } else { - if (rExpr != null) { - rExpr.accept(this); + else if (!throwReachable) { + myCurrentFlow.addInstruction(new GoToInstruction(0, BranchingInstruction.Role.END)); + addElementOffsetLater(statement, false); } - lExpr.accept(this); //? - } - } else if (lExpr instanceof PsiArrayAccessExpression && - ((PsiArrayAccessExpression) lExpr).getArrayExpression() instanceof PsiReferenceExpression) { - PsiVariable variable = getUsedVariable((PsiReferenceExpression) ((PsiArrayAccessExpression) lExpr).getArrayExpression()); - if (variable != null) { - generateReadInstruction(variable); - final PsiExpression indexExpression = ((PsiArrayAccessExpression) lExpr).getIndexExpression(); - if (indexExpression != null) { - indexExpression.accept(this); - } - } else { - lExpr.accept(this); - } - if (rExpr != null) { - rExpr.accept(this); - } - } else if (lExpr != null) { - lExpr.accept(this); - if (rExpr != null) { - rExpr.accept(this); - } - } - //each statement should contain at least one instruction in order to getElement(offset) work - if (!generatedWriteInstruction) { - emitEmptyInstruction(); - } - - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - - finishElement(expression); - } - - private enum Shortcut { - NO_SHORTCUT, // a || b - SKIP_CURRENT_OPERAND, // false || a - STOP_EXPRESSION // true || a - } - - @Override - public void visitPolyadicExpression(PsiPolyadicExpression expression) { - startElement(expression); - IElementType signTokenType = expression.getOperationTokenType(); - - boolean isAndAnd = signTokenType == JavaTokenType.ANDAND; - boolean isOrOr = signTokenType == JavaTokenType.OROR; - - PsiExpression[] operands = expression.getOperands(); - Boolean lValue = isAndAnd; - PsiExpression lOperand = null; - Boolean rValue = null; - for (int i = 0; i < operands.length; i++) { - PsiExpression rOperand = operands[i]; - if ((isAndAnd || isOrOr) && myOptions.enableShortCircuit()) { - Object exprValue = myConstantEvaluationHelper.computeConstantExpression(rOperand); - if (exprValue instanceof Boolean) { - myCurrentFlow.setConstantConditionOccurred(true); - rValue = shouldCalculateConstantExpression(expression) ? (Boolean) exprValue : null; - } else { - rValue = null; - } - - BranchingInstruction.Role role = isAndAnd ? myEndJumpRoles.peek() : myStartJumpRoles.peek(); - PsiElement gotoElement = isAndAnd ? myEndStatementStack.peekElement() : myStartStatementStack.peekElement(); - boolean gotoIsAtStart = isAndAnd ? myEndStatementStack.peekAtStart() : myStartStatementStack.peekAtStart(); - - Shortcut shortcut; - if (lValue != null) { - shortcut = lValue.booleanValue() == isOrOr ? Shortcut.STOP_EXPRESSION : Shortcut.SKIP_CURRENT_OPERAND; - } else if (rValue != null && rValue.booleanValue() == isOrOr) { - shortcut = Shortcut.STOP_EXPRESSION; - } else { - shortcut = Shortcut.NO_SHORTCUT; - } - - switch (shortcut) { - case NO_SHORTCUT: - myCurrentFlow.addInstruction(new ConditionalGoToInstruction(0, role, lOperand)); - addElementOffsetLater(gotoElement, gotoIsAtStart); - break; - - case STOP_EXPRESSION: - myCurrentFlow.addInstruction(new GoToInstruction(0, role)); - addElementOffsetLater(gotoElement, gotoIsAtStart); - rValue = null; - break; - - case SKIP_CURRENT_OPERAND: - break; - } - } - generateLOperand(rOperand, i == operands.length - 1 ? null : operands[i + 1], signTokenType); - - lOperand = rOperand; - lValue = rValue; - } - - finishElement(expression); - } - - private void generateLOperand(@Nonnull PsiExpression lOperand, @Nullable PsiExpression rOperand, @Nonnull IElementType signTokenType) { - if (rOperand != null) { - myStartJumpRoles.push(BranchingInstruction.Role.END); - myEndJumpRoles.push(BranchingInstruction.Role.END); - PsiElement then = signTokenType == JavaTokenType.OROR ? myStartStatementStack.peekElement() : rOperand; - boolean thenAtStart = signTokenType != JavaTokenType.OROR || myStartStatementStack.peekAtStart(); - myStartStatementStack.pushStatement(then, thenAtStart); - PsiElement elseS = signTokenType == JavaTokenType.ANDAND ? myEndStatementStack.peekElement() : rOperand; - boolean elseAtStart = signTokenType != JavaTokenType.ANDAND || myEndStatementStack.peekAtStart(); - myEndStatementStack.pushStatement(elseS, elseAtStart); - } - lOperand.accept(this); - if (rOperand != null) { - myStartStatementStack.popStatement(); - myEndStatementStack.popStatement(); - myStartJumpRoles.pop(); - myEndJumpRoles.pop(); - } - } - - private static boolean isInsideIfCondition(@Nonnull PsiExpression expression) { - PsiElement element = expression; - while (element instanceof PsiExpression) { - final PsiElement parent = element.getParent(); - if (parent instanceof PsiIfStatement && element == ((PsiIfStatement) parent).getCondition()) { - return true; - } - element = parent; + PsiExpression description = statement.getAssertDescription(); + if (description != null) { + description.accept(this); + } + // if description is evaluated, the 'assert' statement cannot complete normally + // though non-necessarily AssertionError will be thrown (description may throw something, or AssertionError ctor, etc.) + PsiClassType exceptionClass = JavaPsiFacade.getElementFactory(statement.getProject()).createTypeByFQClassName( + CommonClassNames.JAVA_LANG_THROWABLE, statement.getResolveScope()); + addThrowInstructions(findThrowToBlocks(exceptionClass)); + + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + + finishElement(statement); } - return false; - } - private boolean shouldCalculateConstantExpression(@Nonnull PsiExpression expression) { - return myOptions.shouldEvaluateConstantIfCondition() || !isInsideIfCondition(expression); - } + @Override + public void visitTryStatement(PsiTryStatement statement) { + startElement(statement); - @Override - public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { - visitChildren(expression); - } + PsiCodeBlock[] catchBlocks = statement.getCatchBlocks(); + PsiParameter[] catchBlockParameters = statement.getCatchBlockParameters(); + int catchNum = Math.min(catchBlocks.length, catchBlockParameters.length); + myUnhandledExceptionCatchBlocks.push(null); + for (int i = catchNum - 1; i >= 0; i--) { + ProgressManager.checkCanceled(); + myCatchParameters.push(catchBlockParameters[i]); + myCatchBlocks.push(catchBlocks[i]); - private void visitChildren(@Nonnull PsiElement element) { - startElement(element); + final PsiType type = catchBlockParameters[i].getType(); + // todo cast param + if (type instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)type)) { + myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); + } + else if (type instanceof PsiDisjunctionType) { + final PsiType lub = ((PsiDisjunctionType)type).getLeastUpperBound(); + if (lub instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)lub)) { + myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); + } + else if (lub instanceof PsiIntersectionType) { + for (PsiType conjunct : ((PsiIntersectionType)lub).getConjuncts()) { + if (conjunct instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)conjunct)) { + myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); + break; + } + } + } + } + } - PsiElement[] children = element.getChildren(); - for (PsiElement child : children) { - ProgressManager.checkCanceled(); - child.accept(this); - } + PsiCodeBlock finallyBlock = statement.getFinallyBlock(); + + FinallyBlockSubroutine finallyBlockSubroutine = null; + if (finallyBlock != null) { + finallyBlockSubroutine = new FinallyBlockSubroutine(finallyBlock); + myFinallyBlocks.push(finallyBlockSubroutine); + } - finishElement(element); - } + PsiResourceList resourceList = statement.getResourceList(); + if (resourceList != null) { + generateCheckedExceptionJumps(resourceList); + resourceList.accept(this); + } + PsiCodeBlock tryBlock = statement.getTryBlock(); + if (tryBlock != null) { + // javac works as if all checked exceptions can occur at the top of the block + generateCheckedExceptionJumps(tryBlock); + tryBlock.accept(this); + } - @Override - public void visitConditionalExpression(PsiConditionalExpression expression) { - startElement(expression); + //noinspection StatementWithEmptyBody + while (myUnhandledExceptionCatchBlocks.pop() != null) { + ; + } - final PsiExpression condition = expression.getCondition(); - final PsiExpression thenExpression = expression.getThenExpression(); - final PsiExpression elseExpression = expression.getElseExpression(); - generateConditionalStatementInstructions(expression, condition, thenExpression, elseExpression); + myCurrentFlow.addInstruction(new GoToInstruction(finallyBlock == null ? 0 : -6)); + if (finallyBlock == null) { + addElementOffsetLater(statement, false); + } + else { + addElementOffsetLater(finallyBlock, true); + } - finishElement(expression); - } + for (int i = 0; i < catchNum; i++) { + myCatchParameters.pop(); + myCatchBlocks.pop(); + } - @Override - public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { - startElement(expression); + for (int i = catchNum - 1; i >= 0; i--) { + ProgressManager.checkCanceled(); + if (myPolicy.isParameterAccepted(catchBlockParameters[i])) { + generateWriteInstruction(catchBlockParameters[i]); + } + PsiCodeBlock catchBlock = catchBlocks[i]; + if (catchBlock != null) { + catchBlock.accept(this); + } + else { + LOG.error("Catch body is null (" + i + ") " + statement.getText()); + } - final PsiExpression operand = expression.getOperand(); - operand.accept(this); + myCurrentFlow.addInstruction(new GoToInstruction(finallyBlock == null ? 0 : -6)); + if (finallyBlock == null) { + addElementOffsetLater(statement, false); + } + else { + addElementOffsetLater(finallyBlock, true); + } + } - PsiPattern pattern = expression.getPattern(); - if (pattern instanceof PsiTypeTestPattern) { - PsiPatternVariable variable = ((PsiTypeTestPattern) pattern).getPatternVariable(); + if (finallyBlock != null) { + myFinallyBlocks.pop(); + } - if (variable != null) { - myCurrentFlow.addInstruction(new WriteVariableInstruction(variable)); - } - } + if (finallyBlock != null) { + // normal completion, call finally block and proceed + CallInstruction normalCompletion = new CallInstruction(0, 0); + finallyBlockSubroutine.addCall(normalCompletion); + myCurrentFlow.addInstruction(normalCompletion); + addElementOffsetLater(finallyBlock, true); + myCurrentFlow.addInstruction(new GoToInstruction(0)); + addElementOffsetLater(statement, false); + // return completion, call finally block and return + CallInstruction returnCompletion = new CallInstruction(0, 0); + finallyBlockSubroutine.addCall(returnCompletion); + myCurrentFlow.addInstruction(returnCompletion); + addElementOffsetLater(finallyBlock, true); + addReturnInstruction(statement); + // throw exception completion, call finally block and rethrow + CallInstruction throwExceptionCompletion = new CallInstruction(0, 0); + finallyBlockSubroutine.addCall(throwExceptionCompletion); + myCurrentFlow.addInstruction(throwExceptionCompletion); + addElementOffsetLater(finallyBlock, true); + final GoToInstruction gotoUncheckedRethrow = new GoToInstruction(0); + myCurrentFlow.addInstruction(gotoUncheckedRethrow); + addElementOffsetLater(finallyBlock, false); + + finallyBlock.accept(this); + final int procStart = myCurrentFlow.getStartOffset(finallyBlock); + final int procEnd = myCurrentFlow.getEndOffset(finallyBlock); + for (CallInstruction callInstruction : finallyBlockSubroutine.getCalls()) { + callInstruction.procBegin = procStart; + callInstruction.procEnd = procEnd; + } - finishElement(expression); - } + // generate return instructions + // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. + + // normal completion + myCurrentFlow.addInstruction(new ReturnInstruction(0, normalCompletion)); + + // return statement call completion + myCurrentFlow.addInstruction(new ReturnInstruction(procStart - 3, returnCompletion)); + + // unchecked exception throwing completion + myCurrentFlow.addInstruction(new ReturnInstruction(procStart - 1, throwExceptionCompletion)); + + // checked exception throwing completion; need to dispatch to the correct catch clause + final List unhandledExceptionCatchBlocks = finallyBlockToUnhandledExceptions.remove(finallyBlock); + for (int i = 0; unhandledExceptionCatchBlocks != null && i < unhandledExceptionCatchBlocks.size(); i++) { + ProgressManager.checkCanceled(); + PsiElement catchBlock = unhandledExceptionCatchBlocks.get(i); + + final ReturnInstruction returnInstruction = new ReturnInstruction(0, throwExceptionCompletion); + returnInstruction.setRethrowFromFinally(); + myCurrentFlow.addInstruction(returnInstruction); + if (catchBlock == null) { + // dispatch to rethrowing exception code + returnInstruction.offset = procStart - 1; + } + else { + // dispatch to catch clause + returnInstruction.offset--; // -1 for catch block init parameter instruction + addElementOffsetLater(catchBlock, true); + } + } - @Override - public void visitLiteralExpression(PsiLiteralExpression expression) { - startElement(expression); - finishElement(expression); - } + // here generated rethrowing code for unchecked exceptions + gotoUncheckedRethrow.offset = myCurrentFlow.getSize(); + generateUncheckedExceptionJumps(statement, false); + // just in case + myCurrentFlow.addInstruction(new ThrowToInstruction(0)); + addElementOffsetLater(myCodeFragment, false); + } - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - startElement(expression); - final PsiElement body = expression.getBody(); - if (body != null) { - List array = new ArrayList<>(); - addUsedVariables(array, body); - for (PsiVariable var : array) { - ProgressManager.checkCanceled(); - generateReadInstruction(var); - } + finishElement(statement); } - finishElement(expression); - } - @Override - public void visitMethodCallExpression(PsiMethodCallExpression expression) { - ArrayDeque calls = new ArrayDeque<>(); - while (true) { - calls.addFirst(expression); - startElement(expression); + @Override + public void visitResourceList(final PsiResourceList resourceList) { + startElement(resourceList); - PsiExpression qualifierExpression = expression.getMethodExpression().getQualifierExpression(); - expression = ObjectUtil.tryCast(PsiUtil.skipParenthesizedExprDown(qualifierExpression), PsiMethodCallExpression.class); - if (expression == null) { - if (qualifierExpression != null) { - qualifierExpression.accept(this); + for (PsiResourceListElement resource : resourceList) { + ProgressManager.checkCanceled(); + if (resource instanceof PsiResourceVariable) { + processVariable((PsiVariable)resource); + } + else if (resource instanceof PsiResourceExpression) { + ((PsiResourceExpression)resource).getExpression().accept(this); + } } - break; - } + + finishElement(resourceList); } - for (PsiMethodCallExpression call : calls) { - final PsiExpressionList argumentList = call.getArgumentList(); - argumentList.accept(this); - // just to increase counter - there is some executable code here - emitEmptyInstruction(); + @Override + public void visitWhileStatement(PsiWhileStatement statement) { + startElement(statement); + PsiStatement body = statement.getBody(); + if (body == null) { + myStartStatementStack.pushStatement(statement, false); + } + else { + myStartStatementStack.pushStatement(body, true); + } + myEndStatementStack.pushStatement(statement, false); + + PsiExpression condition = statement.getCondition(); + if (condition != null) { + condition.accept(this); + } - //generate jumps to all handled exception handlers - generateExceptionJumps(call, ExceptionUtil.getUnhandledExceptions(call, call.getParent())); - finishElement(call); - } - } + Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(statement.getCondition()); + if (loopCondition instanceof Boolean) { + boolean value = ((Boolean)loopCondition).booleanValue(); + if (value) { + emitEmptyInstruction(); + } + else { + myCurrentFlow.addInstruction(new GoToInstruction(0)); + addElementOffsetLater(statement, false); + } + } + else { + Instruction instruction = new ConditionalGoToInstruction(0, statement.getCondition()); + myCurrentFlow.addInstruction(instruction); + addElementOffsetLater(statement, false); + } - @Override - public void visitNewExpression(PsiNewExpression expression) { - startElement(expression); + if (body != null) { + body.accept(this); + } + int offset = myCurrentFlow.getStartOffset(statement); + Instruction instruction = new GoToInstruction(offset); + myCurrentFlow.addInstruction(instruction); - int pc = myCurrentFlow.getSize(); - PsiElement[] children = expression.getChildren(); - for (PsiElement child : children) { - ProgressManager.checkCanceled(); - child.accept(this); + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + finishElement(statement); } - //generate jumps to all handled exception handlers - generateExceptionJumps(expression, ExceptionUtil.getUnhandledExceptions(expression, expression.getParent())); - if (pc == myCurrentFlow.getSize()) { - // generate at least one instruction for constructor call - emitEmptyInstruction(); + @Override + public void visitExpressionList(PsiExpressionList list) { + PsiExpression[] expressions = list.getExpressions(); + for (final PsiExpression expression : expressions) { + ProgressManager.checkCanceled(); + generateExpressionInstructions(expression); + } } - finishElement(expression); - } + private void generateExpressionInstructions(@Nullable PsiExpression expression) { + if (expression != null) { + // handle short circuit + myStartStatementStack.pushStatement(expression, false); + myEndStatementStack.pushStatement(expression, false); - @Override - public void visitParenthesizedExpression(PsiParenthesizedExpression expression) { - visitChildren(expression); - } + expression.accept(this); + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + } + } - @Override - public void visitPostfixExpression(PsiPostfixExpression expression) { - startElement(expression); + @Override + public void visitArrayAccessExpression(PsiArrayAccessExpression expression) { + startElement(expression); - IElementType op = expression.getOperationTokenType(); - PsiExpression operand = PsiUtil.skipParenthesizedExprDown(expression.getOperand()); - if (operand != null) { - operand.accept(this); - if (op == JavaTokenType.PLUSPLUS || op == JavaTokenType.MINUSMINUS) { - if (operand instanceof PsiReferenceExpression) { - PsiVariable variable = getUsedVariable((PsiReferenceExpression) operand); - if (variable != null) { - generateWriteInstruction(variable); - } + expression.getArrayExpression().accept(this); + final PsiExpression indexExpression = expression.getIndexExpression(); + if (indexExpression != null) { + indexExpression.accept(this); } - } + + finishElement(expression); } - finishElement(expression); - } + @Override + public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) { + startElement(expression); - @Override - public void visitPrefixExpression(PsiPrefixExpression expression) { - startElement(expression); + PsiExpression[] initializers = expression.getInitializers(); + for (PsiExpression initializer : initializers) { + ProgressManager.checkCanceled(); + initializer.accept(this); + } - PsiExpression operand = PsiUtil.skipParenthesizedExprDown(expression.getOperand()); - if (operand != null) { - IElementType operationSign = expression.getOperationTokenType(); - if (operationSign == JavaTokenType.EXCL) { - // negation inverts jump targets - PsiElement topStartStatement = myStartStatementStack.peekElement(); - boolean topAtStart = myStartStatementStack.peekAtStart(); - myStartStatementStack.pushStatement(myEndStatementStack.peekElement(), myEndStatementStack.peekAtStart()); - myEndStatementStack.pushStatement(topStartStatement, topAtStart); - } + finishElement(expression); + } + + @Override + public void visitAssignmentExpression(PsiAssignmentExpression expression) { + startElement(expression); - operand.accept(this); + PsiExpression rExpr = expression.getRExpression(); + myStartStatementStack.pushStatement(rExpr == null ? expression : rExpr, false); + myEndStatementStack.pushStatement(rExpr == null ? expression : rExpr, false); + + boolean generatedWriteInstruction = false; + PsiExpression lExpr = PsiUtil.skipParenthesizedExprDown(expression.getLExpression()); + if (lExpr instanceof PsiReferenceExpression) { + if (!myImplicitCompactConstructorAssignments.isEmpty()) { + PsiElement target = ((PsiReferenceExpression)lExpr).resolve(); + if (target instanceof PsiField) { + myImplicitCompactConstructorAssignments.remove(target); + } + } + PsiVariable variable = getUsedVariable((PsiReferenceExpression)lExpr); + if (variable != null) { + if (myAssignmentTargetsAreElements) { + startElement(lExpr); + } + + PsiExpression qualifier = ((PsiReferenceExpression)lExpr).getQualifierExpression(); + if (qualifier != null) { + qualifier.accept(this); + } + + if (expression.getOperationTokenType() != JavaTokenType.EQ) { + generateReadInstruction(variable); + } + if (rExpr != null) { + rExpr.accept(this); + } + generateWriteInstruction(variable); + generatedWriteInstruction = true; + + if (myAssignmentTargetsAreElements) { + finishElement(lExpr); + } + } + else { + if (rExpr != null) { + rExpr.accept(this); + } + lExpr.accept(this); //? + } + } + else if (lExpr instanceof PsiArrayAccessExpression && + ((PsiArrayAccessExpression)lExpr).getArrayExpression() instanceof PsiReferenceExpression) { + PsiVariable variable = getUsedVariable((PsiReferenceExpression)((PsiArrayAccessExpression)lExpr).getArrayExpression()); + if (variable != null) { + generateReadInstruction(variable); + final PsiExpression indexExpression = ((PsiArrayAccessExpression)lExpr).getIndexExpression(); + if (indexExpression != null) { + indexExpression.accept(this); + } + } + else { + lExpr.accept(this); + } + if (rExpr != null) { + rExpr.accept(this); + } + } + else if (lExpr != null) { + lExpr.accept(this); + if (rExpr != null) { + rExpr.accept(this); + } + } + //each statement should contain at least one instruction in order to getElement(offset) work + if (!generatedWriteInstruction) { + emitEmptyInstruction(); + } - if (operationSign == JavaTokenType.EXCL) { - // negation inverts jump targets myStartStatementStack.popStatement(); myEndStatementStack.popStatement(); - } - if (operand instanceof PsiReferenceExpression && - (operationSign == JavaTokenType.PLUSPLUS || operationSign == JavaTokenType.MINUSMINUS)) { - PsiVariable variable = getUsedVariable((PsiReferenceExpression) operand); - if (variable != null) { - generateWriteInstruction(variable); + finishElement(expression); + } + + private enum Shortcut { + NO_SHORTCUT, // a || b + SKIP_CURRENT_OPERAND, // false || a + STOP_EXPRESSION // true || a + } + + @Override + public void visitPolyadicExpression(PsiPolyadicExpression expression) { + startElement(expression); + IElementType signTokenType = expression.getOperationTokenType(); + + boolean isAndAnd = signTokenType == JavaTokenType.ANDAND; + boolean isOrOr = signTokenType == JavaTokenType.OROR; + + PsiExpression[] operands = expression.getOperands(); + Boolean lValue = isAndAnd; + PsiExpression lOperand = null; + Boolean rValue = null; + for (int i = 0; i < operands.length; i++) { + PsiExpression rOperand = operands[i]; + if ((isAndAnd || isOrOr) && myOptions.enableShortCircuit()) { + Object exprValue = myConstantEvaluationHelper.computeConstantExpression(rOperand); + if (exprValue instanceof Boolean) { + myCurrentFlow.setConstantConditionOccurred(true); + rValue = shouldCalculateConstantExpression(expression) ? (Boolean)exprValue : null; + } + else { + rValue = null; + } + + BranchingInstruction.Role role = isAndAnd ? myEndJumpRoles.peek() : myStartJumpRoles.peek(); + PsiElement gotoElement = isAndAnd ? myEndStatementStack.peekElement() : myStartStatementStack.peekElement(); + boolean gotoIsAtStart = isAndAnd ? myEndStatementStack.peekAtStart() : myStartStatementStack.peekAtStart(); + + Shortcut shortcut; + if (lValue != null) { + shortcut = lValue.booleanValue() == isOrOr ? Shortcut.STOP_EXPRESSION : Shortcut.SKIP_CURRENT_OPERAND; + } + else if (rValue != null && rValue.booleanValue() == isOrOr) { + shortcut = Shortcut.STOP_EXPRESSION; + } + else { + shortcut = Shortcut.NO_SHORTCUT; + } + + switch (shortcut) { + case NO_SHORTCUT: + myCurrentFlow.addInstruction(new ConditionalGoToInstruction(0, role, lOperand)); + addElementOffsetLater(gotoElement, gotoIsAtStart); + break; + + case STOP_EXPRESSION: + myCurrentFlow.addInstruction(new GoToInstruction(0, role)); + addElementOffsetLater(gotoElement, gotoIsAtStart); + rValue = null; + break; + + case SKIP_CURRENT_OPERAND: + break; + } + } + generateLOperand(rOperand, i == operands.length - 1 ? null : operands[i + 1], signTokenType); + + lOperand = rOperand; + lValue = rValue; + } + + finishElement(expression); + } + + private void generateLOperand(@Nonnull PsiExpression lOperand, @Nullable PsiExpression rOperand, @Nonnull IElementType signTokenType) { + if (rOperand != null) { + myStartJumpRoles.push(BranchingInstruction.Role.END); + myEndJumpRoles.push(BranchingInstruction.Role.END); + PsiElement then = signTokenType == JavaTokenType.OROR ? myStartStatementStack.peekElement() : rOperand; + boolean thenAtStart = signTokenType != JavaTokenType.OROR || myStartStatementStack.peekAtStart(); + myStartStatementStack.pushStatement(then, thenAtStart); + PsiElement elseS = signTokenType == JavaTokenType.ANDAND ? myEndStatementStack.peekElement() : rOperand; + boolean elseAtStart = signTokenType != JavaTokenType.ANDAND || myEndStatementStack.peekAtStart(); + myEndStatementStack.pushStatement(elseS, elseAtStart); + } + lOperand.accept(this); + if (rOperand != null) { + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + myStartJumpRoles.pop(); + myEndJumpRoles.pop(); + } + } + + private static boolean isInsideIfCondition(@Nonnull PsiExpression expression) { + PsiElement element = expression; + while (element instanceof PsiExpression) { + final PsiElement parent = element.getParent(); + if (parent instanceof PsiIfStatement && element == ((PsiIfStatement)parent).getCondition()) { + return true; + } + element = parent; } - } + return false; } - finishElement(expression); - } + private boolean shouldCalculateConstantExpression(@Nonnull PsiExpression expression) { + return myOptions.shouldEvaluateConstantIfCondition() || !isInsideIfCondition(expression); + } + + @Override + public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { + visitChildren(expression); + } + + private void visitChildren(@Nonnull PsiElement element) { + startElement(element); + + PsiElement[] children = element.getChildren(); + for (PsiElement child : children) { + ProgressManager.checkCanceled(); + child.accept(this); + } + + finishElement(element); + } + + @Override + public void visitConditionalExpression(PsiConditionalExpression expression) { + startElement(expression); + + final PsiExpression condition = expression.getCondition(); + final PsiExpression thenExpression = expression.getThenExpression(); + final PsiExpression elseExpression = expression.getElseExpression(); + generateConditionalStatementInstructions(expression, condition, thenExpression, elseExpression); + + finishElement(expression); + } + + @Override + public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { + startElement(expression); - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - startElement(expression); + final PsiExpression operand = expression.getOperand(); + operand.accept(this); - PsiExpression qualifier = expression.getQualifierExpression(); - if (qualifier != null) { - qualifier.accept(this); + PsiPattern pattern = expression.getPattern(); + if (pattern instanceof PsiTypeTestPattern) { + PsiPatternVariable variable = ((PsiTypeTestPattern)pattern).getPatternVariable(); + + if (variable != null) { + myCurrentFlow.addInstruction(new WriteVariableInstruction(variable)); + } + } + + finishElement(expression); } - PsiVariable variable = getUsedVariable(expression); - if (variable != null) { - generateReadInstruction(variable); + @Override + public void visitLiteralExpression(PsiLiteralExpression expression) { + startElement(expression); + finishElement(expression); + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + startElement(expression); + final PsiElement body = expression.getBody(); + if (body != null) { + List array = new ArrayList<>(); + addUsedVariables(array, body); + for (PsiVariable var : array) { + ProgressManager.checkCanceled(); + generateReadInstruction(var); + } + } + finishElement(expression); + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + ArrayDeque calls = new ArrayDeque<>(); + while (true) { + calls.addFirst(expression); + startElement(expression); + + PsiExpression qualifierExpression = expression.getMethodExpression().getQualifierExpression(); + expression = ObjectUtil.tryCast(PsiUtil.skipParenthesizedExprDown(qualifierExpression), PsiMethodCallExpression.class); + if (expression == null) { + if (qualifierExpression != null) { + qualifierExpression.accept(this); + } + break; + } + } + + for (PsiMethodCallExpression call : calls) { + final PsiExpressionList argumentList = call.getArgumentList(); + argumentList.accept(this); + // just to increase counter - there is some executable code here + emitEmptyInstruction(); + + //generate jumps to all handled exception handlers + generateExceptionJumps(call, ExceptionUtil.getUnhandledExceptions(call, call.getParent())); + + finishElement(call); + } } - finishElement(expression); - } + @Override + public void visitNewExpression(PsiNewExpression expression) { + startElement(expression); + int pc = myCurrentFlow.getSize(); + PsiElement[] children = expression.getChildren(); + for (PsiElement child : children) { + ProgressManager.checkCanceled(); + child.accept(this); + } + //generate jumps to all handled exception handlers + generateExceptionJumps(expression, ExceptionUtil.getUnhandledExceptions(expression, expression.getParent())); - @Override - public void visitSuperExpression(PsiSuperExpression expression) { - startElement(expression); - finishElement(expression); - } + if (pc == myCurrentFlow.getSize()) { + // generate at least one instruction for constructor call + emitEmptyInstruction(); + } - @Override - public void visitThisExpression(PsiThisExpression expression) { - startElement(expression); - finishElement(expression); - } + finishElement(expression); + } - @Override - public void visitTypeCastExpression(PsiTypeCastExpression expression) { - startElement(expression); - PsiExpression operand = expression.getOperand(); - if (operand != null) { - operand.accept(this); + @Override + public void visitParenthesizedExpression(PsiParenthesizedExpression expression) { + visitChildren(expression); } - finishElement(expression); - } - @Override - public void visitClass(PsiClass aClass) { - startElement(aClass); - // anonymous or local class - if (aClass instanceof PsiAnonymousClass) { - final PsiElement arguments = PsiTreeUtil.getChildOfType(aClass, PsiExpressionList.class); - if (arguments != null) { - arguments.accept(this); - } + @Override + public void visitPostfixExpression(PsiPostfixExpression expression) { + startElement(expression); + + IElementType op = expression.getOperationTokenType(); + PsiExpression operand = PsiUtil.skipParenthesizedExprDown(expression.getOperand()); + if (operand != null) { + operand.accept(this); + if (op == JavaTokenType.PLUSPLUS || op == JavaTokenType.MINUSMINUS) { + if (operand instanceof PsiReferenceExpression) { + PsiVariable variable = getUsedVariable((PsiReferenceExpression)operand); + if (variable != null) { + generateWriteInstruction(variable); + } + } + } + } + + finishElement(expression); } - List array = new ArrayList<>(); - addUsedVariables(array, aClass); - for (PsiVariable var : array) { - ProgressManager.checkCanceled(); - generateReadInstruction(var); + + @Override + public void visitPrefixExpression(PsiPrefixExpression expression) { + startElement(expression); + + PsiExpression operand = PsiUtil.skipParenthesizedExprDown(expression.getOperand()); + if (operand != null) { + IElementType operationSign = expression.getOperationTokenType(); + if (operationSign == JavaTokenType.EXCL) { + // negation inverts jump targets + PsiElement topStartStatement = myStartStatementStack.peekElement(); + boolean topAtStart = myStartStatementStack.peekAtStart(); + myStartStatementStack.pushStatement(myEndStatementStack.peekElement(), myEndStatementStack.peekAtStart()); + myEndStatementStack.pushStatement(topStartStatement, topAtStart); + } + + operand.accept(this); + + if (operationSign == JavaTokenType.EXCL) { + // negation inverts jump targets + myStartStatementStack.popStatement(); + myEndStatementStack.popStatement(); + } + + if (operand instanceof PsiReferenceExpression && + (operationSign == JavaTokenType.PLUSPLUS || operationSign == JavaTokenType.MINUSMINUS)) { + PsiVariable variable = getUsedVariable((PsiReferenceExpression)operand); + if (variable != null) { + generateWriteInstruction(variable); + } + } + } + + finishElement(expression); } - finishElement(aClass); - } - private void addUsedVariables(@Nonnull List array, @Nonnull PsiElement scope) { - if (scope instanceof PsiReferenceExpression) { - PsiVariable variable = getUsedVariable((PsiReferenceExpression) scope); - if (variable != null) { - if (!array.contains(variable)) { - array.add(variable); + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + startElement(expression); + + PsiExpression qualifier = expression.getQualifierExpression(); + if (qualifier != null) { + qualifier.accept(this); + } + + PsiVariable variable = getUsedVariable(expression); + if (variable != null) { + generateReadInstruction(variable); } - } + + finishElement(expression); } - PsiElement[] children = scope.getChildren(); - for (PsiElement child : children) { - ProgressManager.checkCanceled(); - addUsedVariables(array, child); + + @Override + public void visitSuperExpression(PsiSuperExpression expression) { + startElement(expression); + finishElement(expression); } - } - private void generateReadInstruction(@Nonnull PsiVariable variable) { - Instruction instruction = new ReadVariableInstruction(variable); - myCurrentFlow.addInstruction(instruction); - } + @Override + public void visitThisExpression(PsiThisExpression expression) { + startElement(expression); + finishElement(expression); + } - private void generateWriteInstruction(@Nonnull PsiVariable variable) { - Instruction instruction = new WriteVariableInstruction(variable); - myCurrentFlow.addInstruction(instruction); - } + @Override + public void visitTypeCastExpression(PsiTypeCastExpression expression) { + startElement(expression); + PsiExpression operand = expression.getOperand(); + if (operand != null) { + operand.accept(this); + } + finishElement(expression); + } - @Nullable - private PsiVariable getUsedVariable(@Nonnull PsiReferenceExpression refExpr) { - if (refExpr.getParent() instanceof PsiMethodCallExpression) { - return null; + @Override + public void visitClass(PsiClass aClass) { + startElement(aClass); + // anonymous or local class + if (aClass instanceof PsiAnonymousClass) { + final PsiElement arguments = PsiTreeUtil.getChildOfType(aClass, PsiExpressionList.class); + if (arguments != null) { + arguments.accept(this); + } + } + List array = new ArrayList<>(); + addUsedVariables(array, aClass); + for (PsiVariable var : array) { + ProgressManager.checkCanceled(); + generateReadInstruction(var); + } + finishElement(aClass); } - return myPolicy.getUsedVariable(refExpr); - } - private static class FinallyBlockSubroutine { - private final PsiElement myElement; - private final List myCalls; + private void addUsedVariables(@Nonnull List array, @Nonnull PsiElement scope) { + if (scope instanceof PsiReferenceExpression) { + PsiVariable variable = getUsedVariable((PsiReferenceExpression)scope); + if (variable != null) { + if (!array.contains(variable)) { + array.add(variable); + } + } + } - FinallyBlockSubroutine(@Nonnull PsiElement element) { - myElement = element; - myCalls = new ArrayList<>(); + PsiElement[] children = scope.getChildren(); + for (PsiElement child : children) { + ProgressManager.checkCanceled(); + addUsedVariables(array, child); + } } - @Nonnull - public PsiElement getElement() { - return myElement; + private void generateReadInstruction(@Nonnull PsiVariable variable) { + Instruction instruction = new ReadVariableInstruction(variable); + myCurrentFlow.addInstruction(instruction); } - @Nonnull - public List getCalls() { - return myCalls; + private void generateWriteInstruction(@Nonnull PsiVariable variable) { + Instruction instruction = new WriteVariableInstruction(variable); + myCurrentFlow.addInstruction(instruction); } - private void addCall(@Nonnull CallInstruction callInstruction) { - myCalls.add(callInstruction); + @Nullable + private PsiVariable getUsedVariable(@Nonnull PsiReferenceExpression refExpr) { + if (refExpr.getParent() instanceof PsiMethodCallExpression) { + return null; + } + return myPolicy.getUsedVariable(refExpr); } - } - private static final class SubRangeInfo { - final PsiElement myElement; - final int myStart; - final int myEnd; + private static class FinallyBlockSubroutine { + private final PsiElement myElement; + private final List myCalls; + + FinallyBlockSubroutine(@Nonnull PsiElement element) { + myElement = element; + myCalls = new ArrayList<>(); + } - private SubRangeInfo(PsiElement element, int start, int end) { - myElement = element; - myStart = start; - myEnd = end; + @Nonnull + public PsiElement getElement() { + return myElement; + } + + @Nonnull + public List getCalls() { + return myCalls; + } + + private void addCall(@Nonnull CallInstruction callInstruction) { + myCalls.add(callInstruction); + } + } + + private static final class SubRangeInfo { + final PsiElement myElement; + final int myStart; + final int myEnd; + + private SubRangeInfo(PsiElement element, int start, int end) { + myElement = element; + myStart = start; + myEnd = end; + } } - } } diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/PsiSubstitutorImpl.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/PsiSubstitutorImpl.java index 7b4532a05e..8dfcfd7b10 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/PsiSubstitutorImpl.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/PsiSubstitutorImpl.java @@ -27,388 +27,401 @@ * @author ik, dsl */ public final class PsiSubstitutorImpl implements PsiSubstitutor { - private static final Logger LOG = Logger.getInstance(PsiSubstitutorImpl.class); + private static final Logger LOG = Logger.getInstance(PsiSubstitutorImpl.class); - private static final HashingStrategy PSI_EQUIVALENCE = new HashingStrategy<>() { - @Override - public int hashCode(PsiTypeParameter parameter) { - return Comparing.hashcode(parameter.getName()); - } - - @Override - public boolean equals(PsiTypeParameter element1, PsiTypeParameter element2) { - return element1.getManager().areElementsEquivalent(element1, element2); - } - }; - private static final UnmodifiableHashMap EMPTY_MAP = UnmodifiableHashMap.empty(PSI_EQUIVALENCE); - - @Nonnull - private final UnmodifiableHashMap mySubstitutionMap; - private final SubstitutionVisitor mySimpleSubstitutionVisitor = new SubstitutionVisitor(); - - PsiSubstitutorImpl(@Nonnull Map map) { - mySubstitutionMap = UnmodifiableHashMap.fromMap(PSI_EQUIVALENCE, map); - } - - private PsiSubstitutorImpl(@Nonnull UnmodifiableHashMap map, - @Nonnull PsiTypeParameter additionalKey, - @Nullable PsiType additionalValue) { - mySubstitutionMap = map.with(additionalKey, additionalValue); - } - - PsiSubstitutorImpl(@Nonnull PsiTypeParameter typeParameter, PsiType mapping) { - mySubstitutionMap = EMPTY_MAP.with(typeParameter, mapping); - } - - PsiSubstitutorImpl(@Nonnull PsiClass parentClass, PsiType[] mappings) { - this(putAllInternal(EMPTY_MAP, parentClass, mappings)); - } - - @Override - public PsiType substitute(@Nonnull PsiTypeParameter typeParameter) { - PsiType type = getFromMap(typeParameter); - return PsiType.VOID.equals(type) ? JavaPsiFacade.getElementFactory(typeParameter.getProject()).createType(typeParameter) : type; - } - - /** - * @return type mapped to type parameter; null if type parameter is mapped to null; or PsiType.VOID if no mapping exists - */ - private PsiType getFromMap(@Nonnull PsiTypeParameter typeParameter) { - if (typeParameter instanceof LightTypeParameter && ((LightTypeParameter) typeParameter).useDelegateToSubstitute()) { - typeParameter = ((LightTypeParameter) typeParameter).getDelegate(); - } - return mySubstitutionMap.getOrDefault(typeParameter, PsiType.VOID); - } + private static final HashingStrategy PSI_EQUIVALENCE = new HashingStrategy<>() { + @Override + public int hashCode(PsiTypeParameter parameter) { + return Comparing.hashcode(parameter.getName()); + } - @Override - public PsiType substitute(PsiType type) { - if (type == null) { - return null; - } - PsiUtil.ensureValidType(type); - PsiType substituted = type.accept(mySimpleSubstitutionVisitor); - return correctExternalSubstitution(substituted, type); - } - - @Override - public PsiType substituteWithBoundsPromotion(@Nonnull PsiTypeParameter typeParameter) { - final PsiType substituted = substitute(typeParameter); - if (substituted instanceof PsiWildcardType && !((PsiWildcardType) substituted).isSuper()) { - final PsiWildcardType wildcardType = (PsiWildcardType) substituted; - final PsiType glb = PsiCapturedWildcardType.captureUpperBound(typeParameter, wildcardType, this); - if (glb instanceof PsiWildcardType) { - return glb; - } - if (glb instanceof PsiCapturedWildcardType) { - PsiWildcardType wildcard = ((PsiCapturedWildcardType) glb).getWildcard(); - if (!wildcard.isSuper()) { - return wildcard; + @Override + public boolean equals(PsiTypeParameter element1, PsiTypeParameter element2) { + return element1.getManager().areElementsEquivalent(element1, element2); } - } + }; + private static final UnmodifiableHashMap EMPTY_MAP = UnmodifiableHashMap.empty(PSI_EQUIVALENCE); + + @Nonnull + private final UnmodifiableHashMap mySubstitutionMap; + private final SubstitutionVisitor mySimpleSubstitutionVisitor = new SubstitutionVisitor(); - if (glb != null) { - return PsiWildcardType.createExtends(typeParameter.getManager(), glb); - } + PsiSubstitutorImpl(@Nonnull Map map) { + mySubstitutionMap = UnmodifiableHashMap.fromMap(PSI_EQUIVALENCE, map); } - return substituted; - } - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; + private PsiSubstitutorImpl( + @Nonnull UnmodifiableHashMap map, + @Nonnull PsiTypeParameter additionalKey, + @Nullable PsiType additionalValue + ) { + mySubstitutionMap = map.with(additionalKey, additionalValue); } - return o instanceof PsiSubstitutorImpl && mySubstitutionMap.equals(((PsiSubstitutorImpl) o).mySubstitutionMap); - } - - @Override - public int hashCode() { - return mySubstitutionMap.hashCode(); - } - - private PsiType rawTypeForTypeParameter(@Nonnull PsiTypeParameter typeParameter) { - final PsiClassType[] extendsTypes = typeParameter.getExtendsListTypes(); - if (extendsTypes.length > 0) { - // First bound - return RecursionManager.doPreventingRecursion(extendsTypes[0], true, () -> substitute(extendsTypes[0])); + + PsiSubstitutorImpl(@Nonnull PsiTypeParameter typeParameter, PsiType mapping) { + mySubstitutionMap = EMPTY_MAP.with(typeParameter, mapping); } - // Object - return PsiType.getJavaLangObject(typeParameter.getManager(), typeParameter.getResolveScope()); - } - - @Nonnull - private static TypeAnnotationProvider getMergedProvider(@Nonnull PsiType type1, @Nonnull PsiType type2) { - if (type1.getAnnotationProvider() == TypeAnnotationProvider.EMPTY && !(type1 instanceof PsiClassReferenceType)) { - return type2.getAnnotationProvider(); + + PsiSubstitutorImpl(@Nonnull PsiClass parentClass, PsiType[] mappings) { + this(putAllInternal(EMPTY_MAP, parentClass, mappings)); } - if (type2.getAnnotationProvider() == TypeAnnotationProvider.EMPTY && !(type2 instanceof PsiClassReferenceType)) { - return type1.getAnnotationProvider(); + + @Override + public PsiType substitute(@Nonnull PsiTypeParameter typeParameter) { + PsiType type = getFromMap(typeParameter); + return PsiType.VOID.equals(type) ? JavaPsiFacade.getElementFactory(typeParameter.getProject()).createType(typeParameter) : type; } - return () -> ArrayUtil.mergeArrays(type1.getAnnotations(), type2.getAnnotations()); - } - private class SubstitutionVisitor extends PsiTypeMapper { + /** + * @return type mapped to type parameter; null if type parameter is mapped to null; or PsiType.VOID if no mapping exists + */ + private PsiType getFromMap(@Nonnull PsiTypeParameter typeParameter) { + if (typeParameter instanceof LightTypeParameter && ((LightTypeParameter)typeParameter).useDelegateToSubstitute()) { + typeParameter = ((LightTypeParameter)typeParameter).getDelegate(); + } + return mySubstitutionMap.getOrDefault(typeParameter, PsiType.VOID); + } @Override - public PsiType visitType(@Nonnull PsiType type) { - return null; + public PsiType substitute(PsiType type) { + if (type == null) { + return null; + } + PsiUtil.ensureValidType(type); + PsiType substituted = type.accept(mySimpleSubstitutionVisitor); + return correctExternalSubstitution(substituted, type); } @Override - public PsiType visitWildcardType(@Nonnull PsiWildcardType wildcardType) { - final PsiType bound = wildcardType.getBound(); - if (bound == null) { - return wildcardType; - } else { - final PsiType newBound = bound.accept(this); - if (newBound == null) { - return null; - } - assert newBound.isValid() : newBound.getClass() + "; " + bound.isValid(); - if (newBound instanceof PsiWildcardType) { - final PsiType newBoundBound = ((PsiWildcardType) newBound).getBound(); - return !((PsiWildcardType) newBound).isBounded() ? PsiWildcardType.createUnbounded(wildcardType.getManager()) - : rebound(wildcardType, newBoundBound); + public PsiType substituteWithBoundsPromotion(@Nonnull PsiTypeParameter typeParameter) { + final PsiType substituted = substitute(typeParameter); + if (substituted instanceof PsiWildcardType && !((PsiWildcardType)substituted).isSuper()) { + final PsiWildcardType wildcardType = (PsiWildcardType)substituted; + final PsiType glb = PsiCapturedWildcardType.captureUpperBound(typeParameter, wildcardType, this); + if (glb instanceof PsiWildcardType) { + return glb; + } + if (glb instanceof PsiCapturedWildcardType) { + PsiWildcardType wildcard = ((PsiCapturedWildcardType)glb).getWildcard(); + if (!wildcard.isSuper()) { + return wildcard; + } + } + + if (glb != null) { + return PsiWildcardType.createExtends(typeParameter.getManager(), glb); + } } + return substituted; + } - return newBound == PsiType.NULL ? newBound : rebound(wildcardType, newBound); - } + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + return o instanceof PsiSubstitutorImpl && mySubstitutionMap.equals(((PsiSubstitutorImpl)o).mySubstitutionMap); } - @Nonnull - private PsiWildcardType rebound(@Nonnull PsiWildcardType type, @Nonnull PsiType newBound) { - LOG.assertTrue(type.getBound() != null); - LOG.assertTrue(newBound.isValid()); + @Override + public int hashCode() { + return mySubstitutionMap.hashCode(); + } - if (type.isExtends()) { - if (newBound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { - return PsiWildcardType.createUnbounded(type.getManager()); + private PsiType rawTypeForTypeParameter(@Nonnull PsiTypeParameter typeParameter) { + final PsiClassType[] extendsTypes = typeParameter.getExtendsListTypes(); + if (extendsTypes.length > 0) { + // First bound + return RecursionManager.doPreventingRecursion(extendsTypes[0], true, () -> substitute(extendsTypes[0])); } - return PsiWildcardType.createExtends(type.getManager(), newBound); - } - return PsiWildcardType.createSuper(type.getManager(), newBound); + // Object + return PsiType.getJavaLangObject(typeParameter.getManager(), typeParameter.getResolveScope()); } - @Override - public PsiType visitClassType(@Nonnull final PsiClassType classType) { - final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); - final PsiClass aClass = resolveResult.getElement(); - if (aClass == null) { - return classType; - } - - PsiUtilCore.ensureValid(aClass); - if (aClass instanceof PsiTypeParameter) { - final PsiTypeParameter typeParameter = (PsiTypeParameter) aClass; - final PsiType result = getFromMap(typeParameter); - if (PsiType.VOID.equals(result)) { - return classType; + @Nonnull + private static TypeAnnotationProvider getMergedProvider(@Nonnull PsiType type1, @Nonnull PsiType type2) { + if (type1.getAnnotationProvider() == TypeAnnotationProvider.EMPTY && !(type1 instanceof PsiClassReferenceType)) { + return type2.getAnnotationProvider(); } - if (result != null) { - PsiUtil.ensureValidType(result); - if (result instanceof PsiClassType || result instanceof PsiArrayType || result instanceof PsiWildcardType) { - return result.annotate(getMergedProvider(classType, result)); - } + if (type2.getAnnotationProvider() == TypeAnnotationProvider.EMPTY && !(type2 instanceof PsiClassReferenceType)) { + return type1.getAnnotationProvider(); } - return result; - } - final Map hashMap = new HashMap<>(2); - if (!processClass(aClass, resolveResult.getSubstitutor(), hashMap)) { - return null; - } - PsiClassType result = JavaPsiFacade.getElementFactory(aClass.getProject()) - .createType(aClass, PsiSubstitutor.createSubstitutor(hashMap), classType.getLanguageLevel()); - PsiUtil.ensureValidType(result); - return result.annotate(classType.getAnnotationProvider()); + return () -> ArrayUtil.mergeArrays(type1.getAnnotations(), type2.getAnnotations()); } - private PsiType substituteInternal(@Nonnull PsiType type) { - return type.accept(this); + private class SubstitutionVisitor extends PsiTypeMapper { + + @Override + public PsiType visitType(@Nonnull PsiType type) { + return null; + } + + @Override + public PsiType visitWildcardType(@Nonnull PsiWildcardType wildcardType) { + final PsiType bound = wildcardType.getBound(); + if (bound == null) { + return wildcardType; + } + else { + final PsiType newBound = bound.accept(this); + if (newBound == null) { + return null; + } + assert newBound.isValid() : newBound.getClass() + "; " + bound.isValid(); + if (newBound instanceof PsiWildcardType) { + final PsiType newBoundBound = ((PsiWildcardType)newBound).getBound(); + return !((PsiWildcardType)newBound).isBounded() ? PsiWildcardType.createUnbounded(wildcardType.getManager()) + : rebound(wildcardType, newBoundBound); + } + + return newBound == PsiType.NULL ? newBound : rebound(wildcardType, newBound); + } + } + + @Nonnull + private PsiWildcardType rebound(@Nonnull PsiWildcardType type, @Nonnull PsiType newBound) { + LOG.assertTrue(type.getBound() != null); + LOG.assertTrue(newBound.isValid()); + + if (type.isExtends()) { + if (newBound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { + return PsiWildcardType.createUnbounded(type.getManager()); + } + return PsiWildcardType.createExtends(type.getManager(), newBound); + } + return PsiWildcardType.createSuper(type.getManager(), newBound); + } + + @Override + public PsiType visitClassType(@Nonnull final PsiClassType classType) { + final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); + final PsiClass aClass = resolveResult.getElement(); + if (aClass == null) { + return classType; + } + + PsiUtilCore.ensureValid(aClass); + if (aClass instanceof PsiTypeParameter) { + final PsiTypeParameter typeParameter = (PsiTypeParameter)aClass; + final PsiType result = getFromMap(typeParameter); + if (PsiType.VOID.equals(result)) { + return classType; + } + if (result != null) { + PsiUtil.ensureValidType(result); + if (result instanceof PsiClassType || result instanceof PsiArrayType || result instanceof PsiWildcardType) { + return result.annotate(getMergedProvider(classType, result)); + } + } + return result; + } + final Map hashMap = new HashMap<>(2); + if (!processClass(aClass, resolveResult.getSubstitutor(), hashMap)) { + return null; + } + PsiClassType result = JavaPsiFacade.getElementFactory(aClass.getProject()) + .createType(aClass, PsiSubstitutor.createSubstitutor(hashMap), classType.getLanguageLevel()); + PsiUtil.ensureValidType(result); + return result.annotate(classType.getAnnotationProvider()); + } + + private PsiType substituteInternal(@Nonnull PsiType type) { + return type.accept(this); + } + + private boolean processClass( + @Nonnull PsiClass resolve, + @Nonnull PsiSubstitutor originalSubstitutor, + @Nonnull Map substMap + ) { + final PsiTypeParameter[] params = resolve.getTypeParameters(); + for (final PsiTypeParameter param : params) { + final PsiType original = originalSubstitutor.substitute(param); + if (original == null) { + substMap.put(param, null); + } + else { + substMap.put(param, substituteInternal(original)); + } + } + if (resolve.hasModifierProperty(PsiModifier.STATIC)) { + return true; + } + + final PsiClass containingClass = resolve.getContainingClass(); + return containingClass == null || + processClass(containingClass, originalSubstitutor, substMap); + } } - private boolean processClass(@Nonnull PsiClass resolve, @Nonnull PsiSubstitutor originalSubstitutor, @Nonnull Map substMap) { - final PsiTypeParameter[] params = resolve.getTypeParameters(); - for (final PsiTypeParameter param : params) { - final PsiType original = originalSubstitutor.substitute(param); - if (original == null) { - substMap.put(param, null); - } else { - substMap.put(param, substituteInternal(original)); + private PsiType correctExternalSubstitution(PsiType substituted, @Nonnull PsiType original) { + if (substituted != null) { + return substituted; } - } - if (resolve.hasModifierProperty(PsiModifier.STATIC)) { - return true; - } + return original.accept(new PsiTypeVisitor() { + @Override + public PsiType visitArrayType(@Nonnull PsiArrayType arrayType) { + return new PsiArrayType(arrayType.getComponentType().accept(this)); + } + + @Override + public PsiType visitEllipsisType(@Nonnull PsiEllipsisType ellipsisType) { + return new PsiEllipsisType(ellipsisType.getComponentType().accept(this)); + } + + @Override + public PsiType visitClassType(@Nonnull PsiClassType classType) { + PsiClass aClass = classType.resolve(); + if (aClass == null) { + return classType; + } + if (aClass instanceof PsiTypeParameter) { + return rawTypeForTypeParameter((PsiTypeParameter)aClass); + } + return JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass); + } + + @Override + public PsiType visitType(@Nonnull PsiType type) { + return null; + } + }); + } - final PsiClass containingClass = resolve.getContainingClass(); - return containingClass == null || - processClass(containingClass, originalSubstitutor, substMap); + @Override + protected PsiSubstitutorImpl clone() { + throw new UnsupportedOperationException(); } - } - private PsiType correctExternalSubstitution(PsiType substituted, @Nonnull PsiType original) { - if (substituted != null) { - return substituted; + @Nonnull + @Override + public PsiSubstitutor put(@Nonnull PsiTypeParameter typeParameter, PsiType mapping) { + if (mapping != null && !mapping.isValid()) { + LOG.error("Invalid type in substitutor: " + mapping + "; " + mapping.getClass()); + } + return new PsiSubstitutorImpl(mySubstitutionMap, typeParameter, mapping); } - return original.accept(new PsiTypeVisitor() { - @Override - public PsiType visitArrayType(@Nonnull PsiArrayType arrayType) { - return new PsiArrayType(arrayType.getComponentType().accept(this)); - } - - @Override - public PsiType visitEllipsisType(@Nonnull PsiEllipsisType ellipsisType) { - return new PsiEllipsisType(ellipsisType.getComponentType().accept(this)); - } - - @Override - public PsiType visitClassType(@Nonnull PsiClassType classType) { - PsiClass aClass = classType.resolve(); - if (aClass == null) { - return classType; + + @Nonnull + private static UnmodifiableHashMap putAllInternal( + @Nonnull UnmodifiableHashMap originalMap, + @Nonnull PsiClass parentClass, + PsiType[] mappings + ) { + final PsiTypeParameter[] params = parentClass.getTypeParameters(); + if (params.length == 0) { + return originalMap; } - if (aClass instanceof PsiTypeParameter) { - return rawTypeForTypeParameter((PsiTypeParameter) aClass); + UnmodifiableHashMap newMap = originalMap; + + for (int i = 0; i < params.length; i++) { + PsiTypeParameter param = params[i]; + assert param != null; + if (mappings != null && mappings.length > i) { + PsiType mapping = mappings[i]; + newMap = newMap.with(param, mapping); + if (mapping != null && !mapping.isValid()) { + LOG.error("Invalid type in substitutor: " + mapping); + } + } + else { + newMap = newMap.with(param, null); + } } - return JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass); - } - - @Override - public PsiType visitType(@Nonnull PsiType type) { - return null; - } - }); - } - - @Override - protected PsiSubstitutorImpl clone() { - throw new UnsupportedOperationException(); - } - - @Nonnull - @Override - public PsiSubstitutor put(@Nonnull PsiTypeParameter typeParameter, PsiType mapping) { - if (mapping != null && !mapping.isValid()) { - LOG.error("Invalid type in substitutor: " + mapping + "; " + mapping.getClass()); + return newMap; } - return new PsiSubstitutorImpl(mySubstitutionMap, typeParameter, mapping); - } - - @Nonnull - private static UnmodifiableHashMap putAllInternal(@Nonnull UnmodifiableHashMap originalMap, - @Nonnull PsiClass parentClass, - PsiType[] mappings) { - final PsiTypeParameter[] params = parentClass.getTypeParameters(); - if (params.length == 0) { - return originalMap; + + @Nonnull + @Override + public PsiSubstitutor putAll(@Nonnull PsiClass parentClass, PsiType[] mappings) { + return new PsiSubstitutorImpl(putAllInternal(mySubstitutionMap, parentClass, mappings)); } - UnmodifiableHashMap newMap = originalMap; - - for (int i = 0; i < params.length; i++) { - PsiTypeParameter param = params[i]; - assert param != null; - if (mappings != null && mappings.length > i) { - PsiType mapping = mappings[i]; - newMap = newMap.with(param, mapping); - if (mapping != null && !mapping.isValid()) { - LOG.error("Invalid type in substitutor: " + mapping); + + @Nonnull + @Override + public PsiSubstitutor putAll(@Nonnull PsiSubstitutor another) { + if (another instanceof EmptySubstitutor) { + return this; } - } else { - newMap = newMap.with(param, null); - } + final PsiSubstitutorImpl anotherImpl = (PsiSubstitutorImpl)another; + return putAll(anotherImpl.mySubstitutionMap); } - return newMap; - } - - @Nonnull - @Override - public PsiSubstitutor putAll(@Nonnull PsiClass parentClass, PsiType[] mappings) { - return new PsiSubstitutorImpl(putAllInternal(mySubstitutionMap, parentClass, mappings)); - } - - @Nonnull - @Override - public PsiSubstitutor putAll(@Nonnull PsiSubstitutor another) { - if (another instanceof EmptySubstitutor) { - return this; + + @Nonnull + @Override + public PsiSubstitutor putAll(@Nonnull Map map) { + if (map.isEmpty()) { + return this; + } + return new PsiSubstitutorImpl(mySubstitutionMap.withAll(map)); } - final PsiSubstitutorImpl anotherImpl = (PsiSubstitutorImpl) another; - return putAll(anotherImpl.mySubstitutionMap); - } - - @Nonnull - @Override - public PsiSubstitutor putAll(@Nonnull Map map) { - if (map.isEmpty()) { - return this; + + @Override + public String toString() { + @NonNls StringBuilder buffer = new StringBuilder(); + final Set> set = mySubstitutionMap.entrySet(); + for (Map.Entry entry : set) { + final PsiTypeParameter typeParameter = entry.getKey(); + buffer.append(typeParameter.getName()); + final PsiElement owner = typeParameter.getOwner(); + if (owner instanceof PsiClass) { + buffer.append(" of "); + buffer.append(((PsiClass)owner).getQualifiedName()); + } + else if (owner instanceof PsiMethod) { + buffer.append(" of "); + buffer.append(((PsiMethod)owner).getName()); + buffer.append(" in "); + PsiClass aClass = ((PsiMethod)owner).getContainingClass(); + buffer.append(aClass != null ? aClass.getQualifiedName() : ""); + } + buffer.append(" -> "); + if (entry.getValue() != null) { + buffer.append(entry.getValue().getCanonicalText()); + } + else { + buffer.append("null"); + } + buffer.append('\n'); + } + return buffer.toString(); + } + + @Override + public boolean isValid() { + for (Map.Entry entry : mySubstitutionMap.entrySet()) { + if (!entry.getKey().isValid()) { + return false; + } + + PsiType type = entry.getValue(); + if (type != null && !type.isValid()) { + return false; + } + } + return true; } - return new PsiSubstitutorImpl(mySubstitutionMap.withAll(map)); - } - - @Override - public String toString() { - @NonNls StringBuilder buffer = new StringBuilder(); - final Set> set = mySubstitutionMap.entrySet(); - for (Map.Entry entry : set) { - final PsiTypeParameter typeParameter = entry.getKey(); - buffer.append(typeParameter.getName()); - final PsiElement owner = typeParameter.getOwner(); - if (owner instanceof PsiClass) { - buffer.append(" of "); - buffer.append(((PsiClass) owner).getQualifiedName()); - } else if (owner instanceof PsiMethod) { - buffer.append(" of "); - buffer.append(((PsiMethod) owner).getName()); - buffer.append(" in "); - PsiClass aClass = ((PsiMethod) owner).getContainingClass(); - buffer.append(aClass != null ? aClass.getQualifiedName() : ""); - } - buffer.append(" -> "); - if (entry.getValue() != null) { - buffer.append(entry.getValue().getCanonicalText()); - } else { - buffer.append("null"); - } - buffer.append('\n'); + + @Override + public void ensureValid() { + mySubstitutionMap.values().forEach(type -> { + if (type != null) { + PsiUtil.ensureValidType(type); + } + }); } - return buffer.toString(); - } - - @Override - public boolean isValid() { - for (Map.Entry entry : mySubstitutionMap.entrySet()) { - if (!entry.getKey().isValid()) { - return false; - } - - PsiType type = entry.getValue(); - if (type != null && !type.isValid()) { - return false; - } + + @Override + @Nonnull + public Map getSubstitutionMap() { + return mySubstitutionMap; } - return true; - } - @Override - public void ensureValid() { - mySubstitutionMap.values().forEach(type -> { - if (type != null) { - PsiUtil.ensureValidType(type); - } - }); - } - - @Override - @Nonnull - public Map getSubstitutionMap() { - return mySubstitutionMap; - } - - /** - * @deprecated use {@link PsiSubstitutor#createSubstitutor(Map)} - */ - @Deprecated - public static PsiSubstitutor createSubstitutor(@Nullable Map map) { - return PsiSubstitutor.createSubstitutor(map); - } + /** + * @deprecated use {@link PsiSubstitutor#createSubstitutor(Map)} + */ + @Deprecated + public static PsiSubstitutor createSubstitutor(@Nullable Map map) { + return PsiSubstitutor.createSubstitutor(map); + } } diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/cache/TypeInfo.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/cache/TypeInfo.java index 990fc8c1ee..6fcf730268 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/cache/TypeInfo.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/cache/TypeInfo.java @@ -26,696 +26,727 @@ * Represents a type encoded inside a stub tree */ public /*sealed*/ abstract class TypeInfo { - private static final int HAS_TYPE_ANNOTATIONS = 0x80; - - public static final TypeInfo[] EMPTY_ARRAY = {}; - - private static final String[] ourIndexFrequentType; - private static final ObjectIntMap ourFrequentTypeIndex; - private static final int ourTypeLengthMask; - - static { - int typeLengthMask = 0; - ourIndexFrequentType = new String[]{ - "", - "boolean", "byte", "char", "double", "float", "int", "long", "null", "short", "void", - CommonClassNames.JAVA_LANG_OBJECT_SHORT, CommonClassNames.JAVA_LANG_OBJECT, - CommonClassNames.JAVA_LANG_STRING_SHORT, CommonClassNames.JAVA_LANG_STRING - }; - - ourFrequentTypeIndex = ObjectMaps.newObjectIntHashMap(); - for (int i = 0; i < ourIndexFrequentType.length; i++) { - String type = ourIndexFrequentType[i]; - ourFrequentTypeIndex.putInt(type, i); - assert type.length() < 32; - typeLengthMask |= (1 << type.length()); + private static final int HAS_TYPE_ANNOTATIONS = 0x80; + + public static final TypeInfo[] EMPTY_ARRAY = {}; + + private static final String[] ourIndexFrequentType; + private static final ObjectIntMap ourFrequentTypeIndex; + private static final int ourTypeLengthMask; + + static { + int typeLengthMask = 0; + ourIndexFrequentType = new String[]{ + "", + "boolean", + "byte", + "char", + "double", + "float", + "int", + "long", + "null", + "short", + "void", + CommonClassNames.JAVA_LANG_OBJECT_SHORT, + CommonClassNames.JAVA_LANG_OBJECT, + CommonClassNames.JAVA_LANG_STRING_SHORT, + CommonClassNames.JAVA_LANG_STRING + }; + + ourFrequentTypeIndex = ObjectMaps.newObjectIntHashMap(); + for (int i = 0; i < ourIndexFrequentType.length; i++) { + String type = ourIndexFrequentType[i]; + ourFrequentTypeIndex.putInt(type, i); + assert type.length() < 32; + typeLengthMask |= (1 << type.length()); + } + assert ourFrequentTypeIndex.size() == ourIndexFrequentType.length; + ourTypeLengthMask = typeLengthMask; } - assert ourFrequentTypeIndex.size() == ourIndexFrequentType.length; - ourTypeLengthMask = typeLengthMask; - } - - private static final TypeKind[] ALL_KINDS = TypeKind.values(); - private static final Map TEXT_TO_KIND = StreamEx.of(ALL_KINDS).mapToEntry(kind -> kind.text, kind -> kind) - .nonNullKeys().toImmutableMap(); - - /** - * Kind of a type. - * - * @implNote Ordinal values are used in the serialization protocol. - * Any changes in this enum should be accompanied by serialization version bump. - */ - public enum TypeKind { - /** - * Absent type (e.g., constructor return value) - */ - NULL, - // Reference - /** - * Simple reference, no outer class, no generic parameters - */ - REF, - /** - * Reference with generic parameters - */ - GENERIC, - - // References to widely used classes (skip encoding the class name) - JAVA_LANG_OBJECT(CommonClassNames.JAVA_LANG_OBJECT), - JAVA_LANG_STRING(CommonClassNames.JAVA_LANG_STRING), - JAVA_LANG_THROWABLE(CommonClassNames.JAVA_LANG_THROWABLE), - JAVA_LANG_EXCEPTION(CommonClassNames.JAVA_LANG_EXCEPTION), - JAVA_UTIL_COLLECTION(CommonClassNames.JAVA_UTIL_COLLECTION), - JAVA_UTIL_LIST(CommonClassNames.JAVA_UTIL_LIST), - JAVA_LANG_ITERABLE(CommonClassNames.JAVA_LANG_ITERABLE), - JAVA_UTIL_ITERATOR(CommonClassNames.JAVA_UTIL_ITERATOR), - JAVA_UTIL_MAP(CommonClassNames.JAVA_UTIL_MAP), - JAVA_LANG_ANNOTATION_ANNOTATION(CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION), + private static final TypeKind[] ALL_KINDS = TypeKind.values(); + private static final Map TEXT_TO_KIND = StreamEx.of(ALL_KINDS).mapToEntry(kind -> kind.text, kind -> kind) + .nonNullKeys().toImmutableMap(); /** - * Reference with outer class (which may probably be inner as well, or have generic parameters), - * but current ref has no generic parameters, like {@code Outer.Inner}, or {@code Outer1.Outer2.Inner} - */ - INNER, - /** - * Reference with outer class, which is a simple {@link #REF} itself (avoid encoding inner kind), like {@code Outer.Inner} - */ - INNER_SIMPLE, - /** - * Reference with arbitrary outer class and generic parameters, like {@code Outer.Inner}, or {@code Outer.Inner} + * Kind of a type. + * + * @implNote Ordinal values are used in the serialization protocol. + * Any changes in this enum should be accompanied by serialization version bump. */ - INNER_GENERIC, - // Derived - /** - * Wildcard type, like {@code ? extends X} - */ - EXTENDS, - /** - * Wildcard type, like {@code ? super X} - */ - SUPER, + public enum TypeKind { + /** + * Absent type (e.g., constructor return value) + */ + NULL, + // Reference + + /** + * Simple reference, no outer class, no generic parameters + */ + REF, + /** + * Reference with generic parameters + */ + GENERIC, + + // References to widely used classes (skip encoding the class name) + JAVA_LANG_OBJECT(CommonClassNames.JAVA_LANG_OBJECT), + JAVA_LANG_STRING(CommonClassNames.JAVA_LANG_STRING), + JAVA_LANG_THROWABLE(CommonClassNames.JAVA_LANG_THROWABLE), + JAVA_LANG_EXCEPTION(CommonClassNames.JAVA_LANG_EXCEPTION), + JAVA_UTIL_COLLECTION(CommonClassNames.JAVA_UTIL_COLLECTION), + JAVA_UTIL_LIST(CommonClassNames.JAVA_UTIL_LIST), + JAVA_LANG_ITERABLE(CommonClassNames.JAVA_LANG_ITERABLE), + JAVA_UTIL_ITERATOR(CommonClassNames.JAVA_UTIL_ITERATOR), + JAVA_UTIL_MAP(CommonClassNames.JAVA_UTIL_MAP), + JAVA_LANG_ANNOTATION_ANNOTATION(CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION), + + /** + * Reference with outer class (which may probably be inner as well, or have generic parameters), + * but current ref has no generic parameters, like {@code Outer.Inner}, or {@code Outer1.Outer2.Inner} + */ + INNER, + /** + * Reference with outer class, which is a simple {@link #REF} itself (avoid encoding inner kind), like {@code Outer.Inner} + */ + INNER_SIMPLE, + /** + * Reference with arbitrary outer class and generic parameters, like {@code Outer.Inner}, or {@code Outer.Inner} + */ + INNER_GENERIC, + // Derived + /** + * Wildcard type, like {@code ? extends X} + */ + EXTENDS, + /** + * Wildcard type, like {@code ? super X} + */ + SUPER, + /** + * Array type (component could be primitive, reference, or another array) + */ + ARRAY, + /** + * Ellipsis type (vararg parameter of a method, or a record component) + */ + ELLIPSIS, + BOOLEAN("boolean"), + BYTE("byte"), + CHAR("char"), + DOUBLE("double"), + FLOAT("float"), + INT("int"), + LONG("long"), + SHORT("short"), + VOID("void"), + OBJECT("Object"), + STRING("String"), + WILDCARD("?"); + + private final @Nullable String text; + + TypeKind() { + this(null); + } + + TypeKind(@Nullable String text) { + this.text = text; + } + + boolean isReference() { + return ordinal() >= REF.ordinal() && ordinal() <= INNER_GENERIC.ordinal(); + } + + boolean isDerived() { + return ordinal() >= EXTENDS.ordinal() && ordinal() <= ELLIPSIS.ordinal(); + } + } + + private final @Nonnull TypeKind kind; + private TypeAnnotationContainer myTypeAnnotations; + /** - * Array type (component could be primitive, reference, or another array) + * Derived type: either array or wildcard */ - ARRAY, + public static final class DerivedTypeInfo extends TypeInfo { + private final TypeInfo myChild; + + public DerivedTypeInfo(@Nonnull TypeKind kind, @Nonnull TypeInfo child) { + super(kind); + assert kind.isDerived(); + myChild = child; + } + + public TypeInfo child() { + return myChild; + } + + @Override + public TypeInfo withEllipsis() { + switch (getKind()) { + case ELLIPSIS: + return this; + case ARRAY: + return new DerivedTypeInfo(TypeKind.ELLIPSIS, myChild); + default: + throw new UnsupportedOperationException(); + } + } + + @Override + String text(boolean isShort) { + switch (getKind()) { + case EXTENDS: + return "? extends " + myChild.text(isShort); + case SUPER: + return "? super " + myChild.text(isShort); + case ARRAY: + return myChild.text(isShort) + "[]"; + case ELLIPSIS: + return myChild.text(isShort) + "..."; + default: + throw new IllegalStateException(); + } + } + } + /** - * Ellipsis type (vararg parameter of a method, or a record component) + * Reference type; may be inner type or generic type */ - ELLIPSIS, - BOOLEAN("boolean"), - BYTE("byte"), - CHAR("char"), - DOUBLE("double"), - FLOAT("float"), - INT("int"), - LONG("long"), - SHORT("short"), - VOID("void"), - OBJECT("Object"), - STRING("String"), - WILDCARD("?"); - - private final @Nullable String text; - - TypeKind() { - this(null); - } + public static final class RefTypeInfo extends TypeInfo { + private final String myName; + private final @Nullable RefTypeInfo myOuter; + private final @Nonnull TypeInfo[] myComponents; - TypeKind(@Nullable String text) { - this.text = text; - } + public RefTypeInfo(@Nonnull String name) { + this(name, null, EMPTY_ARRAY); + } - boolean isReference() { - return ordinal() >= REF.ordinal() && ordinal() <= INNER_GENERIC.ordinal(); - } + public RefTypeInfo(@Nonnull String name, @Nullable RefTypeInfo outer) { + this(name, outer, EMPTY_ARRAY); + } - boolean isDerived() { - return ordinal() >= EXTENDS.ordinal() && ordinal() <= ELLIPSIS.ordinal(); - } - } + public RefTypeInfo(@Nonnull String name, @Nullable RefTypeInfo outer, @Nonnull TypeInfo[] components) { + super( + outer != null + ? ( + components.length == 0 + ? (outer.getKind() == TypeKind.REF ? TypeKind.INNER_SIMPLE : TypeKind.INNER) + : TypeKind.INNER_GENERIC + ) + : (components.length == 0 ? TEXT_TO_KIND.getOrDefault(name, TypeKind.REF) : TypeKind.GENERIC)); + myName = name; + myComponents = components; + myOuter = outer; + } - private final @Nonnull TypeKind kind; - private TypeAnnotationContainer myTypeAnnotations; + @Override + public String text(boolean isShort) { + if (isShort) { + return StringUtil.getShortName(myName); + } + if (myComponents.length == 0) { + return myOuter != null ? myOuter.text(isShort) + "." + myName : myName; + } + StringBuilder sb = new StringBuilder(); + if (myOuter != null) { + sb.append(myOuter.text(isShort)); + sb.append("."); + } + sb.append(myName); + sb.append("<"); + for (int i = 0; i < myComponents.length; i++) { + if (i > 0) { + sb.append(","); + } + sb.append(myComponents[i].text()); + } + sb.append(">"); + return sb.toString(); + } - /** - * Derived type: either array or wildcard - */ - public static final class DerivedTypeInfo extends TypeInfo { - private final TypeInfo myChild; + @Override + public int innerDepth() { + return myOuter != null ? myOuter.innerDepth() + 1 : 0; + } - public DerivedTypeInfo(@Nonnull TypeKind kind, @Nonnull TypeInfo child) { - super(kind); - assert kind.isDerived(); - myChild = child; - } + public @Nonnull RefTypeInfo withComponents(@Nonnull List components) { + return new RefTypeInfo(myName, myOuter, components.toArray(EMPTY_ARRAY)); + } - public TypeInfo child() { - return myChild; - } + public @Nonnull RefTypeInfo withOuter(@Nullable RefTypeInfo outer) { + if (myOuter != null) { + return new RefTypeInfo(myName, myOuter.withOuter(outer), myComponents); + } + return new RefTypeInfo(myName, outer, myComponents); + } - @Override - public TypeInfo withEllipsis() { - switch (getKind()) { - case ELLIPSIS: - return this; - case ARRAY: - return new DerivedTypeInfo(TypeKind.ELLIPSIS, myChild); - default: - throw new UnsupportedOperationException(); - } + /** + * @param index index of a generic component, non-negative + * @return corresponding component; null if there are too few components, or the type is not generic + */ + public @Nullable TypeInfo genericComponent(int index) { + return index >= myComponents.length ? null : myComponents[index]; + } + + /** + * @return outer type; null if this type is not an inner type + */ + public @Nullable TypeInfo outerType() { + return myOuter; + } } - @Override - String text(boolean isShort) { - switch (getKind()) { - case EXTENDS: - return "? extends " + myChild.text(isShort); - case SUPER: - return "? super " + myChild.text(isShort); - case ARRAY: - return myChild.text(isShort) + "[]"; - case ELLIPSIS: - return myChild.text(isShort) + "..."; - default: - throw new IllegalStateException(); - } + /** + * Immediate type; fully described by its kind (primitive, void, non-parameterized wildcard) + */ + public static final class SimpleTypeInfo extends TypeInfo { + public static final SimpleTypeInfo NULL = new SimpleTypeInfo(TypeKind.NULL); + + public SimpleTypeInfo(@Nonnull TypeKind kind) { + super(kind); + if (kind.isDerived() || kind.isReference()) { + throw new IllegalArgumentException(kind.toString()); + } + } } - } - - /** - * Reference type; may be inner type or generic type - */ - public static final class RefTypeInfo extends TypeInfo { - private final String myName; - private final @Nullable RefTypeInfo myOuter; - private final @Nonnull TypeInfo[] myComponents; - - public RefTypeInfo(@Nonnull String name) { - this(name, null, EMPTY_ARRAY); + + private TypeInfo(@Nonnull TypeKind kind) { + this.kind = kind; } - public RefTypeInfo(@Nonnull String name, @Nullable RefTypeInfo outer) { - this(name, outer, EMPTY_ARRAY); + String text(boolean isShort) { + return isShort && kind.text == null ? "" : kind.text; } - public RefTypeInfo(@Nonnull String name, @Nullable RefTypeInfo outer, @Nonnull TypeInfo[] components) { - super(outer != null ? - (components.length == 0 ? - (outer.getKind() == TypeKind.REF ? TypeKind.INNER_SIMPLE : TypeKind.INNER) : TypeKind.INNER_GENERIC) : - (components.length == 0 ? TEXT_TO_KIND.getOrDefault(name, TypeKind.REF) : TypeKind.GENERIC)); - myName = name; - myComponents = components; - myOuter = outer; + /** + * @return type text (without annotations); null for {@link TypeKind#NULL} type + */ + public final String text() { + return text(false); } - @Override - public String text(boolean isShort) { - if (isShort) { - return StringUtil.getShortName(myName); - } - if (myComponents.length == 0) { - return myOuter != null ? myOuter.text(isShort) + "." + myName : myName; - } - StringBuilder sb = new StringBuilder(); - if (myOuter != null) { - sb.append(myOuter.text(isShort)); - sb.append("."); - } - sb.append(myName); - sb.append("<"); - for (int i = 0; i < myComponents.length; i++) { - if (i > 0) sb.append(","); - sb.append(myComponents[i].text()); - } - sb.append(">"); - return sb.toString(); + /** + * @return type kind + */ + public final @Nonnull TypeKind getKind() { + return kind; } - @Override + /** + * @return depth of the inner type (how many enclosing types it has) + */ public int innerDepth() { - return myOuter != null ? myOuter.innerDepth() + 1 : 0; + return 0; } - public @Nonnull RefTypeInfo withComponents(@Nonnull List components) { - return new RefTypeInfo(myName, myOuter, components.toArray(EMPTY_ARRAY)); + /** + * @return true if this type is a vararg type + */ + public boolean isEllipsis() { + return kind == TypeKind.ELLIPSIS; } - public @Nonnull RefTypeInfo withOuter(@Nullable RefTypeInfo outer) { - if (myOuter != null) { - return new RefTypeInfo(myName, myOuter.withOuter(outer), myComponents); - } - return new RefTypeInfo(myName, outer, myComponents); + /** + * @return this array type replacing the latest component with an ellipsis + * @throws UnsupportedOperationException if this type is not an array type + */ + public TypeInfo withEllipsis() { + throw new UnsupportedOperationException(); } /** - * @param index index of a generic component, non-negative - * @return corresponding component; null if there are too few components, or the type is not generic + * @param typeAnnotations set type annotations. Could be called only once. */ - public @Nullable TypeInfo genericComponent(int index) { - return index >= myComponents.length ? null : myComponents[index]; + public void setTypeAnnotations(@Nonnull TypeAnnotationContainer typeAnnotations) { + if (this == SimpleTypeInfo.NULL) { + return; + } + if (myTypeAnnotations != null) { + throw new IllegalStateException(); + } + myTypeAnnotations = typeAnnotations; } /** - * @return outer type; null if this type is not an inner type + * @return type annotations associated with this type. */ - public @Nullable TypeInfo outerType() { - return myOuter; - } - } - - /** - * Immediate type; fully described by its kind (primitive, void, non-parameterized wildcard) - */ - public static final class SimpleTypeInfo extends TypeInfo { - public static final SimpleTypeInfo NULL = new SimpleTypeInfo(TypeKind.NULL); - - public SimpleTypeInfo(@Nonnull TypeKind kind) { - super(kind); - if (kind.isDerived() || kind.isReference()) { - throw new IllegalArgumentException(kind.toString()); - } + public @Nonnull TypeAnnotationContainer getTypeAnnotations() { + return myTypeAnnotations == null ? TypeAnnotationContainer.EMPTY : myTypeAnnotations; } - } - - private TypeInfo(@Nonnull TypeKind kind) { - this.kind = kind; - } - - String text(boolean isShort) { - return isShort && kind.text == null ? "" : kind.text; - } - - /** - * @return type text (without annotations); null for {@link TypeKind#NULL} type - */ - public final String text() { - return text(false); - } - - /** - * @return type kind - */ - public final @Nonnull TypeKind getKind() { - return kind; - } - - /** - * @return depth of the inner type (how many enclosing types it has) - */ - public int innerDepth() { - return 0; - } - - /** - * @return true if this type is a vararg type - */ - public boolean isEllipsis() { - return kind == TypeKind.ELLIPSIS; - } - - /** - * @return this array type replacing the latest component with an ellipsis - * @throws UnsupportedOperationException if this type is not an array type - */ - public TypeInfo withEllipsis() { - throw new UnsupportedOperationException(); - } - - /** - * @param typeAnnotations set type annotations. Could be called only once. - */ - public void setTypeAnnotations(@Nonnull TypeAnnotationContainer typeAnnotations) { - if (this == SimpleTypeInfo.NULL) return; - if (myTypeAnnotations != null) { - throw new IllegalStateException(); - } - myTypeAnnotations = typeAnnotations; - } - - /** - * @return type annotations associated with this type. - */ - public @Nonnull TypeAnnotationContainer getTypeAnnotations() { - return myTypeAnnotations == null ? TypeAnnotationContainer.EMPTY : myTypeAnnotations; - } - - /** - * @return short type representation (unqualified name without generic parameters) - */ - @Nonnull - public String getShortTypeText() { - return text(true); - } - - @Override - public String toString() { - String text = text(); - return text != null ? text : "null"; - } - - /* factories and serialization */ - - /** - * @return return type of the constructor (null-type) - */ - @Nonnull - public static TypeInfo createConstructorType() { - return TypeInfo.SimpleTypeInfo.NULL; - } - - /** - * @return type created from {@link LighterAST} - */ - @Nonnull - public static TypeInfo create(@Nonnull LighterAST tree, @Nonnull LighterASTNode element, StubElement parentStub) { - int arrayCount = 0; - - LighterASTNode typeElement = null; - - if (element.getTokenType() == JavaElementType.ENUM_CONSTANT) { - return ((PsiClassStubImpl)parentStub).getQualifiedNameTypeInfo(); - } - for (final LighterASTNode child : tree.getChildren(element)) { - IElementType type = child.getTokenType(); - if (type == JavaElementType.TYPE) { - typeElement = child; - } - else if (type == JavaTokenType.LBRACKET) { - arrayCount++; // C-style array - } + + /** + * @return short type representation (unqualified name without generic parameters) + */ + @Nonnull + public String getShortTypeText() { + return text(true); } - if (typeElement == null && element.getTokenType() == JavaElementType.FIELD) { - LighterASTNode parent = tree.getParent(element); - assert parent != null : element; - List fields = LightTreeUtil.getChildrenOfType(tree, parent, JavaElementType.FIELD); - int idx = fields.indexOf(element); - for (int i = idx - 1; i >= 0 && typeElement == null; i--) { // int i, j - typeElement = LightTreeUtil.firstChildOfType(tree, fields.get(i), JavaElementType.TYPE); - } + + @Override + public String toString() { + String text = text(); + return text != null ? text : "null"; } - assert typeElement != null : element + " in " + parentStub; + /* factories and serialization */ - TypeInfo typeInfo = fromTypeElement(tree, typeElement); - for (int i = 0; i < arrayCount; i++) { - typeInfo = typeInfo.arrayOf(); - } - byte[] prefix = new byte[arrayCount]; - Arrays.fill(prefix, TypeAnnotationContainer.Collector.ARRAY_ELEMENT); - TypeAnnotationContainer.Collector collector = new TypeAnnotationContainer.Collector(typeInfo); - collectAnnotations(typeInfo, collector, tree, typeElement, prefix); - collector.install(); - return typeInfo; - } - - private static void collectAnnotations(@Nonnull TypeInfo info, - @Nonnull TypeAnnotationContainer.Collector collector, - @Nonnull LighterAST tree, - @Nonnull LighterASTNode element, - @Nonnull byte[] prefix) { - // TODO: support bounds, generics and enclosing types - int arrayCount = 0; - List children = tree.getChildren(element); - for (LighterASTNode child : children) { - IElementType tokenType = child.getTokenType(); - if (tokenType == JavaTokenType.LBRACKET) { - arrayCount++; - } - } - int nestingLevel = 0; - boolean bound = false; - for (LighterASTNode child : children) { - IElementType tokenType = child.getTokenType(); - if (tokenType == JavaTokenType.EXTENDS_KEYWORD || tokenType == JavaTokenType.SUPER_KEYWORD) { - bound = true; - } - if (tokenType == JavaElementType.TYPE && info instanceof DerivedTypeInfo) { - byte[] newPrefix; - if (bound) { - newPrefix = Arrays.copyOf(prefix, prefix.length + 1); - newPrefix[prefix.length] = TypeAnnotationContainer.Collector.WILDCARD_BOUND; - } - else { - newPrefix = Arrays.copyOf(prefix, prefix.length + arrayCount); - Arrays.fill(newPrefix, prefix.length, newPrefix.length, TypeAnnotationContainer.Collector.ARRAY_ELEMENT); - } - collectAnnotations(((DerivedTypeInfo)info).child(), collector, tree, child, newPrefix); - } - else if (tokenType == JavaTokenType.LBRACKET) { - nestingLevel++; - } - else if (tokenType == JavaElementType.ANNOTATION) { - String anno = LightTreeUtil.toFilteredString(tree, child, null); - byte[] typePath = Arrays.copyOf(prefix, prefix.length + nestingLevel); - Arrays.fill(typePath, prefix.length, typePath.length, TypeAnnotationContainer.Collector.ARRAY_ELEMENT); - collector.add(typePath, anno); - } - } - } - - private static final TokenSet PRIMITIVE_TYPES = - TokenSet.create(JavaTokenType.INT_KEYWORD, JavaTokenType.CHAR_KEYWORD, JavaTokenType.LONG_KEYWORD, - JavaTokenType.DOUBLE_KEYWORD, JavaTokenType.FLOAT_KEYWORD, JavaTokenType.SHORT_KEYWORD, - JavaTokenType.BOOLEAN_KEYWORD, JavaTokenType.BYTE_KEYWORD, JavaTokenType.VOID_KEYWORD); - - @Nonnull - private static TypeInfo fromTypeElement(@Nonnull LighterAST tree, - @Nonnull LighterASTNode typeElement) { - TypeInfo info = null; - TypeKind derivedKind = null; - for (LighterASTNode child : tree.getChildren(typeElement)) { - IElementType tokenType = child.getTokenType(); - if (PRIMITIVE_TYPES.contains(tokenType)) { - info = new SimpleTypeInfo(TEXT_TO_KIND.get(((LighterASTTokenNode)child).getText().toString())); - } - else if (tokenType == JavaElementType.TYPE) { - info = fromTypeElement(tree, child); - } - else if (tokenType == JavaElementType.DUMMY_ELEMENT) { - info = fromString(LightTreeUtil.toFilteredString(tree, child, null)); - } - else if (tokenType == JavaElementType.JAVA_CODE_REFERENCE) { - info = fromCodeReference(tree, child); - } - else if (tokenType == JavaTokenType.EXTENDS_KEYWORD) { - derivedKind = TypeKind.EXTENDS; - } - else if (tokenType == JavaTokenType.SUPER_KEYWORD) { - derivedKind = TypeKind.SUPER; - } - else if (tokenType == JavaTokenType.QUEST) { - info = new SimpleTypeInfo(TypeKind.WILDCARD); // may be overwritten - } - if (tokenType == JavaTokenType.LBRACKET) { - info = Objects.requireNonNull(info).arrayOf(); - } - else if (tokenType == JavaTokenType.ELLIPSIS) { - info = Objects.requireNonNull(info).arrayOf().withEllipsis(); - } - } - if (info == null) { - throw new IllegalArgumentException("Malformed type: " + LightTreeUtil.toFilteredString(tree, typeElement, null)); + /** + * @return return type of the constructor (null-type) + */ + @Nonnull + public static TypeInfo createConstructorType() { + return TypeInfo.SimpleTypeInfo.NULL; } - if (derivedKind != null) { - info = new DerivedTypeInfo(derivedKind, info); + + /** + * @return type created from {@link LighterAST} + */ + @Nonnull + public static TypeInfo create(@Nonnull LighterAST tree, @Nonnull LighterASTNode element, StubElement parentStub) { + int arrayCount = 0; + + LighterASTNode typeElement = null; + + if (element.getTokenType() == JavaElementType.ENUM_CONSTANT) { + return ((PsiClassStubImpl)parentStub).getQualifiedNameTypeInfo(); + } + for (final LighterASTNode child : tree.getChildren(element)) { + IElementType type = child.getTokenType(); + if (type == JavaElementType.TYPE) { + typeElement = child; + } + else if (type == JavaTokenType.LBRACKET) { + arrayCount++; // C-style array + } + } + if (typeElement == null && element.getTokenType() == JavaElementType.FIELD) { + LighterASTNode parent = tree.getParent(element); + assert parent != null : element; + List fields = LightTreeUtil.getChildrenOfType(tree, parent, JavaElementType.FIELD); + int idx = fields.indexOf(element); + for (int i = idx - 1; i >= 0 && typeElement == null; i--) { // int i, j + typeElement = LightTreeUtil.firstChildOfType(tree, fields.get(i), JavaElementType.TYPE); + } + } + + assert typeElement != null : element + " in " + parentStub; + + TypeInfo typeInfo = fromTypeElement(tree, typeElement); + for (int i = 0; i < arrayCount; i++) { + typeInfo = typeInfo.arrayOf(); + } + byte[] prefix = new byte[arrayCount]; + Arrays.fill(prefix, TypeAnnotationContainer.Collector.ARRAY_ELEMENT); + TypeAnnotationContainer.Collector collector = new TypeAnnotationContainer.Collector(typeInfo); + collectAnnotations(typeInfo, collector, tree, typeElement, prefix); + collector.install(); + return typeInfo; + } + + private static void collectAnnotations( + @Nonnull TypeInfo info, + @Nonnull TypeAnnotationContainer.Collector collector, + @Nonnull LighterAST tree, + @Nonnull LighterASTNode element, + @Nonnull byte[] prefix + ) { + // TODO: support bounds, generics and enclosing types + int arrayCount = 0; + List children = tree.getChildren(element); + for (LighterASTNode child : children) { + IElementType tokenType = child.getTokenType(); + if (tokenType == JavaTokenType.LBRACKET) { + arrayCount++; + } + } + int nestingLevel = 0; + boolean bound = false; + for (LighterASTNode child : children) { + IElementType tokenType = child.getTokenType(); + if (tokenType == JavaTokenType.EXTENDS_KEYWORD || tokenType == JavaTokenType.SUPER_KEYWORD) { + bound = true; + } + if (tokenType == JavaElementType.TYPE && info instanceof DerivedTypeInfo) { + byte[] newPrefix; + if (bound) { + newPrefix = Arrays.copyOf(prefix, prefix.length + 1); + newPrefix[prefix.length] = TypeAnnotationContainer.Collector.WILDCARD_BOUND; + } + else { + newPrefix = Arrays.copyOf(prefix, prefix.length + arrayCount); + Arrays.fill(newPrefix, prefix.length, newPrefix.length, TypeAnnotationContainer.Collector.ARRAY_ELEMENT); + } + collectAnnotations(((DerivedTypeInfo)info).child(), collector, tree, child, newPrefix); + } + else if (tokenType == JavaTokenType.LBRACKET) { + nestingLevel++; + } + else if (tokenType == JavaElementType.ANNOTATION) { + String anno = LightTreeUtil.toFilteredString(tree, child, null); + byte[] typePath = Arrays.copyOf(prefix, prefix.length + nestingLevel); + Arrays.fill(typePath, prefix.length, typePath.length, TypeAnnotationContainer.Collector.ARRAY_ELEMENT); + collector.add(typePath, anno); + } + } } - return info; - } - - private static RefTypeInfo fromCodeReference(@Nonnull LighterAST tree, @Nonnull LighterASTNode ref) { - RefTypeInfo info = null; - for (LighterASTNode child : tree.getChildren(ref)) { - IElementType tokenType = child.getTokenType(); - if (tokenType == JavaElementType.JAVA_CODE_REFERENCE) { - info = fromCodeReference(tree, child); - } - else if (tokenType == JavaTokenType.IDENTIFIER) { - String text = ((LighterASTTokenNode)child).getText().toString(); - info = new RefTypeInfo(text, info); - } - else if (tokenType == JavaElementType.REFERENCE_PARAMETER_LIST) { + + private static final TokenSet PRIMITIVE_TYPES = TokenSet.create( + JavaTokenType.INT_KEYWORD, + JavaTokenType.CHAR_KEYWORD, + JavaTokenType.LONG_KEYWORD, + JavaTokenType.DOUBLE_KEYWORD, + JavaTokenType.FLOAT_KEYWORD, + JavaTokenType.SHORT_KEYWORD, + JavaTokenType.BOOLEAN_KEYWORD, + JavaTokenType.BYTE_KEYWORD, + JavaTokenType.VOID_KEYWORD + ); + + @Nonnull + private static TypeInfo fromTypeElement(@Nonnull LighterAST tree, @Nonnull LighterASTNode typeElement) { + TypeInfo info = null; + TypeKind derivedKind = null; + for (LighterASTNode child : tree.getChildren(typeElement)) { + IElementType tokenType = child.getTokenType(); + if (PRIMITIVE_TYPES.contains(tokenType)) { + info = new SimpleTypeInfo(TEXT_TO_KIND.get(((LighterASTTokenNode)child).getText().toString())); + } + else if (tokenType == JavaElementType.TYPE) { + info = fromTypeElement(tree, child); + } + else if (tokenType == JavaElementType.DUMMY_ELEMENT) { + info = fromString(LightTreeUtil.toFilteredString(tree, child, null)); + } + else if (tokenType == JavaElementType.JAVA_CODE_REFERENCE) { + info = fromCodeReference(tree, child); + } + else if (tokenType == JavaTokenType.EXTENDS_KEYWORD) { + derivedKind = TypeKind.EXTENDS; + } + else if (tokenType == JavaTokenType.SUPER_KEYWORD) { + derivedKind = TypeKind.SUPER; + } + else if (tokenType == JavaTokenType.QUEST) { + info = new SimpleTypeInfo(TypeKind.WILDCARD); // may be overwritten + } + if (tokenType == JavaTokenType.LBRACKET) { + info = Objects.requireNonNull(info).arrayOf(); + } + else if (tokenType == JavaTokenType.ELLIPSIS) { + info = Objects.requireNonNull(info).arrayOf().withEllipsis(); + } + } if (info == null) { - throw new IllegalArgumentException("Malformed type: " + LightTreeUtil.toFilteredString(tree, ref, null)); + throw new IllegalArgumentException("Malformed type: " + LightTreeUtil.toFilteredString(tree, typeElement, null)); } - List components = new ArrayList<>(); - for (LighterASTNode component : tree.getChildren(child)) { - if (component.getTokenType() == JavaElementType.TYPE) { - components.add(fromTypeElement(tree, component)); - } + if (derivedKind != null) { + info = new DerivedTypeInfo(derivedKind, info); } - info = info.withComponents(components); - } - } - return info; - } - - public @Nonnull DerivedTypeInfo arrayOf() { - return new DerivedTypeInfo(TypeKind.ARRAY, this); - } - - /** - * @param text type text - * @param ellipsis if true, then the last array component will be replaced with an ellipsis - * @return the type created from the text - * @deprecated avoid using it, as this method cannot correctly process inner types and actually requires parsing. - * Instead, create the type structure explicitly, using the corresponding constructors of {@link SimpleTypeInfo}, {@link RefTypeInfo} and - * {@link DerivedTypeInfo}. - */ - @Nonnull - @Deprecated - public static TypeInfo fromString(@Nullable String text, boolean ellipsis) { - TypeInfo typeInfo = fromString(text); - return ellipsis ? typeInfo.withEllipsis() : typeInfo; - } - - @Nonnull - public static TypeInfo fromString(@Nullable String text) { - if (text == null) return TypeInfo.SimpleTypeInfo.NULL; - TypeKind kind = TEXT_TO_KIND.get(text); - if (kind != null) { - return kind.isReference() ? new RefTypeInfo(text) : new SimpleTypeInfo(kind); - } - if (text.startsWith("? extends ")) { - return new DerivedTypeInfo(TypeKind.EXTENDS, fromString(text.substring("? extends ".length()))); + return info; } - if (text.startsWith("? super ")) { - return new DerivedTypeInfo(TypeKind.SUPER, fromString(text.substring("? super ".length()))); - } - if (text.endsWith("[]")) { - return fromString(text.substring(0, text.length() - 2)).arrayOf(); + + private static RefTypeInfo fromCodeReference(@Nonnull LighterAST tree, @Nonnull LighterASTNode ref) { + RefTypeInfo info = null; + for (LighterASTNode child : tree.getChildren(ref)) { + IElementType tokenType = child.getTokenType(); + if (tokenType == JavaElementType.JAVA_CODE_REFERENCE) { + info = fromCodeReference(tree, child); + } + else if (tokenType == JavaTokenType.IDENTIFIER) { + String text = ((LighterASTTokenNode)child).getText().toString(); + info = new RefTypeInfo(text, info); + } + else if (tokenType == JavaElementType.REFERENCE_PARAMETER_LIST) { + if (info == null) { + throw new IllegalArgumentException("Malformed type: " + LightTreeUtil.toFilteredString(tree, ref, null)); + } + List components = new ArrayList<>(); + for (LighterASTNode component : tree.getChildren(child)) { + if (component.getTokenType() == JavaElementType.TYPE) { + components.add(fromTypeElement(tree, component)); + } + } + info = info.withComponents(components); + } + } + return info; } - if (text.endsWith("...")) { - return new DerivedTypeInfo(TypeKind.ELLIPSIS, fromString(text.substring(0, text.length() - 3))); + + public @Nonnull DerivedTypeInfo arrayOf() { + return new DerivedTypeInfo(TypeKind.ARRAY, this); } - if (text.endsWith(">")) { - int depth = 1; - int end = text.length() - 1; - List components = new ArrayList<>(); - for (int pos = end - 1; pos > 0; pos--) { - char ch = text.charAt(pos); - if (ch == '>') depth++; - else if (ch == ',' && depth == 1) { - String component = text.substring(pos + 1, end); - end = pos; - components.add(fromString(component)); - } - else if (ch == '<') { - depth--; - if (depth == 0) { - String component = text.substring(pos + 1, end); - components.add(fromString(component)); - Collections.reverse(components); - int prevGeneric = text.lastIndexOf('>', pos); - RefTypeInfo outer; - String name; - if (prevGeneric > 0) { - if (text.charAt(prevGeneric + 1) != '.') { - throw new IllegalArgumentException("Malformed type: " + text); - } - outer = (RefTypeInfo)fromString(text.substring(0, prevGeneric + 1)); - name = text.substring(prevGeneric + 2, pos); - } - else { - name = text.substring(0, pos); - outer = null; - } - return new RefTypeInfo(name, outer, components.toArray(EMPTY_ARRAY)); - } - } - } - throw new IllegalArgumentException("Malformed type: " + text); + + /** + * @param text type text + * @param ellipsis if true, then the last array component will be replaced with an ellipsis + * @return the type created from the text + * @deprecated avoid using it, as this method cannot correctly process inner types and actually requires parsing. + * Instead, create the type structure explicitly, using the corresponding constructors of {@link SimpleTypeInfo}, {@link RefTypeInfo} and + * {@link DerivedTypeInfo}. + */ + @Nonnull + @Deprecated + public static TypeInfo fromString(@Nullable String text, boolean ellipsis) { + TypeInfo typeInfo = fromString(text); + return ellipsis ? typeInfo.withEllipsis() : typeInfo; } - return new RefTypeInfo(text); - } - - @Nonnull - public static TypeInfo readTYPE(@Nonnull StubInputStream record) throws IOException { - int flags = record.readByte() & 0xFF; - boolean hasTypeAnnotations = isSet(flags, HAS_TYPE_ANNOTATIONS); - int kindOrdinal = clear(flags, HAS_TYPE_ANNOTATIONS); - if (kindOrdinal >= ALL_KINDS.length) { - throw new IOException("Unexpected TypeKind: " + flags); + + @Nonnull + public static TypeInfo fromString(@Nullable String text) { + if (text == null) { + return TypeInfo.SimpleTypeInfo.NULL; + } + TypeKind kind = TEXT_TO_KIND.get(text); + if (kind != null) { + return kind.isReference() ? new RefTypeInfo(text) : new SimpleTypeInfo(kind); + } + if (text.startsWith("? extends ")) { + return new DerivedTypeInfo(TypeKind.EXTENDS, fromString(text.substring("? extends ".length()))); + } + if (text.startsWith("? super ")) { + return new DerivedTypeInfo(TypeKind.SUPER, fromString(text.substring("? super ".length()))); + } + if (text.endsWith("[]")) { + return fromString(text.substring(0, text.length() - 2)).arrayOf(); + } + if (text.endsWith("...")) { + return new DerivedTypeInfo(TypeKind.ELLIPSIS, fromString(text.substring(0, text.length() - 3))); + } + if (text.endsWith(">")) { + int depth = 1; + int end = text.length() - 1; + List components = new ArrayList<>(); + for (int pos = end - 1; pos > 0; pos--) { + char ch = text.charAt(pos); + if (ch == '>') { + depth++; + } + else if (ch == ',' && depth == 1) { + String component = text.substring(pos + 1, end); + end = pos; + components.add(fromString(component)); + } + else if (ch == '<') { + depth--; + if (depth == 0) { + String component = text.substring(pos + 1, end); + components.add(fromString(component)); + Collections.reverse(components); + int prevGeneric = text.lastIndexOf('>', pos); + RefTypeInfo outer; + String name; + if (prevGeneric > 0) { + if (text.charAt(prevGeneric + 1) != '.') { + throw new IllegalArgumentException("Malformed type: " + text); + } + outer = (RefTypeInfo)fromString(text.substring(0, prevGeneric + 1)); + name = text.substring(prevGeneric + 2, pos); + } + else { + name = text.substring(0, pos); + outer = null; + } + return new RefTypeInfo(name, outer, components.toArray(EMPTY_ARRAY)); + } + } + } + throw new IllegalArgumentException("Malformed type: " + text); + } + return new RefTypeInfo(text); } - TypeKind kind = ALL_KINDS[kindOrdinal]; - TypeInfo info; - RefTypeInfo outer = null; - switch (kind) { - case REF: - info = new RefTypeInfo(Objects.requireNonNull(record.readNameString())); - break; - case INNER_SIMPLE: - outer = new RefTypeInfo(Objects.requireNonNull(record.readNameString())); - info = new RefTypeInfo(Objects.requireNonNull(record.readNameString()), outer); - break; - case INNER: - outer = (RefTypeInfo)readTYPE(record); - info = new RefTypeInfo(Objects.requireNonNull(record.readNameString()), outer); - break; - case INNER_GENERIC: - outer = (RefTypeInfo)readTYPE(record); - case GENERIC: - String name = Objects.requireNonNull(record.readNameString()); - byte count = record.readByte(); - TypeInfo[] components = new TypeInfo[count]; - for (int i = 0; i < count; i++) { - components[i] = readTYPE(record); - } - info = new RefTypeInfo(name, outer, components); - break; - case EXTENDS: - case SUPER: - case ARRAY: - case ELLIPSIS: - info = new DerivedTypeInfo(kind, readTYPE(record)); - break; - default: - info = kind.isReference() ? new RefTypeInfo(Objects.requireNonNull(kind.text)) : new SimpleTypeInfo(kind); + + @Nonnull + public static TypeInfo readTYPE(@Nonnull StubInputStream record) throws IOException { + int flags = record.readByte() & 0xFF; + boolean hasTypeAnnotations = isSet(flags, HAS_TYPE_ANNOTATIONS); + int kindOrdinal = clear(flags, HAS_TYPE_ANNOTATIONS); + if (kindOrdinal >= ALL_KINDS.length) { + throw new IOException("Unexpected TypeKind: " + flags); + } + TypeKind kind = ALL_KINDS[kindOrdinal]; + TypeInfo info; + RefTypeInfo outer = null; + switch (kind) { + case REF: + info = new RefTypeInfo(Objects.requireNonNull(record.readNameString())); + break; + case INNER_SIMPLE: + outer = new RefTypeInfo(Objects.requireNonNull(record.readNameString())); + info = new RefTypeInfo(Objects.requireNonNull(record.readNameString()), outer); + break; + case INNER: + outer = (RefTypeInfo)readTYPE(record); + info = new RefTypeInfo(Objects.requireNonNull(record.readNameString()), outer); + break; + case INNER_GENERIC: + outer = (RefTypeInfo)readTYPE(record); + case GENERIC: + String name = Objects.requireNonNull(record.readNameString()); + byte count = record.readByte(); + TypeInfo[] components = new TypeInfo[count]; + for (int i = 0; i < count; i++) { + components[i] = readTYPE(record); + } + info = new RefTypeInfo(name, outer, components); + break; + case EXTENDS: + case SUPER: + case ARRAY: + case ELLIPSIS: + info = new DerivedTypeInfo(kind, readTYPE(record)); + break; + default: + info = kind.isReference() ? new RefTypeInfo(Objects.requireNonNull(kind.text)) : new SimpleTypeInfo(kind); + } + info.setTypeAnnotations(hasTypeAnnotations ? TypeAnnotationContainer.readTypeAnnotations(record) : TypeAnnotationContainer.EMPTY); + return info; } - info.setTypeAnnotations(hasTypeAnnotations ? TypeAnnotationContainer.readTypeAnnotations(record) : TypeAnnotationContainer.EMPTY); - return info; - } - public static void writeTYPE(@Nonnull StubOutputStream dataStream, @Nonnull TypeInfo typeInfo) throws IOException { - boolean hasTypeAnnotations = typeInfo.myTypeAnnotations != null && !typeInfo.myTypeAnnotations.isEmpty(); - dataStream.writeByte(typeInfo.kind.ordinal() | (hasTypeAnnotations ? HAS_TYPE_ANNOTATIONS : 0)); + public static void writeTYPE(@Nonnull StubOutputStream dataStream, @Nonnull TypeInfo typeInfo) throws IOException { + boolean hasTypeAnnotations = typeInfo.myTypeAnnotations != null && !typeInfo.myTypeAnnotations.isEmpty(); + dataStream.writeByte(typeInfo.kind.ordinal() | (hasTypeAnnotations ? HAS_TYPE_ANNOTATIONS : 0)); - if (typeInfo instanceof DerivedTypeInfo) { - writeTYPE(dataStream, ((DerivedTypeInfo)typeInfo).myChild); + if (typeInfo instanceof DerivedTypeInfo) { + writeTYPE(dataStream, ((DerivedTypeInfo)typeInfo).myChild); + } + else if (typeInfo instanceof RefTypeInfo && typeInfo.kind.text == null) { + if (typeInfo.kind == TypeKind.INNER_SIMPLE) { + dataStream.writeName(Objects.requireNonNull(((RefTypeInfo)typeInfo).myOuter).myName); + } + if (typeInfo.kind == TypeKind.INNER || typeInfo.kind == TypeKind.INNER_GENERIC) { + writeTYPE(dataStream, Objects.requireNonNull(((RefTypeInfo)typeInfo).myOuter)); + } + dataStream.writeName(((RefTypeInfo)typeInfo).myName); + if (typeInfo.kind == TypeKind.INNER_GENERIC || typeInfo.kind == TypeKind.GENERIC) { + TypeInfo[] components = ((RefTypeInfo)typeInfo).myComponents; + dataStream.writeByte(components.length); + for (TypeInfo component : components) { + writeTYPE(dataStream, component); + } + } + } + if (hasTypeAnnotations) { + TypeAnnotationContainer.writeTypeAnnotations(dataStream, typeInfo.myTypeAnnotations); + } } - else if (typeInfo instanceof RefTypeInfo && typeInfo.kind.text == null) { - if (typeInfo.kind == TypeKind.INNER_SIMPLE) { - dataStream.writeName(Objects.requireNonNull(((RefTypeInfo)typeInfo).myOuter).myName); - } - if (typeInfo.kind == TypeKind.INNER || typeInfo.kind == TypeKind.INNER_GENERIC) { - writeTYPE(dataStream, Objects.requireNonNull(((RefTypeInfo)typeInfo).myOuter)); - } - dataStream.writeName(((RefTypeInfo)typeInfo).myName); - if (typeInfo.kind == TypeKind.INNER_GENERIC || typeInfo.kind == TypeKind.GENERIC) { - TypeInfo[] components = ((RefTypeInfo)typeInfo).myComponents; - dataStream.writeByte(components.length); - for (TypeInfo component : components) { - writeTYPE(dataStream, component); - } - } + + /** + * @return type text without annotations + * @deprecated Use simply {@link TypeInfo#text()} + */ + @Nullable + @Deprecated + public static String createTypeText(@Nonnull TypeInfo typeInfo) { + return typeInfo.text(); } - if (hasTypeAnnotations) { - TypeAnnotationContainer.writeTypeAnnotations(dataStream, typeInfo.myTypeAnnotations); + + @Nonnull + public static String internFrequentType(@Nonnull String type) { + int frequentIndex = (type.length() < 32 && (ourTypeLengthMask & (1 << type.length())) != 0) ? ourFrequentTypeIndex.getInt(type) : 0; + return frequentIndex == 0 ? StringUtil.internEmptyString(type) : ourIndexFrequentType[frequentIndex]; } - } - - /** - * @return type text without annotations - * @deprecated Use simply {@link TypeInfo#text()} - */ - @Nullable - @Deprecated - public static String createTypeText(@Nonnull TypeInfo typeInfo) { - return typeInfo.text(); - } - - @Nonnull - public static String internFrequentType(@Nonnull String type) { - int frequentIndex = (type.length() < 32 && (ourTypeLengthMask & (1 << type.length())) != 0) ? ourFrequentTypeIndex.getInt(type) : 0; - return frequentIndex == 0 ? StringUtil.internEmptyString(type) : ourIndexFrequentType[frequentIndex]; - } } \ No newline at end of file diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/scope/util/PsiScopesUtil.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/scope/util/PsiScopesUtil.java index 49dedf3b25..adfb81683c 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/scope/util/PsiScopesUtil.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/scope/util/PsiScopesUtil.java @@ -40,482 +40,529 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.ArrayList; import java.util.List; public class PsiScopesUtil { - private static final Logger LOG = Logger.getInstance(PsiScopesUtil.class); - - private PsiScopesUtil() { - } - - public static boolean treeWalkUp(@Nonnull PsiScopeProcessor processor, - @Nonnull PsiElement entrance, - @Nullable PsiElement maxScope) { - return treeWalkUp(processor, entrance, maxScope, ResolveState.initial()); - } - - public static boolean treeWalkUp(@Nonnull final PsiScopeProcessor processor, - @Nonnull final PsiElement entrance, - @Nullable final PsiElement maxScope, - @Nonnull final ResolveState state) { - if (!entrance.isValid()) { - LOG.error(new PsiInvalidElementAccessException(entrance)); - } - PsiElement prevParent = entrance; - PsiElement scope = entrance; - - while (scope != null) { - ProgressIndicatorProvider.checkCanceled(); - if (scope instanceof PsiClass) { - processor.handleEvent(JavaScopeProcessorEvent.SET_CURRENT_FILE_CONTEXT, scope); - } - if (!scope.processDeclarations(processor, state, prevParent, entrance)) { - return false; // resolved - } - - if (scope instanceof PsiModifierListOwner && !(scope instanceof PsiParameter/* important for not loading tree! */)) { - PsiModifierList modifierList = ((PsiModifierListOwner) scope).getModifierList(); - if (modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC)) { - processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null); - } - } - if (scope == maxScope) { - break; - } - prevParent = scope; - scope = prevParent.getContext(); - processor.handleEvent(JavaScopeProcessorEvent.CHANGE_LEVEL, null); - } - - return true; - } - - public static boolean walkChildrenScopes(@Nonnull PsiElement thisElement, - @Nonnull PsiScopeProcessor processor, - @Nonnull ResolveState state, - PsiElement lastParent, - PsiElement place) { - PsiElement child = null; - if (lastParent != null && lastParent.getParent() == thisElement) { - child = lastParent.getPrevSibling(); - if (child == null) { - return true; // first element - } - } + private static final Logger LOG = Logger.getInstance(PsiScopesUtil.class); - if (child == null) { - child = thisElement.getLastChild(); + private PsiScopesUtil() { } - while (child != null) { - if (!child.processDeclarations(processor, state, null, place)) { - return false; - } - child = child.getPrevSibling(); + public static boolean treeWalkUp(@Nonnull PsiScopeProcessor processor, @Nonnull PsiElement entrance, @Nullable PsiElement maxScope) { + return treeWalkUp(processor, entrance, maxScope, ResolveState.initial()); } - return true; - } - - public static void processTypeDeclarations(PsiType type, PsiElement place, PsiScopeProcessor processor) { - if (type instanceof PsiArrayType) { - LanguageLevel languageLevel = PsiUtil.getLanguageLevel(place); - final PsiClass arrayClass = JavaPsiFacade.getElementFactory(place.getProject()).getArrayClass(languageLevel); - final PsiTypeParameter[] arrayTypeParameters = arrayClass.getTypeParameters(); - PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; - if (arrayTypeParameters.length > 0) { - substitutor = substitutor.put(arrayTypeParameters[0], ((PsiArrayType) type).getComponentType()); - } - arrayClass.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, substitutor), arrayClass, place); - } else if (type instanceof PsiIntersectionType) { - for (PsiType psiType : ((PsiIntersectionType) type).getConjuncts()) { - processTypeDeclarations(psiType, place, processor); - } - } else if (type instanceof PsiDisjunctionType) { - final PsiType lub = ((PsiDisjunctionType) type).getLeastUpperBound(); - processTypeDeclarations(lub, place, processor); - } else if (type instanceof PsiCapturedWildcardType) { - final PsiType classType = convertToTypeParameter((PsiCapturedWildcardType) type, place); - if (classType != null) { - processTypeDeclarations(classType, place, processor); - } - } else { - final JavaResolveResult result = PsiUtil.resolveGenericsClassInType(type); - final PsiClass clazz = (PsiClass) result.getElement(); - if (clazz != null) { - clazz.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, result.getSubstitutor()), clazz, place); - } - } - } - - public static boolean resolveAndWalk(@Nonnull PsiScopeProcessor processor, - @Nonnull PsiJavaCodeReferenceElement ref, - @Nullable PsiElement maxScope) { - return resolveAndWalk(processor, ref, maxScope, false); - } - - public static boolean resolveAndWalk(@Nonnull PsiScopeProcessor processor, - @Nonnull PsiJavaCodeReferenceElement ref, - @Nullable PsiElement maxScope, - boolean incompleteCode) { - final PsiElement qualifier = ref.getQualifier(); - final PsiElement classNameElement = ref.getReferenceNameElement(); - if (classNameElement == null) { - return true; - } - if (qualifier != null) { - // Composite expression - PsiElement target = null; - PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; - if (qualifier instanceof PsiExpression || qualifier instanceof PsiJavaCodeReferenceElement) { - PsiType type = null; - if (qualifier instanceof PsiExpression) { - type = ((PsiExpression) qualifier).getType(); - if (type != null) { - assert type.isValid() : type.getClass() + "; " + qualifier; - } - processTypeDeclarations(type, ref, processor); + public static boolean treeWalkUp( + @Nonnull final PsiScopeProcessor processor, + @Nonnull final PsiElement entrance, + @Nullable final PsiElement maxScope, + @Nonnull final ResolveState state + ) { + if (!entrance.isValid()) { + LOG.error(new PsiInvalidElementAccessException(entrance)); } + PsiElement prevParent = entrance; + PsiElement scope = entrance; - if (type == null && qualifier instanceof PsiJavaCodeReferenceElement) { - // In case of class qualifier - final PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement) qualifier; - final JavaResolveResult result = referenceElement.advancedResolve(incompleteCode); - target = result.getElement(); - substitutor = result.getSubstitutor(); - - if (target instanceof PsiVariable) { - type = substitutor.substitute(((PsiVariable) target).getType()); - if (type instanceof PsiClassType) { - final JavaResolveResult typeResult = ((PsiClassType) type).resolveGenerics(); - target = typeResult.getElement(); - substitutor = substitutor.putAll(typeResult.getSubstitutor()); - } else { - target = null; + while (scope != null) { + ProgressIndicatorProvider.checkCanceled(); + if (scope instanceof PsiClass) { + processor.handleEvent(JavaScopeProcessorEvent.SET_CURRENT_FILE_CONTEXT, scope); } - } else if (target instanceof PsiMethod) { - type = substitutor.substitute(((PsiMethod) target).getReturnType()); - if (type instanceof PsiClassType) { - final JavaResolveResult typeResult = ((PsiClassType) type).resolveGenerics(); - target = typeResult.getElement(); - substitutor = substitutor.putAll(typeResult.getSubstitutor()); - } else { - target = null; + if (!scope.processDeclarations(processor, state, prevParent, entrance)) { + return false; // resolved + } + + if (scope instanceof PsiModifierListOwner && !(scope instanceof PsiParameter/* important for not loading tree! */)) { + PsiModifierList modifierList = ((PsiModifierListOwner)scope).getModifierList(); + if (modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC)) { + processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null); + } } - final PsiType[] types = referenceElement.getTypeParameters(); - if (target instanceof PsiClass) { - substitutor = substitutor.putAll((PsiClass) target, types); + if (scope == maxScope) { + break; } - } else if (target instanceof PsiClass) { - processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null); - } + prevParent = scope; + scope = prevParent.getContext(); + processor.handleEvent(JavaScopeProcessorEvent.CHANGE_LEVEL, null); } - } - - if (target != null) { - return target.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, substitutor), target, ref); - } - } else { - // simple expression -> trying to resolve variable or method - return treeWalkUp(processor, ref, maxScope); - } - - return true; - } - public static void setupAndRunProcessor(@Nonnull MethodsProcessor processor, - @Nonnull PsiCallExpression call, - boolean dummyImplicitConstructor) - throws MethodProcessorSetupFailedException { - if (call instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) call; - final PsiJavaCodeReferenceElement ref = methodCall.getMethodExpression(); + return true; + } + public static boolean walkChildrenScopes( + @Nonnull PsiElement thisElement, + @Nonnull PsiScopeProcessor processor, + @Nonnull ResolveState state, + PsiElement lastParent, + PsiElement place + ) { + PsiElement child = null; + if (lastParent != null && lastParent.getParent() == thisElement) { + child = lastParent.getPrevSibling(); + if (child == null) { + return true; // first element + } + } - processor.setArgumentList(methodCall.getArgumentList()); - processor.obtainTypeArguments(methodCall); - if (!ref.isQualified() || ref.getReferenceNameElement() instanceof PsiKeyword) { - final PsiElement referenceNameElement = ref.getReferenceNameElement(); - if (referenceNameElement == null) { - return; + if (child == null) { + child = thisElement.getLastChild(); } - if (referenceNameElement instanceof PsiKeyword) { - final PsiKeyword keyword = (PsiKeyword) referenceNameElement; - if (keyword.getTokenType() == JavaTokenType.THIS_KEYWORD) { - final PsiClass aClass = JavaResolveUtil.getContextClass(methodCall); - if (aClass == null) { - throw new MethodProcessorSetupFailedException("Can't resolve class for this expression"); + while (child != null) { + if (!child.processDeclarations(processor, state, null, place)) { + return false; } + child = child.getPrevSibling(); + } - processor.setIsConstructor(true); - processor.setAccessClass(aClass); - aClass.processDeclarations(processor, ResolveState.initial(), null, call); + return true; + } - if (dummyImplicitConstructor) { - processDummyConstructor(processor, aClass); + public static void processTypeDeclarations(PsiType type, PsiElement place, PsiScopeProcessor processor) { + if (type instanceof PsiArrayType) { + LanguageLevel languageLevel = PsiUtil.getLanguageLevel(place); + final PsiClass arrayClass = JavaPsiFacade.getElementFactory(place.getProject()).getArrayClass(languageLevel); + final PsiTypeParameter[] arrayTypeParameters = arrayClass.getTypeParameters(); + PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; + if (arrayTypeParameters.length > 0) { + substitutor = substitutor.put(arrayTypeParameters[0], ((PsiArrayType)type).getComponentType()); } - } else if (keyword.getTokenType() == JavaTokenType.SUPER_KEYWORD) { - PsiClass aClass = JavaResolveUtil.getContextClass(methodCall); - if (aClass == null) { - throw new MethodProcessorSetupFailedException("Can't resolve class for super expression"); + arrayClass.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, substitutor), arrayClass, place); + } + else if (type instanceof PsiIntersectionType) { + for (PsiType psiType : ((PsiIntersectionType)type).getConjuncts()) { + processTypeDeclarations(psiType, place, processor); + } + } + else if (type instanceof PsiDisjunctionType) { + final PsiType lub = ((PsiDisjunctionType)type).getLeastUpperBound(); + processTypeDeclarations(lub, place, processor); + } + else if (type instanceof PsiCapturedWildcardType) { + final PsiType classType = convertToTypeParameter((PsiCapturedWildcardType)type, place); + if (classType != null) { + processTypeDeclarations(classType, place, processor); + } + } + else { + final JavaResolveResult result = PsiUtil.resolveGenericsClassInType(type); + final PsiClass clazz = (PsiClass)result.getElement(); + if (clazz != null) { + clazz.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, result.getSubstitutor()), clazz, place); } + } + } - final PsiClass superClass = aClass.getSuperClass(); - if (superClass != null) { - PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; - PsiClass runSuper = superClass; - List contextSubstitutors = new ArrayList<>(); - do { - if (runSuper != null) { - PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(runSuper, aClass, PsiSubstitutor.EMPTY); - contextSubstitutors.add(superSubstitutor); - } - if (aClass.hasModifierProperty(PsiModifier.STATIC)) { - break; - } - aClass = JavaResolveUtil.getContextClass(aClass); - if (aClass != null) { - runSuper = aClass.getSuperClass(); + public static boolean resolveAndWalk( + @Nonnull PsiScopeProcessor processor, + @Nonnull PsiJavaCodeReferenceElement ref, + @Nullable PsiElement maxScope + ) { + return resolveAndWalk(processor, ref, maxScope, false); + } + + public static boolean resolveAndWalk( + @Nonnull PsiScopeProcessor processor, + @Nonnull PsiJavaCodeReferenceElement ref, + @Nullable PsiElement maxScope, + boolean incompleteCode + ) { + final PsiElement qualifier = ref.getQualifier(); + final PsiElement classNameElement = ref.getReferenceNameElement(); + if (classNameElement == null) { + return true; + } + if (qualifier != null) { + // Composite expression + PsiElement target = null; + PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; + if (qualifier instanceof PsiExpression || qualifier instanceof PsiJavaCodeReferenceElement) { + PsiType type = null; + if (qualifier instanceof PsiExpression) { + type = ((PsiExpression)qualifier).getType(); + if (type != null) { + assert type.isValid() : type.getClass() + "; " + qualifier; + } + processTypeDeclarations(type, ref, processor); } - } - while (aClass != null); - //apply substitutors in 'outer classes down to inner classes' order because inner class subst take precedence - for (int i = contextSubstitutors.size() - 1; i >= 0; i--) { - PsiSubstitutor contextSubstitutor = contextSubstitutors.get(i); - substitutor = substitutor.putAll(contextSubstitutor); - } - - processor.setIsConstructor(true); - processor.setAccessClass(null); - final PsiMethod[] constructors = superClass.getConstructors(); - ResolveState state = ResolveState.initial().put(PsiSubstitutor.KEY, substitutor); - for (PsiMethod constructor : constructors) { - if (!processor.execute(constructor, state)) { - return; + + if (type == null && qualifier instanceof PsiJavaCodeReferenceElement) { + // In case of class qualifier + final PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)qualifier; + final JavaResolveResult result = referenceElement.advancedResolve(incompleteCode); + target = result.getElement(); + substitutor = result.getSubstitutor(); + + if (target instanceof PsiVariable) { + type = substitutor.substitute(((PsiVariable)target).getType()); + if (type instanceof PsiClassType) { + final JavaResolveResult typeResult = ((PsiClassType)type).resolveGenerics(); + target = typeResult.getElement(); + substitutor = substitutor.putAll(typeResult.getSubstitutor()); + } + else { + target = null; + } + } + else if (target instanceof PsiMethod) { + type = substitutor.substitute(((PsiMethod)target).getReturnType()); + if (type instanceof PsiClassType) { + final JavaResolveResult typeResult = ((PsiClassType)type).resolveGenerics(); + target = typeResult.getElement(); + substitutor = substitutor.putAll(typeResult.getSubstitutor()); + } + else { + target = null; + } + final PsiType[] types = referenceElement.getTypeParameters(); + if (target instanceof PsiClass) { + substitutor = substitutor.putAll((PsiClass)target, types); + } + } + else if (target instanceof PsiClass) { + processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null); + } } - } + } - if (dummyImplicitConstructor) { - processDummyConstructor(processor, superClass); - } + if (target != null) { + return target.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, substitutor), target, ref); } - } else { - LOG.error("Unknown name element " + referenceNameElement + " in reference " + ref.getText() + "(" + ref + ")"); - } - } else if (referenceNameElement instanceof PsiIdentifier) { - processor.setIsConstructor(false); - processor.setName(referenceNameElement.getText()); - processor.setAccessClass(null); - resolveAndWalk(processor, ref, null); - } else { - LOG.error("Unknown name element " + referenceNameElement + " in reference " + ref.getText() + "(" + ref + ")"); } - } else { - // Complex expression - final PsiElement referenceName = methodCall.getMethodExpression().getReferenceNameElement(); - final PsiManager manager = call.getManager(); - final PsiElement qualifier = ref.getQualifier(); - if (referenceName == null) { - // e.g. "manager.(beginTransaction)" - throw new MethodProcessorSetupFailedException("Can't resolve method name for this expression"); + else { + // simple expression -> trying to resolve variable or method + return treeWalkUp(processor, ref, maxScope); } - if (referenceName instanceof PsiIdentifier && qualifier instanceof PsiExpression) { - PsiType type = ((PsiExpression) qualifier).getType(); - if (type != null && qualifier instanceof PsiReferenceExpression) { - final PsiElement resolve = ((PsiReferenceExpression) qualifier).resolve(); - if (resolve instanceof PsiEnumConstant) { - final PsiEnumConstantInitializer initializingClass = ((PsiEnumConstant) resolve).getInitializingClass(); - if (hasDesiredMethod(methodCall, type, initializingClass)) { - processQualifierResult(new ClassCandidateInfo(initializingClass, PsiSubstitutor.EMPTY), processor, methodCall); - return; - } - } else if (resolve instanceof PsiVariable && ((PsiVariable) resolve).hasModifierProperty(PsiModifier.FINAL) && ((PsiVariable) resolve).hasInitializer()) { - final PsiExpression initializer = ((PsiVariable) resolve).getInitializer(); - if (initializer instanceof PsiNewExpression) { - final PsiAnonymousClass anonymousClass = ((PsiNewExpression) initializer).getAnonymousClass(); - if (hasDesiredMethod(methodCall, type, anonymousClass)) { - type = initializer.getType(); + + return true; + } + + public static void setupAndRunProcessor( + @Nonnull MethodsProcessor processor, + @Nonnull PsiCallExpression call, + boolean dummyImplicitConstructor + ) + throws MethodProcessorSetupFailedException { + if (call instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)call; + final PsiJavaCodeReferenceElement ref = methodCall.getMethodExpression(); + + + processor.setArgumentList(methodCall.getArgumentList()); + processor.obtainTypeArguments(methodCall); + if (!ref.isQualified() || ref.getReferenceNameElement() instanceof PsiKeyword) { + final PsiElement referenceNameElement = ref.getReferenceNameElement(); + if (referenceNameElement == null) { + return; + } + if (referenceNameElement instanceof PsiKeyword) { + final PsiKeyword keyword = (PsiKeyword)referenceNameElement; + + if (keyword.getTokenType() == JavaTokenType.THIS_KEYWORD) { + final PsiClass aClass = JavaResolveUtil.getContextClass(methodCall); + if (aClass == null) { + throw new MethodProcessorSetupFailedException("Can't resolve class for this expression"); + } + + processor.setIsConstructor(true); + processor.setAccessClass(aClass); + aClass.processDeclarations(processor, ResolveState.initial(), null, call); + + if (dummyImplicitConstructor) { + processDummyConstructor(processor, aClass); + } + } + else if (keyword.getTokenType() == JavaTokenType.SUPER_KEYWORD) { + PsiClass aClass = JavaResolveUtil.getContextClass(methodCall); + if (aClass == null) { + throw new MethodProcessorSetupFailedException("Can't resolve class for super expression"); + } + + final PsiClass superClass = aClass.getSuperClass(); + if (superClass != null) { + PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; + PsiClass runSuper = superClass; + List contextSubstitutors = new ArrayList<>(); + do { + if (runSuper != null) { + PsiSubstitutor superSubstitutor = + TypeConversionUtil.getSuperClassSubstitutor(runSuper, aClass, PsiSubstitutor.EMPTY); + contextSubstitutors.add(superSubstitutor); + } + if (aClass.hasModifierProperty(PsiModifier.STATIC)) { + break; + } + aClass = JavaResolveUtil.getContextClass(aClass); + if (aClass != null) { + runSuper = aClass.getSuperClass(); + } + } + while (aClass != null); + //apply substitutors in 'outer classes down to inner classes' order because inner class subst take precedence + for (int i = contextSubstitutors.size() - 1; i >= 0; i--) { + PsiSubstitutor contextSubstitutor = contextSubstitutors.get(i); + substitutor = substitutor.putAll(contextSubstitutor); + } + + processor.setIsConstructor(true); + processor.setAccessClass(null); + final PsiMethod[] constructors = superClass.getConstructors(); + ResolveState state = ResolveState.initial().put(PsiSubstitutor.KEY, substitutor); + for (PsiMethod constructor : constructors) { + if (!processor.execute(constructor, state)) { + return; + } + } + + if (dummyImplicitConstructor) { + processDummyConstructor(processor, superClass); + } + } + } + else { + LOG.error("Unknown name element " + referenceNameElement + " in reference " + ref.getText() + "(" + ref + ")"); + } + } + else if (referenceNameElement instanceof PsiIdentifier) { + processor.setIsConstructor(false); + processor.setName(referenceNameElement.getText()); + processor.setAccessClass(null); + resolveAndWalk(processor, ref, null); + } + else { + LOG.error("Unknown name element " + referenceNameElement + " in reference " + ref.getText() + "(" + ref + ")"); } - } } - } - if (type == null) { - if (qualifier instanceof PsiJavaCodeReferenceElement) { - final JavaResolveResult result = ((PsiJavaCodeReferenceElement) qualifier).advancedResolve(false); - if (result.getElement() instanceof PsiClass) { - processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null); - processQualifierResult(result, processor, methodCall); - } - } else { - throw new MethodProcessorSetupFailedException("Cant determine qualifier type!"); + else { + // Complex expression + final PsiElement referenceName = methodCall.getMethodExpression().getReferenceNameElement(); + final PsiManager manager = call.getManager(); + final PsiElement qualifier = ref.getQualifier(); + if (referenceName == null) { + // e.g. "manager.(beginTransaction)" + throw new MethodProcessorSetupFailedException("Can't resolve method name for this expression"); + } + if (referenceName instanceof PsiIdentifier && qualifier instanceof PsiExpression) { + PsiType type = ((PsiExpression)qualifier).getType(); + if (type != null && qualifier instanceof PsiReferenceExpression) { + final PsiElement resolve = ((PsiReferenceExpression)qualifier).resolve(); + if (resolve instanceof PsiEnumConstant) { + final PsiEnumConstantInitializer initializingClass = ((PsiEnumConstant)resolve).getInitializingClass(); + if (hasDesiredMethod(methodCall, type, initializingClass)) { + processQualifierResult( + new ClassCandidateInfo(initializingClass, PsiSubstitutor.EMPTY), + processor, + methodCall + ); + return; + } + } + else if (resolve instanceof PsiVariable + && ((PsiVariable)resolve).hasModifierProperty(PsiModifier.FINAL) + && ((PsiVariable)resolve).hasInitializer()) { + final PsiExpression initializer = ((PsiVariable)resolve).getInitializer(); + if (initializer instanceof PsiNewExpression) { + final PsiAnonymousClass anonymousClass = ((PsiNewExpression)initializer).getAnonymousClass(); + if (hasDesiredMethod(methodCall, type, anonymousClass)) { + type = initializer.getType(); + } + } + } + } + if (type == null) { + if (qualifier instanceof PsiJavaCodeReferenceElement) { + final JavaResolveResult result = ((PsiJavaCodeReferenceElement)qualifier).advancedResolve(false); + if (result.getElement() instanceof PsiClass) { + processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null); + processQualifierResult(result, processor, methodCall); + } + } + else { + throw new MethodProcessorSetupFailedException("Cant determine qualifier type!"); + } + } + else if (type instanceof PsiDisjunctionType) { + processQualifierType(((PsiDisjunctionType)type).getLeastUpperBound(), processor, manager, methodCall); + } + else if (type instanceof PsiCapturedWildcardType) { + final PsiType psiType = convertToTypeParameter((PsiCapturedWildcardType)type, methodCall); + if (psiType != null) { + processQualifierType(psiType, processor, manager, methodCall); + } + } + else { + processQualifierType(type, processor, manager, methodCall); + } + } + else { + LOG.error("ref: " + ref + " (" + ref.getClass() + ")," + + " ref.getReferenceNameElement()=" + ref.getReferenceNameElement() + + "; methodCall.getMethodExpression().getReferenceNameElement()=" + + methodCall.getMethodExpression().getReferenceNameElement() + + "; qualifier=" + qualifier); + } + } + } + else { + LOG.assertTrue(call instanceof PsiNewExpression); + PsiNewExpression newExpr = (PsiNewExpression)call; + PsiJavaCodeReferenceElement classRef = newExpr.getClassOrAnonymousClassReference(); + if (classRef == null) { + throw new MethodProcessorSetupFailedException("Cant get reference to class in new expression"); + } + + final JavaResolveResult result = classRef.advancedResolve(false); + PsiClass aClass = (PsiClass)result.getElement(); + if (aClass == null) { + throw new MethodProcessorSetupFailedException("Cant resolve class in new expression"); } - } else if (type instanceof PsiDisjunctionType) { - processQualifierType(((PsiDisjunctionType) type).getLeastUpperBound(), processor, manager, methodCall); - } else if (type instanceof PsiCapturedWildcardType) { - final PsiType psiType = convertToTypeParameter((PsiCapturedWildcardType) type, methodCall); - if (psiType != null) { - processQualifierType(psiType, processor, manager, methodCall); + processor.setIsConstructor(true); + processor.setAccessClass(aClass); + processor.setArgumentList(newExpr.getArgumentList()); + processor.obtainTypeArguments(newExpr); + aClass.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, result.getSubstitutor()), null, call); + + if (dummyImplicitConstructor) { + processDummyConstructor(processor, aClass); } - } else { - processQualifierType(type, processor, manager, methodCall); - } - } else { - LOG.error("ref: " + ref + " (" + ref.getClass() + ")," + - " ref.getReferenceNameElement()=" + ref.getReferenceNameElement() + - "; methodCall.getMethodExpression().getReferenceNameElement()=" + methodCall.getMethodExpression().getReferenceNameElement() + - "; qualifier=" + qualifier); } - } - } else { - LOG.assertTrue(call instanceof PsiNewExpression); - PsiNewExpression newExpr = (PsiNewExpression) call; - PsiJavaCodeReferenceElement classRef = newExpr.getClassOrAnonymousClassReference(); - if (classRef == null) { - throw new MethodProcessorSetupFailedException("Cant get reference to class in new expression"); - } - - final JavaResolveResult result = classRef.advancedResolve(false); - PsiClass aClass = (PsiClass) result.getElement(); - if (aClass == null) { - throw new MethodProcessorSetupFailedException("Cant resolve class in new expression"); - } - processor.setIsConstructor(true); - processor.setAccessClass(aClass); - processor.setArgumentList(newExpr.getArgumentList()); - processor.obtainTypeArguments(newExpr); - aClass.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, result.getSubstitutor()), null, call); - - if (dummyImplicitConstructor) { - processDummyConstructor(processor, aClass); - } } - } - private static PsiType convertToTypeParameter(PsiCapturedWildcardType type, PsiElement methodCall) { - GlobalSearchScope placeResolveScope = methodCall.getResolveScope(); - PsiType upperBound = PsiClassImplUtil.correctType(type.getUpperBound(), placeResolveScope); - while (upperBound instanceof PsiCapturedWildcardType) { - upperBound = PsiClassImplUtil.correctType(((PsiCapturedWildcardType) upperBound).getUpperBound(), placeResolveScope); - } + private static PsiType convertToTypeParameter(PsiCapturedWildcardType type, PsiElement methodCall) { + GlobalSearchScope placeResolveScope = methodCall.getResolveScope(); + PsiType upperBound = PsiClassImplUtil.correctType(type.getUpperBound(), placeResolveScope); + while (upperBound instanceof PsiCapturedWildcardType) { + upperBound = PsiClassImplUtil.correctType(((PsiCapturedWildcardType)upperBound).getUpperBound(), placeResolveScope); + } - //arrays can't participate in extends list - if (upperBound instanceof PsiArrayType) { - return upperBound; - } + //arrays can't participate in extends list + if (upperBound instanceof PsiArrayType) { + return upperBound; + } - if (upperBound != null) { - return InferenceSession.createTypeParameterTypeWithUpperBound(upperBound, methodCall); + if (upperBound != null) { + return InferenceSession.createTypeParameterTypeWithUpperBound(upperBound, methodCall); + } + return null; } - return null; - } - - private static boolean hasDesiredMethod(PsiMethodCallExpression methodCall, PsiType type, PsiAnonymousClass anonymousClass) { - if (anonymousClass != null && type.equals(anonymousClass.getBaseClassType())) { - final PsiMethod[] refMethods = anonymousClass.findMethodsByName(methodCall.getMethodExpression().getReferenceName(), false); - if (refMethods.length > 0) { - final PsiClass baseClass = PsiUtil.resolveClassInType(type); - if (baseClass != null && !hasCovariantOverridingOrNotPublic(baseClass, refMethods)) { - for (PsiMethod method : refMethods) { - if (method.findSuperMethods(baseClass).length > 0) { - return true; + + private static boolean hasDesiredMethod(PsiMethodCallExpression methodCall, PsiType type, PsiAnonymousClass anonymousClass) { + if (anonymousClass != null && type.equals(anonymousClass.getBaseClassType())) { + final PsiMethod[] refMethods = anonymousClass.findMethodsByName(methodCall.getMethodExpression().getReferenceName(), false); + if (refMethods.length > 0) { + final PsiClass baseClass = PsiUtil.resolveClassInType(type); + if (baseClass != null && !hasCovariantOverridingOrNotPublic(baseClass, refMethods)) { + for (PsiMethod method : refMethods) { + if (method.findSuperMethods(baseClass).length > 0) { + return true; + } + } + } } - } } - } + return false; } - return false; - } - - private static boolean hasCovariantOverridingOrNotPublic(PsiClass baseClass, PsiMethod[] refMethods) { - for (PsiMethod method : refMethods) { - final PsiType methodReturnType = method.getReturnType(); - for (PsiMethod superMethod : method.findSuperMethods(baseClass)) { - if (!Comparing.equal(methodReturnType, superMethod.getReturnType())) { - return true; - } - if (!superMethod.hasModifierProperty(PsiModifier.PUBLIC)) { - return true; + private static boolean hasCovariantOverridingOrNotPublic(PsiClass baseClass, PsiMethod[] refMethods) { + for (PsiMethod method : refMethods) { + final PsiType methodReturnType = method.getReturnType(); + for (PsiMethod superMethod : method.findSuperMethods(baseClass)) { + if (!Comparing.equal(methodReturnType, superMethod.getReturnType())) { + return true; + } + + if (!superMethod.hasModifierProperty(PsiModifier.PUBLIC)) { + return true; + } + } } - } - } - return false; - } - - private static boolean processQualifierType(@Nonnull final PsiType type, - final MethodsProcessor processor, - PsiManager manager, - PsiMethodCallExpression call) throws MethodProcessorSetupFailedException { - LOG.assertTrue(type.isValid()); - if (type instanceof PsiClassType) { - JavaResolveResult qualifierResult = ((PsiClassType) type).resolveGenerics(); - return processQualifierResult(qualifierResult, processor, call); - } - if (type instanceof PsiArrayType) { - LanguageLevel languageLevel = PsiUtil.getLanguageLevel(call); - PsiElementFactory factory = JavaPsiFacade.getElementFactory(manager.getProject()); - JavaResolveResult qualifierResult = - factory.getArrayClassType(((PsiArrayType) type).getComponentType(), languageLevel).resolveGenerics(); - return processQualifierResult(qualifierResult, processor, call); + return false; } - if (type instanceof PsiIntersectionType) { - for (PsiType conjunct : ((PsiIntersectionType) type).getConjuncts()) { - if (!processQualifierType(conjunct, processor, manager, call)) { - return false; + + private static boolean processQualifierType( + @Nonnull final PsiType type, + final MethodsProcessor processor, + PsiManager manager, + PsiMethodCallExpression call + ) throws MethodProcessorSetupFailedException { + LOG.assertTrue(type.isValid()); + if (type instanceof PsiClassType) { + JavaResolveResult qualifierResult = ((PsiClassType)type).resolveGenerics(); + return processQualifierResult(qualifierResult, processor, call); + } + if (type instanceof PsiArrayType) { + LanguageLevel languageLevel = PsiUtil.getLanguageLevel(call); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(manager.getProject()); + JavaResolveResult qualifierResult = + factory.getArrayClassType(((PsiArrayType)type).getComponentType(), languageLevel).resolveGenerics(); + return processQualifierResult(qualifierResult, processor, call); } - } + if (type instanceof PsiIntersectionType) { + for (PsiType conjunct : ((PsiIntersectionType)type).getConjuncts()) { + if (!processQualifierType(conjunct, processor, manager, call)) { + return false; + } + } + } + + return true; } - return true; - } + private static boolean processQualifierResult( + @Nonnull JavaResolveResult qualifierResult, + @Nonnull MethodsProcessor processor, + @Nonnull PsiMethodCallExpression methodCall + ) throws MethodProcessorSetupFailedException { + PsiElement resolve = qualifierResult.getElement(); - private static boolean processQualifierResult(@Nonnull JavaResolveResult qualifierResult, - @Nonnull MethodsProcessor processor, - @Nonnull PsiMethodCallExpression methodCall) throws MethodProcessorSetupFailedException { - PsiElement resolve = qualifierResult.getElement(); + if (resolve == null) { + throw new MethodProcessorSetupFailedException("Cant determine qualifier class!"); + } - if (resolve == null) { - throw new MethodProcessorSetupFailedException("Cant determine qualifier class!"); - } + if (resolve instanceof PsiTypeParameter) { + processor.setAccessClass((PsiClass)resolve); + } + else if (resolve instanceof PsiClass) { + PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); + if (!(qualifier instanceof PsiSuperExpression)) { + processor.setAccessClass((PsiClass)PsiUtil.getAccessObjectClass(qualifier).getElement()); + } + else if (((PsiSuperExpression)qualifier).getQualifier() != null + && PsiUtil.isLanguageLevel8OrHigher(qualifier) + && CommonClassNames.JAVA_LANG_CLONEABLE.equals(((PsiClass)resolve).getQualifiedName()) + && ((PsiClass)resolve).isInterface()) { + processor.setAccessClass((PsiClass)resolve); + } + } - if (resolve instanceof PsiTypeParameter) { - processor.setAccessClass((PsiClass) resolve); - } else if (resolve instanceof PsiClass) { - PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); - if (!(qualifier instanceof PsiSuperExpression)) { - processor.setAccessClass((PsiClass) PsiUtil.getAccessObjectClass(qualifier).getElement()); - } else if (((PsiSuperExpression) qualifier).getQualifier() != null && PsiUtil.isLanguageLevel8OrHigher(qualifier) && - CommonClassNames.JAVA_LANG_CLONEABLE.equals(((PsiClass) resolve).getQualifiedName()) && ((PsiClass) resolve).isInterface()) { - processor.setAccessClass((PsiClass) resolve); - } + processor.setIsConstructor(false); + processor.setName(methodCall.getMethodExpression().getReferenceName()); + ResolveState state = ResolveState.initial().put(PsiSubstitutor.KEY, qualifierResult.getSubstitutor()); + return resolve.processDeclarations(processor, state, methodCall, methodCall); } - processor.setIsConstructor(false); - processor.setName(methodCall.getMethodExpression().getReferenceName()); - ResolveState state = ResolveState.initial().put(PsiSubstitutor.KEY, qualifierResult.getSubstitutor()); - return resolve.processDeclarations(processor, state, methodCall, methodCall); - } - - private static void processDummyConstructor(MethodsProcessor processor, PsiClass aClass) { - if (aClass instanceof PsiAnonymousClass) { - return; - } - try { - PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length != 0) { - return; - } - final PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject()); - final PsiMethod dummyConstructor = factory.createConstructor(); - PsiIdentifier nameIdentifier = aClass.getNameIdentifier(); - if (nameIdentifier != null) { - dummyConstructor.getNameIdentifier().replace(nameIdentifier); - } - processor.forceAddResult(dummyConstructor); - } catch (IncorrectOperationException e) { - LOG.error(e); + private static void processDummyConstructor(MethodsProcessor processor, PsiClass aClass) { + if (aClass instanceof PsiAnonymousClass) { + return; + } + try { + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length != 0) { + return; + } + final PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject()); + final PsiMethod dummyConstructor = factory.createConstructor(); + PsiIdentifier nameIdentifier = aClass.getNameIdentifier(); + if (nameIdentifier != null) { + dummyConstructor.getNameIdentifier().replace(nameIdentifier); + } + processor.forceAddResult(dummyConstructor); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/FunctionalInterfaceSuggester.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/FunctionalInterfaceSuggester.java index 8093712a88..e1b10ce827 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/FunctionalInterfaceSuggester.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/FunctionalInterfaceSuggester.java @@ -21,300 +21,277 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; -import consulo.application.util.function.Processor; +import consulo.annotation.access.RequiredReadAction; +import consulo.java.language.module.util.JavaClassNames; import consulo.language.psi.PsiElement; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.util.PsiTreeUtil; import consulo.project.Project; - import jakarta.annotation.Nonnull; + import java.util.*; import java.util.function.Function; - -public class FunctionalInterfaceSuggester -{ - public static final String[] FUNCTIONAL_INTERFACES = { - //old jdk without annotations - CommonClassNames.JAVA_LANG_RUNNABLE, - CommonClassNames.JAVA_UTIL_CONCURRENT_CALLABLE, - CommonClassNames.JAVA_UTIL_COMPARATOR, - - //IDEA - "com.intellij.util.Function", - "com.intellij.util.Consumer", - "com.intellij.openapi.util.Computable", - "com.intellij.openapi.util.Condition", - "com.intellij.util.Processor", - "com.intellij.util.Producer", - - //guava - "com.google.common.base.Function", - "com.google.common.base.Predicate", - "com.google.common.base.Supplier", - - //common collections - "org.apache.commons.collections.Closure", - "org.apache.commons.collections.Factory", - "org.apache.commons.collections.Predicate", - "org.apache.commons.collections.Transformer", - }; - - public static Collection suggestFunctionalInterfaces(final @Nonnull PsiFunctionalExpression expression) - { - final PsiType qualifierType = expression instanceof PsiMethodReferenceExpression - ? PsiMethodReferenceUtil.getQualifierType((PsiMethodReferenceExpression) expression) : null; - return suggestFunctionalInterfaces(expression, aClass -> composeAcceptableType(aClass, expression, qualifierType)); - } - - public static Collection suggestFunctionalInterfaces(final @Nonnull PsiMethod method) - { - return suggestFunctionalInterfaces(method, false); - } - - public static Collection suggestFunctionalInterfaces(final @Nonnull PsiMethod method, - boolean suggestUnhandledThrowables) - { - if(method.isConstructor()) - { - return Collections.emptyList(); - } - - return suggestFunctionalInterfaces(method, aClass -> { - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(aClass); - if(interfaceMethod != null) - { - final PsiParameter[] parameters = method.getParameterList().getParameters(); - final PsiParameter[] interfaceMethodParameters = interfaceMethod.getParameterList().getParameters(); - if(parameters.length != interfaceMethodParameters.length) - { - return Collections.emptyList(); - } - - final PsiType[] left = new PsiType[parameters.length + 1]; - final PsiType[] right = new PsiType[parameters.length + 1]; - - for(int i = 0; i < parameters.length; i++) - { - left[i] = interfaceMethodParameters[i].getType(); - right[i] = parameters[i].getType(); - } - - left[parameters.length] = method.getReturnType(); - right[parameters.length] = interfaceMethod.getReturnType(); - - final PsiTypeParameter[] typeParameters = aClass.getTypeParameters(); - final PsiSubstitutor substitutor = PsiResolveHelper.getInstance(aClass.getProject()) - .inferTypeArguments(typeParameters, left, right, PsiUtil.getLanguageLevel(method)); - if(PsiUtil.isRawSubstitutor(aClass, substitutor)) - { - return Collections.emptyList(); - } - - for(int i = 0; i < interfaceMethodParameters.length; i++) - { - PsiType paramType = parameters[i].getType(); - PsiType interfaceParamType = substitutor.substitute(interfaceMethodParameters[i].getType()); - if(!(interfaceParamType instanceof PsiPrimitiveType - ? paramType.equals(interfaceParamType) : TypeConversionUtil.isAssignable(paramType, interfaceParamType))) - { - return Collections.emptyList(); - } - } - - final PsiType returnType = method.getReturnType(); - PsiType interfaceMethodReturnType = substitutor.substitute(interfaceMethod.getReturnType()); - if(returnType != null && !TypeConversionUtil.isAssignable(returnType, interfaceMethodReturnType)) - { - return Collections.emptyList(); - } - - if(interfaceMethodReturnType instanceof PsiPrimitiveType && !interfaceMethodReturnType.equals(returnType)) - { - return Collections.emptyList(); - } - - final PsiClassType[] interfaceThrownTypes = interfaceMethod.getThrowsList().getReferencedTypes(); - final PsiClassType[] thrownTypes = method.getThrowsList().getReferencedTypes(); - for(PsiClassType thrownType : thrownTypes) - { - if(!ExceptionUtil.isHandledBy(thrownType, interfaceThrownTypes, substitutor)) - { - return Collections.emptyList(); - } - } - - if(!suggestUnhandledThrowables) - { - for(PsiClassType thrownType : interfaceThrownTypes) - { - final PsiCodeBlock codeBlock = PsiTreeUtil.getContextOfType(method, PsiCodeBlock.class); - PsiType substitutedThrowable = substitutor.substitute(thrownType); - if(codeBlock == null || !(substitutedThrowable instanceof PsiClassType) || - !ExceptionUtil.isHandled((PsiClassType) substitutedThrowable, codeBlock)) - { - return Collections.emptyList(); - } - } - } - - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(aClass.getProject()); - return Collections.singletonList(elementFactory.createType(aClass, substitutor)); - } - return Collections.emptyList(); - }); - } - - private static Collection suggestFunctionalInterfaces(final @Nonnull T element, - final Function> acceptanceChecker) - { - final Project project = element.getProject(); - final Set types = new HashSet<>(); - final Processor consumer = member -> { - if(member instanceof PsiClass && Java15APIUsageInspection.getLastIncompatibleLanguageLevel(member, PsiUtil.getLanguageLevel(element)) == null) - { - if(!JavaPsiFacade.getInstance(project).getResolveHelper().isAccessible(member, element, null)) - { - return true; - } - types.addAll(acceptanceChecker.apply((PsiClass) member)); - } - return true; - }; - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final GlobalSearchScope allScope = GlobalSearchScope.allScope(project); - final PsiClass functionalInterfaceClass = psiFacade.findClass(CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, allScope); - if(functionalInterfaceClass != null) - { - AnnotatedMembersSearch.search(functionalInterfaceClass, element.getResolveScope()).forEach(consumer); - } - - for(String functionalInterface : FUNCTIONAL_INTERFACES) - { - final PsiClass aClass = psiFacade.findClass(functionalInterface, element.getResolveScope()); - if(aClass != null) - { - consumer.process(aClass); - } - } - - final ArrayList typesToSuggest = new ArrayList<>(types); - Collections.sort(typesToSuggest, Comparator.comparing(PsiType::getCanonicalText)); - return typesToSuggest; - } - - private static List composeAcceptableType(@Nonnull PsiClass interface2Consider, - @Nonnull PsiFunctionalExpression expression, - PsiType qualifierType) - { - final PsiType type = JavaPsiFacade.getElementFactory(interface2Consider.getProject()).createType(interface2Consider, PsiSubstitutor.EMPTY); - if(expression.isAcceptable(type)) - { - return Collections.singletonList(type); - } - - return composeAcceptableType1(interface2Consider, expression, qualifierType); - } - - @Nonnull - private static List composeAcceptableType1(final PsiClass interface2Consider, - final PsiFunctionalExpression expression, - final PsiType qualifierType) - { - if(interface2Consider.hasTypeParameters()) - { - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(interface2Consider); - if(interfaceMethod != null) - { - final PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); - Project project = interface2Consider.getProject(); - PsiType returnType = interfaceMethod.getReturnType(); - if(expression instanceof PsiLambdaExpression && ((PsiLambdaExpression) expression).hasFormalParameterTypes()) - { - PsiParameter[] functionalExprParameters = ((PsiLambdaExpression) expression).getParameterList().getParameters(); - if(parameters.length != functionalExprParameters.length) - { - return Collections.emptyList(); - } - - final PsiType[] left = new PsiType[parameters.length + 1]; - final PsiType[] right = new PsiType[parameters.length + 1]; - for(int i = 0; i < functionalExprParameters.length; i++) - { - left[i] = parameters[i].getType(); - right[i] = functionalExprParameters[i].getType(); - } - - List returnExpressions = LambdaUtil.getReturnExpressions(((PsiLambdaExpression) expression)); - left[parameters.length] = returnExpressions.isEmpty() ? PsiType.VOID : returnExpressions.get(0).getType(); - right[parameters.length] = returnType; - - final PsiSubstitutor substitutor = PsiResolveHelper.getInstance(project) - .inferTypeArguments(interface2Consider.getTypeParameters(), left, right, PsiUtil.getLanguageLevel(expression)); - - PsiType type = JavaPsiFacade.getElementFactory(project).createType(interface2Consider, substitutor); - - if(expression.isAcceptable(type)) - { - return Collections.singletonList(type); - } - } - else if(expression instanceof PsiMethodReferenceExpression) - { - List types = new ArrayList<>(); - for(JavaResolveResult result : ((PsiMethodReferenceExpression) expression).multiResolve(true)) - { - final PsiElement element = result.getElement(); - if(element instanceof PsiMethod) - { - PsiMethod method = (PsiMethod) element; - int offset = hasOffset((PsiMethodReferenceExpression) expression, method) ? 1 : 0; - final PsiParameter[] targetMethodParameters = method.getParameterList().getParameters(); - if(targetMethodParameters.length + offset == parameters.length) - { - final PsiType[] left = new PsiType[parameters.length + 1]; - final PsiType[] right = new PsiType[parameters.length + 1]; - if(offset > 0) - { - if(qualifierType == null) - { - continue; - } - left[0] = parameters[0].getType(); - right[0] = qualifierType; - } - - for(int i = 0; i < targetMethodParameters.length; i++) - { - left[i + offset] = parameters[i + offset].getType(); - right[i + offset] = targetMethodParameters[i].getType(); - } - - left[parameters.length] = method.isConstructor() ? qualifierType : method.getReturnType(); - right[parameters.length] = returnType; - - final PsiSubstitutor substitutor = PsiResolveHelper.getInstance(project) - .inferTypeArguments(interface2Consider.getTypeParameters(), left, right, PsiUtil.getLanguageLevel(expression)); - - PsiType type = JavaPsiFacade.getElementFactory(project).createType(interface2Consider, substitutor); - - if(expression.isAcceptable(type)) - { - types.add(type); - } - } - } - } - return types; - } - } - } - return Collections.emptyList(); - } - - private static boolean hasOffset(PsiMethodReferenceExpression expression, PsiMethod method) - { - return PsiMethodReferenceUtil.isStaticallyReferenced(expression) && - !method.hasModifierProperty(PsiModifier.STATIC) && - !method.isConstructor(); - } +import java.util.function.Predicate; + +public class FunctionalInterfaceSuggester { + public static final String[] FUNCTIONAL_INTERFACES = { + //old jdk without annotations + JavaClassNames.JAVA_LANG_RUNNABLE, + JavaClassNames.JAVA_UTIL_CONCURRENT_CALLABLE, + JavaClassNames.JAVA_UTIL_COMPARATOR, + + //IDEA + "com.intellij.util.Function", + "com.intellij.util.Consumer", + "com.intellij.openapi.util.Computable", + "com.intellij.openapi.util.Condition", + "com.intellij.util.Processor", + "com.intellij.util.Producer", + + //guava + "com.google.common.base.Function", + "com.google.common.base.Predicate", + "com.google.common.base.Supplier", + + //common collections + "org.apache.commons.collections.Closure", + "org.apache.commons.collections.Factory", + "org.apache.commons.collections.Predicate", + "org.apache.commons.collections.Transformer", + }; + + public static Collection suggestFunctionalInterfaces(@Nonnull PsiFunctionalExpression expression) { + PsiType qualifierType = expression instanceof PsiMethodReferenceExpression methodRefExpr + ? PsiMethodReferenceUtil.getQualifierType(methodRefExpr) : null; + return suggestFunctionalInterfaces(expression, aClass -> composeAcceptableType(aClass, expression, qualifierType)); + } + + public static Collection suggestFunctionalInterfaces(@Nonnull PsiMethod method) { + return suggestFunctionalInterfaces(method, false); + } + + public static Collection suggestFunctionalInterfaces( + @Nonnull PsiMethod method, + boolean suggestUnhandledThrowables + ) { + if (method.isConstructor()) { + return Collections.emptyList(); + } + + return suggestFunctionalInterfaces( + method, + aClass -> { + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(aClass); + if (interfaceMethod != null) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiParameter[] interfaceMethodParameters = interfaceMethod.getParameterList().getParameters(); + if (parameters.length != interfaceMethodParameters.length) { + return Collections.emptyList(); + } + + PsiType[] left = new PsiType[parameters.length + 1]; + PsiType[] right = new PsiType[parameters.length + 1]; + + for (int i = 0; i < parameters.length; i++) { + left[i] = interfaceMethodParameters[i].getType(); + right[i] = parameters[i].getType(); + } + + left[parameters.length] = method.getReturnType(); + right[parameters.length] = interfaceMethod.getReturnType(); + + PsiTypeParameter[] typeParameters = aClass.getTypeParameters(); + PsiSubstitutor substitutor = PsiResolveHelper.getInstance(aClass.getProject()) + .inferTypeArguments(typeParameters, left, right, PsiUtil.getLanguageLevel(method)); + if (PsiUtil.isRawSubstitutor(aClass, substitutor)) { + return Collections.emptyList(); + } + + for (int i = 0; i < interfaceMethodParameters.length; i++) { + PsiType paramType = parameters[i].getType(); + PsiType interfaceParamType = substitutor.substitute(interfaceMethodParameters[i].getType()); + if (!(interfaceParamType instanceof PsiPrimitiveType + ? paramType.equals(interfaceParamType) + : TypeConversionUtil.isAssignable(paramType, interfaceParamType))) { + return Collections.emptyList(); + } + } + + PsiType returnType = method.getReturnType(); + PsiType interfaceMethodReturnType = substitutor.substitute(interfaceMethod.getReturnType()); + if (returnType != null && !TypeConversionUtil.isAssignable(returnType, interfaceMethodReturnType)) { + return Collections.emptyList(); + } + + if (interfaceMethodReturnType instanceof PsiPrimitiveType && !interfaceMethodReturnType.equals(returnType)) { + return Collections.emptyList(); + } + + PsiClassType[] interfaceThrownTypes = interfaceMethod.getThrowsList().getReferencedTypes(); + PsiClassType[] thrownTypes = method.getThrowsList().getReferencedTypes(); + for (PsiClassType thrownType : thrownTypes) { + if (!ExceptionUtil.isHandledBy(thrownType, interfaceThrownTypes, substitutor)) { + return Collections.emptyList(); + } + } + + if (!suggestUnhandledThrowables) { + for (PsiClassType thrownType : interfaceThrownTypes) { + PsiCodeBlock codeBlock = PsiTreeUtil.getContextOfType(method, PsiCodeBlock.class); + PsiType substitutedThrowable = substitutor.substitute(thrownType); + if (codeBlock == null || !(substitutedThrowable instanceof PsiClassType) + || !ExceptionUtil.isHandled((PsiClassType)substitutedThrowable, codeBlock)) { + return Collections.emptyList(); + } + } + } + + PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(aClass.getProject()); + return Collections.singletonList(elementFactory.createType(aClass, substitutor)); + } + return Collections.emptyList(); + } + ); + } + + private static Collection suggestFunctionalInterfaces( + @Nonnull T element, + Function> acceptanceChecker + ) { + Project project = element.getProject(); + Set types = new HashSet<>(); + Predicate consumer = member -> { + if (member instanceof PsiClass + && Java15APIUsageInspection.getLastIncompatibleLanguageLevel(member, PsiUtil.getLanguageLevel(element)) == null) { + if (!JavaPsiFacade.getInstance(project).getResolveHelper().isAccessible(member, element, null)) { + return true; + } + types.addAll(acceptanceChecker.apply((PsiClass)member)); + } + return true; + }; + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + GlobalSearchScope allScope = GlobalSearchScope.allScope(project); + PsiClass functionalInterfaceClass = psiFacade.findClass(JavaClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, allScope); + if (functionalInterfaceClass != null) { + AnnotatedMembersSearch.search(functionalInterfaceClass, element.getResolveScope()).forEach(consumer); + } + + for (String functionalInterface : FUNCTIONAL_INTERFACES) { + PsiClass aClass = psiFacade.findClass(functionalInterface, element.getResolveScope()); + if (aClass != null) { + consumer.test(aClass); + } + } + + ArrayList typesToSuggest = new ArrayList<>(types); + Collections.sort(typesToSuggest, Comparator.comparing(PsiType::getCanonicalText)); + return typesToSuggest; + } + + @RequiredReadAction + private static List composeAcceptableType( + @Nonnull PsiClass interface2Consider, + @Nonnull PsiFunctionalExpression expression, + PsiType qualifierType + ) { + PsiType type = + JavaPsiFacade.getElementFactory(interface2Consider.getProject()).createType(interface2Consider, PsiSubstitutor.EMPTY); + if (expression.isAcceptable(type)) { + return Collections.singletonList(type); + } + + return composeAcceptableType1(interface2Consider, expression, qualifierType); + } + + @Nonnull + @RequiredReadAction + private static List composeAcceptableType1( + PsiClass interface2Consider, + PsiFunctionalExpression expression, + PsiType qualifierType + ) { + if (interface2Consider.hasTypeParameters()) { + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(interface2Consider); + if (interfaceMethod != null) { + PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); + Project project = interface2Consider.getProject(); + PsiType returnType = interfaceMethod.getReturnType(); + if (expression instanceof PsiLambdaExpression lambda && lambda.hasFormalParameterTypes()) { + PsiParameter[] functionalExprParameters = lambda.getParameterList().getParameters(); + if (parameters.length != functionalExprParameters.length) { + return Collections.emptyList(); + } + + PsiType[] left = new PsiType[parameters.length + 1]; + PsiType[] right = new PsiType[parameters.length + 1]; + for (int i = 0; i < functionalExprParameters.length; i++) { + left[i] = parameters[i].getType(); + right[i] = functionalExprParameters[i].getType(); + } + + List returnExpressions = LambdaUtil.getReturnExpressions(lambda); + left[parameters.length] = returnExpressions.isEmpty() ? PsiType.VOID : returnExpressions.get(0).getType(); + right[parameters.length] = returnType; + + PsiSubstitutor substitutor = PsiResolveHelper.getInstance(project) + .inferTypeArguments(interface2Consider.getTypeParameters(), left, right, PsiUtil.getLanguageLevel(expression)); + + PsiType type = JavaPsiFacade.getElementFactory(project).createType(interface2Consider, substitutor); + + if (expression.isAcceptable(type)) { + return Collections.singletonList(type); + } + } + else if (expression instanceof PsiMethodReferenceExpression methodRefExpr) { + List types = new ArrayList<>(); + for (JavaResolveResult result : methodRefExpr.multiResolve(true)) { + PsiElement element = result.getElement(); + if (element instanceof PsiMethod method) { + int offset = hasOffset(methodRefExpr, method) ? 1 : 0; + PsiParameter[] targetMethodParameters = method.getParameterList().getParameters(); + if (targetMethodParameters.length + offset == parameters.length) { + PsiType[] left = new PsiType[parameters.length + 1]; + PsiType[] right = new PsiType[parameters.length + 1]; + if (offset > 0) { + if (qualifierType == null) { + continue; + } + left[0] = parameters[0].getType(); + right[0] = qualifierType; + } + + for (int i = 0; i < targetMethodParameters.length; i++) { + left[i + offset] = parameters[i + offset].getType(); + right[i + offset] = targetMethodParameters[i].getType(); + } + + left[parameters.length] = method.isConstructor() ? qualifierType : method.getReturnType(); + right[parameters.length] = returnType; + + PsiSubstitutor substitutor = PsiResolveHelper.getInstance(project).inferTypeArguments( + interface2Consider.getTypeParameters(), + left, + right, + PsiUtil.getLanguageLevel(methodRefExpr) + ); + + PsiType type = JavaPsiFacade.getElementFactory(project).createType(interface2Consider, substitutor); + + if (methodRefExpr.isAcceptable(type)) { + types.add(type); + } + } + } + } + return types; + } + } + } + return Collections.emptyList(); + } + + private static boolean hasOffset(PsiMethodReferenceExpression expression, PsiMethod method) { + return PsiMethodReferenceUtil.isStaticallyReferenced(expression) && !method.isStatic() && !method.isConstructor(); + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaClassNameCompletionContributor.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaClassNameCompletionContributor.java index d9b9949705..e272c4a081 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaClassNameCompletionContributor.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaClassNameCompletionContributor.java @@ -24,17 +24,17 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.javadoc.PsiDocComment; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.application.util.function.Processor; import consulo.application.util.matcher.PrefixMatcher; import consulo.codeEditor.Editor; import consulo.java.language.module.util.JavaClassNames; -import consulo.language.LangBundle; import consulo.language.Language; import consulo.language.custom.CustomSyntaxTableFileType; import consulo.language.editor.completion.*; import consulo.language.editor.completion.lookup.InsertHandler; import consulo.language.editor.completion.lookup.LookupElement; +import consulo.language.localize.LanguageLocalize; import consulo.language.pattern.ElementPattern; import consulo.language.plain.psi.PsiPlainTextFile; import consulo.language.psi.PsiComment; @@ -52,8 +52,6 @@ import consulo.util.collection.SmartList; import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.Condition; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -61,6 +59,7 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Predicate; import static com.intellij.java.impl.codeInsight.completion.JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER; import static com.intellij.java.language.patterns.PsiJavaPatterns.*; @@ -70,224 +69,260 @@ */ @ExtensionImpl(id = "javaClassName", order = "last, before default") public class JavaClassNameCompletionContributor extends CompletionContributor { - public static final PsiJavaElementPattern.Capture AFTER_NEW = psiJavaElement().afterLeaf(PsiKeyword.NEW); - private static final PsiJavaElementPattern.Capture IN_TYPE_PARAMETER = psiJavaElement().afterLeaf(PsiKeyword.EXTENDS, PsiKeyword.SUPER, "&").withParent(psiElement(PsiReferenceList.class) - .withParent(PsiTypeParameter.class)); - private static final ElementPattern IN_EXTENDS_IMPLEMENTS = psiElement().inside(psiElement(PsiReferenceList.class).withParent(psiClass())); + public static final PsiJavaElementPattern.Capture AFTER_NEW = psiJavaElement().afterLeaf(PsiKeyword.NEW); + private static final PsiJavaElementPattern.Capture IN_TYPE_PARAMETER = psiJavaElement() + .afterLeaf(PsiKeyword.EXTENDS, PsiKeyword.SUPER, "&") + .withParent(psiElement(PsiReferenceList.class).withParent(PsiTypeParameter.class)); + private static final ElementPattern IN_EXTENDS_IMPLEMENTS = + psiElement().inside(psiElement(PsiReferenceList.class).withParent(psiClass())); - @Override - public void fillCompletionVariants(@Nonnull CompletionParameters parameters, @Nonnull final CompletionResultSet _result) { - if (parameters.getCompletionType() == CompletionType.CLASS_NAME || parameters.isExtendedCompletion() && mayContainClassName(parameters)) { - addAllClasses(parameters, _result); + @Override + @RequiredReadAction + public void fillCompletionVariants(@Nonnull CompletionParameters parameters, @Nonnull CompletionResultSet _result) { + if (parameters.getCompletionType() == CompletionType.CLASS_NAME + || parameters.isExtendedCompletion() && mayContainClassName(parameters)) { + addAllClasses(parameters, _result); + } } - } - - static void addAllClasses(CompletionParameters parameters, final CompletionResultSet _result) { - CompletionResultSet result = _result.withPrefixMatcher(CompletionUtilCore.findReferenceOrAlphanumericPrefix(parameters)); - addAllClasses(parameters, parameters.getInvocationCount() <= 1, result.getPrefixMatcher(), _result); - } - private static boolean mayContainClassName(CompletionParameters parameters) { - PsiElement position = parameters.getPosition(); - PsiFile file = position.getContainingFile(); - if (file instanceof PsiPlainTextFile || file.getFileType() instanceof CustomSyntaxTableFileType) { - return true; + @RequiredReadAction + static void addAllClasses(CompletionParameters parameters, CompletionResultSet _result) { + CompletionResultSet result = _result.withPrefixMatcher(CompletionUtilCore.findReferenceOrAlphanumericPrefix(parameters)); + addAllClasses(parameters, parameters.getInvocationCount() <= 1, result.getPrefixMatcher(), _result); } - if (SkipAutopopupInStrings.isInStringLiteral(position)) { - return true; - } - PsiComment comment = PsiTreeUtil.getParentOfType(position, PsiComment.class, false); - if (comment != null && !(comment instanceof PsiDocComment)) { - return true; - } - return false; - } - - public static void addAllClasses(@Nonnull CompletionParameters parameters, final boolean filterByScope, @Nonnull final PrefixMatcher matcher, @Nonnull final Consumer consumer) { - final PsiElement insertedElement = parameters.getPosition(); - if (JavaCompletionContributor.ANNOTATION_NAME.accepts(insertedElement)) { - MultiMap annoMap = getAllAnnotationClasses(insertedElement, matcher); - Processor processor = new LimitedAccessibleClassPreprocessor(parameters, filterByScope, anno -> - { - JavaPsiClassReferenceElement item = AllClassesGetter.createLookupItem(anno, JAVA_CLASS_INSERT_HANDLER); - item.addLookupStrings(getClassNameWithContainers(anno)); - consumer.accept(item); - return true; - }); - for (String name : matcher.sortMatching(annoMap.keySet())) { - if (!ContainerUtil.process(annoMap.get(name), processor)) { - break; + private static boolean mayContainClassName(CompletionParameters parameters) { + PsiElement position = parameters.getPosition(); + PsiFile file = position.getContainingFile(); + if (file instanceof PsiPlainTextFile || file.getFileType() instanceof CustomSyntaxTableFileType) { + return true; + } + if (SkipAutopopupInStrings.isInStringLiteral(position)) { + return true; } - } - return; + PsiComment comment = PsiTreeUtil.getParentOfType(position, PsiComment.class, false); + return comment != null && !(comment instanceof PsiDocComment); } - final ElementFilter filter = IN_EXTENDS_IMPLEMENTS.accepts(insertedElement) ? new ExcludeDeclaredFilter(new ClassFilter(PsiClass.class)) : IN_TYPE_PARAMETER.accepts(insertedElement) ? new - ExcludeDeclaredFilter(new ClassFilter(PsiTypeParameter.class)) : TrueFilter.INSTANCE; + @RequiredReadAction + public static void addAllClasses( + @Nonnull CompletionParameters parameters, + final boolean filterByScope, + @Nonnull final PrefixMatcher matcher, + @Nonnull final Consumer consumer + ) { + final PsiElement insertedElement = parameters.getPosition(); - final boolean inJavaContext = parameters.getPosition() instanceof PsiIdentifier; - final boolean afterNew = AFTER_NEW.accepts(insertedElement); - if (afterNew) { - final PsiExpression expr = PsiTreeUtil.getContextOfType(insertedElement, PsiExpression.class, true); - for (final ExpectedTypeInfo info : ExpectedTypesProvider.getExpectedTypes(expr, true)) { - final PsiType type = info.getType(); - final PsiClass psiClass = PsiUtil.resolveClassInType(type); - if (psiClass != null && psiClass.getName() != null) { - consumer.accept(createClassLookupItem(psiClass, inJavaContext)); - } - final PsiType defaultType = info.getDefaultType(); - if (!defaultType.equals(type)) { - final PsiClass defClass = PsiUtil.resolveClassInType(defaultType); - if (defClass != null && defClass.getName() != null) { - consumer.accept(createClassLookupItem(defClass, true)); - } + if (JavaCompletionContributor.ANNOTATION_NAME.accepts(insertedElement)) { + MultiMap annoMap = getAllAnnotationClasses(insertedElement, matcher); + Predicate processor = new LimitedAccessibleClassPreprocessor( + parameters, + filterByScope, + anno -> { + JavaPsiClassReferenceElement item = AllClassesGetter.createLookupItem(anno, JAVA_CLASS_INSERT_HANDLER); + item.addLookupStrings(getClassNameWithContainers(anno)); + consumer.accept(item); + return true; + } + ); + for (String name : matcher.sortMatching(annoMap.keySet())) { + if (!ContainerUtil.process(annoMap.get(name), processor)) { + break; + } + } + return; } - } - } - final boolean pkgContext = JavaCompletionUtil.inSomePackage(insertedElement); - AllClassesGetter.processJavaClasses(parameters, matcher, filterByScope, new Consumer() { - @Override - public void accept(PsiClass psiClass) { - processClass(psiClass, null, ""); - } + final ElementFilter filter = IN_EXTENDS_IMPLEMENTS.accepts(insertedElement) + ? new ExcludeDeclaredFilter(new ClassFilter(PsiClass.class)) + : IN_TYPE_PARAMETER.accepts(insertedElement) + ? new ExcludeDeclaredFilter(new ClassFilter(PsiTypeParameter.class)) + : TrueFilter.INSTANCE; - private void processClass(PsiClass psiClass, @Nullable Set visited, String prefix) { - boolean isInnerClass = StringUtil.isNotEmpty(prefix); - if (isInnerClass && isProcessedIndependently(psiClass)) { - return; + final boolean inJavaContext = parameters.getPosition() instanceof PsiIdentifier; + final boolean afterNew = AFTER_NEW.accepts(insertedElement); + if (afterNew) { + PsiExpression expr = PsiTreeUtil.getContextOfType(insertedElement, PsiExpression.class, true); + for (ExpectedTypeInfo info : ExpectedTypesProvider.getExpectedTypes(expr, true)) { + PsiType type = info.getType(); + PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (psiClass != null && psiClass.getName() != null) { + consumer.accept(createClassLookupItem(psiClass, inJavaContext)); + } + PsiType defaultType = info.getDefaultType(); + if (!defaultType.equals(type)) { + PsiClass defClass = PsiUtil.resolveClassInType(defaultType); + if (defClass != null && defClass.getName() != null) { + consumer.accept(createClassLookupItem(defClass, true)); + } + } + } } - if (filter.isAcceptable(psiClass, insertedElement)) { - if (!inJavaContext) { - JavaPsiClassReferenceElement element = AllClassesGetter.createLookupItem(psiClass, AllClassesGetter.TRY_SHORTENING); - element.setLookupString(prefix + element.getLookupString()); - consumer.accept(element); - } else { - Condition condition = eachClass -> filter.isAcceptable(eachClass, insertedElement) && AllClassesGetter.isAcceptableInContext(insertedElement, eachClass, - filterByScope, pkgContext); - for (JavaPsiClassReferenceElement element : createClassLookupItems(psiClass, afterNew, JAVA_CLASS_INSERT_HANDLER, condition)) { - element.setLookupString(prefix + element.getLookupString()); - JavaConstructorCallElement.wrap(element, insertedElement).forEach(consumer::accept); - } - } - } else { - String name = psiClass.getName(); - if (name != null) { - PsiClass[] innerClasses = psiClass.getInnerClasses(); - if (innerClasses.length > 0) { - if (visited == null) { - visited = new HashSet<>(); - } + final boolean pkgContext = JavaCompletionUtil.inSomePackage(insertedElement); + AllClassesGetter.processJavaClasses( + parameters, + matcher, + filterByScope, + new Consumer<>() { + @Override + @RequiredReadAction + public void accept(PsiClass psiClass) { + processClass(psiClass, null, ""); + } + + @RequiredReadAction + private void processClass(PsiClass psiClass, @Nullable Set visited, String prefix) { + boolean isInnerClass = StringUtil.isNotEmpty(prefix); + if (isInnerClass && isProcessedIndependently(psiClass)) { + return; + } + + if (filter.isAcceptable(psiClass, insertedElement)) { + if (!inJavaContext) { + JavaPsiClassReferenceElement element = + AllClassesGetter.createLookupItem(psiClass, AllClassesGetter.TRY_SHORTENING); + element.setLookupString(prefix + element.getLookupString()); + consumer.accept(element); + } + else { + Predicate condition = eachClass -> filter.isAcceptable(eachClass, insertedElement) + && AllClassesGetter.isAcceptableInContext(insertedElement, eachClass, filterByScope, pkgContext); + for (JavaPsiClassReferenceElement element + : createClassLookupItems(psiClass, afterNew, JAVA_CLASS_INSERT_HANDLER, condition)) { + element.setLookupString(prefix + element.getLookupString()); + JavaConstructorCallElement.wrap(element, insertedElement).forEach(consumer::accept); + } + } + } + else { + String name = psiClass.getName(); + if (name != null) { + PsiClass[] innerClasses = psiClass.getInnerClasses(); + if (innerClasses.length > 0) { + if (visited == null) { + visited = new HashSet<>(); + } - for (PsiClass innerClass : innerClasses) { - if (visited.add(innerClass)) { - processClass(innerClass, visited, prefix + name + "."); + for (PsiClass innerClass : innerClasses) { + if (visited.add(innerClass)) { + processClass(innerClass, visited, prefix + name + "."); + } + } + } + } + } + } + + @RequiredReadAction + private boolean isProcessedIndependently(PsiClass psiClass) { + String innerName = psiClass.getName(); + return innerName != null && matcher.prefixMatches(innerName); } - } } - } - } - } + ); + } - private boolean isProcessedIndependently(PsiClass psiClass) { - String innerName = psiClass.getName(); - return innerName != null && matcher.prefixMatches(innerName); - } - }); - } + @Nonnull + @RequiredReadAction + private static MultiMap getAllAnnotationClasses(PsiElement context, PrefixMatcher matcher) { + MultiMap map = new MultiMap<>(); + GlobalSearchScope scope = context.getResolveScope(); + PsiClass annotation = + JavaPsiFacade.getInstance(context.getProject()).findClass(JavaClassNames.JAVA_LANG_ANNOTATION_ANNOTATION, scope); + if (annotation != null) { + DirectClassInheritorsSearch.search(annotation, scope, false).forEach(psiClass -> { + if (!psiClass.isAnnotationType() || psiClass.getQualifiedName() == null) { + return true; + } - @Nonnull - private static MultiMap getAllAnnotationClasses(PsiElement context, PrefixMatcher matcher) { - MultiMap map = new MultiMap<>(); - GlobalSearchScope scope = context.getResolveScope(); - PsiClass annotation = JavaPsiFacade.getInstance(context.getProject()).findClass(JavaClassNames.JAVA_LANG_ANNOTATION_ANNOTATION, scope); - if (annotation != null) { - DirectClassInheritorsSearch.search(annotation, scope, false).forEach(psiClass -> - { - if (!psiClass.isAnnotationType() || psiClass.getQualifiedName() == null) { - return true; + String name = ObjectUtil.assertNotNull(psiClass.getName()); + if (!matcher.prefixMatches(name)) { + name = getClassNameWithContainers(psiClass); + if (!matcher.prefixMatches(name)) { + return true; + } + } + map.putValue(name, psiClass); + return true; + }); } + return map; + } + @Nonnull + @RequiredReadAction + private static String getClassNameWithContainers(@Nonnull PsiClass psiClass) { String name = ObjectUtil.assertNotNull(psiClass.getName()); - if (!matcher.prefixMatches(name)) { - name = getClassNameWithContainers(psiClass); - if (!matcher.prefixMatches(name)) { - return true; - } + for (PsiClass parent : JBIterable.generate(psiClass, PsiClass::getContainingClass)) { + name = parent.getName() + "." + name; } - map.putValue(name, psiClass); - return true; - }); + return name; } - return map; - } - @Nonnull - private static String getClassNameWithContainers(@Nonnull PsiClass psiClass) { - String name = ObjectUtil.assertNotNull(psiClass.getName()); - for (PsiClass parent : JBIterable.generate(psiClass, PsiClass::getContainingClass)) { - name = parent.getName() + "." + name; + static LookupElement highlightIfNeeded(JavaPsiClassReferenceElement element, CompletionParameters parameters) { + return JavaCompletionUtil.highlightIfNeeded(null, element, element.getObject(), parameters.getPosition()); } - return name; - } - - static LookupElement highlightIfNeeded(JavaPsiClassReferenceElement element, CompletionParameters parameters) { - return JavaCompletionUtil.highlightIfNeeded(null, element, element.getObject(), parameters.getPosition()); - } - - public static JavaPsiClassReferenceElement createClassLookupItem(final PsiClass psiClass, final boolean inJavaContext) { - return AllClassesGetter.createLookupItem(psiClass, inJavaContext ? JAVA_CLASS_INSERT_HANDLER : AllClassesGetter.TRY_SHORTENING); - } - public static List createClassLookupItems(final PsiClass psiClass, - boolean withInners, - InsertHandler insertHandler, - Condition condition) { - List result = new SmartList<>(); - if (condition.value(psiClass)) { - result.add(AllClassesGetter.createLookupItem(psiClass, insertHandler)); + public static JavaPsiClassReferenceElement createClassLookupItem(PsiClass psiClass, boolean inJavaContext) { + return AllClassesGetter.createLookupItem(psiClass, inJavaContext ? JAVA_CLASS_INSERT_HANDLER : AllClassesGetter.TRY_SHORTENING); } - String name = psiClass.getName(); - if (withInners && name != null) { - for (PsiClass inner : psiClass.getInnerClasses()) { - if (inner.hasModifierProperty(PsiModifier.STATIC)) { - for (JavaPsiClassReferenceElement lookupInner : createClassLookupItems(inner, true, insertHandler, condition)) { - String forced = lookupInner.getForcedPresentableName(); - String qualifiedName = name + "." + (forced != null ? forced : inner.getName()); - lookupInner.setForcedPresentableName(qualifiedName); - lookupInner.setLookupString(qualifiedName); - result.add(lookupInner); - } + + @RequiredReadAction + public static List createClassLookupItems( + PsiClass psiClass, + boolean withInners, + InsertHandler insertHandler, + Predicate condition + ) { + List result = new SmartList<>(); + if (condition.test(psiClass)) { + result.add(AllClassesGetter.createLookupItem(psiClass, insertHandler)); } - } + String name = psiClass.getName(); + if (withInners && name != null) { + for (PsiClass inner : psiClass.getInnerClasses()) { + if (inner.isStatic()) { + for (JavaPsiClassReferenceElement lookupInner : createClassLookupItems(inner, true, insertHandler, condition)) { + String forced = lookupInner.getForcedPresentableName(); + String qualifiedName = name + "." + (forced != null ? forced : inner.getName()); + lookupInner.setForcedPresentableName(qualifiedName); + lookupInner.setLookupString(qualifiedName); + result.add(lookupInner); + } + } + } + } + return result; } - return result; - } + @Override + @RequiredReadAction + public String handleEmptyLookup(@Nonnull CompletionParameters parameters, Editor editor) { + if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) { + return null; + } + + if (shouldShowSecondSmartCompletionHint(parameters)) { + return LanguageLocalize.completionNoSuggestions() + "; " + StringUtil.decapitalize(CompletionBundle.message( + "completion.class.name.hint.2", + getActionShortcut(IdeActions.ACTION_CODE_COMPLETION) + )); + } - @Override - public String handleEmptyLookup(@Nonnull final CompletionParameters parameters, final Editor editor) { - if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) { - return null; + return null; } - if (shouldShowSecondSmartCompletionHint(parameters)) { - return LangBundle.message("completion.no.suggestions") + "; " + StringUtil.decapitalize(CompletionBundle.message("completion.class.name.hint.2", getActionShortcut(IdeActions - .ACTION_CODE_COMPLETION))); + @RequiredReadAction + private static boolean shouldShowSecondSmartCompletionHint(CompletionParameters parameters) { + return parameters.getCompletionType() == CompletionType.BASIC + && parameters.getInvocationCount() == 2 + && parameters.getOriginalFile().getLanguage().isKindOf(JavaLanguage.INSTANCE); } - return null; - } - - private static boolean shouldShowSecondSmartCompletionHint(final CompletionParameters parameters) { - return parameters.getCompletionType() == CompletionType.BASIC && parameters.getInvocationCount() == 2 && parameters.getOriginalFile().getLanguage().isKindOf(JavaLanguage.INSTANCE); - } - - @Nonnull - @Override - public Language getLanguage() { - return JavaLanguage.INSTANCE; - } + @Nonnull + @Override + public Language getLanguage() { + return JavaLanguage.INSTANCE; + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaCompletionUtil.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaCompletionUtil.java index ead037d2bc..3a378ea180 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaCompletionUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaCompletionUtil.java @@ -23,12 +23,15 @@ import com.intellij.java.language.psi.codeStyle.VariableKind; import com.intellij.java.language.psi.util.*; import com.siyeh.ig.psiutils.SideEffectChecker; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.application.util.matcher.PrefixMatcher; import consulo.codeEditor.Editor; import consulo.codeEditor.action.TabOutScopesTracker; import consulo.document.Document; import consulo.document.FileDocumentManager; import consulo.document.RangeMarker; +import consulo.java.language.module.util.JavaClassNames; import consulo.language.codeStyle.CommonCodeStyleSettings; import consulo.language.codeStyle.PostprocessReformattingAspect; import consulo.language.editor.AutoPopupController; @@ -46,6 +49,7 @@ import consulo.language.util.IncorrectOperationException; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.style.StandardColors; import consulo.util.collection.ContainerUtil; import consulo.util.collection.JBIterable; @@ -53,976 +57,1064 @@ import consulo.util.dataholder.NullableLazyKey; import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.Condition; -import consulo.util.lang.function.Conditions; -import consulo.util.lang.function.PairFunction; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.function.Predicates; +import consulo.util.lang.ref.SimpleReference; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Predicate; import static com.intellij.java.impl.codeInsight.completion.ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch; import static com.intellij.java.impl.psi.util.proximity.ReferenceListWeigher.ReferenceListApplicability.inapplicable; import static consulo.language.pattern.PlatformPatterns.psiElement; public class JavaCompletionUtil { - public static final Key FORCE_SHOW_SIGNATURE_ATTR = Key.create("forceShowSignature"); - private static final Logger LOG = Logger.getInstance(JavaCompletionUtil.class); - public static final Key> DYNAMIC_TYPE_EVALUATOR = Key.create("DYNAMIC_TYPE_EVALUATOR"); - - private static final Key QUALIFIER_TYPE_ATTR = Key.create("qualifierType"); // SmartPsiElementPointer to PsiType of "qualifier" - static final NullableLazyKey EXPECTED_TYPES = NullableLazyKey.create("expectedTypes", - location -> { - if (PsiJavaPatterns.psiElement().beforeLeaf(PsiJavaPatterns.psiElement().withText(".")) - .accepts(location.getCompletionParameters().getPosition())) { - return ExpectedTypeInfo.EMPTY_ARRAY; + public static final Key FORCE_SHOW_SIGNATURE_ATTR = Key.create("forceShowSignature"); + private static final Logger LOG = Logger.getInstance(JavaCompletionUtil.class); + public static final Key> DYNAMIC_TYPE_EVALUATOR = + Key.create("DYNAMIC_TYPE_EVALUATOR"); + + private static final Key QUALIFIER_TYPE_ATTR = Key.create("qualifierType"); // SmartPsiElementPointer to PsiType of "qualifier" + static final NullableLazyKey EXPECTED_TYPES = NullableLazyKey.create( + "expectedTypes", + location -> { + if (PsiJavaPatterns.psiElement().beforeLeaf(PsiJavaPatterns.psiElement().withText(".")) + .accepts(location.getCompletionParameters().getPosition())) { + return ExpectedTypeInfo.EMPTY_ARRAY; + } + + return JavaSmartCompletionContributor.getExpectedTypes(location.getCompletionParameters()); } + ); - return JavaSmartCompletionContributor.getExpectedTypes(location.getCompletionParameters()); - }); + public static final Key SUPER_METHOD_PARAMETERS = Key.create("SUPER_METHOD_PARAMETERS"); - public static final Key SUPER_METHOD_PARAMETERS = Key.create("SUPER_METHOD_PARAMETERS"); + @Nullable + public static Set getExpectedTypes(CompletionParameters parameters) { + PsiExpression expr = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true); + if (expr != null) { + Set set = new HashSet<>(); + for (ExpectedTypeInfo expectedInfo : JavaSmartCompletionContributor.getExpectedTypes(parameters)) { + set.add(expectedInfo.getType()); + } + return set; + } + return null; + } + + private static final Key>> ALL_METHODS_ATTRIBUTE = Key.create("allMethods"); - @Nullable - public static Set getExpectedTypes(final CompletionParameters parameters) { - final PsiExpression expr = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true); - if (expr != null) { - final Set set = new HashSet<>(); - for (final ExpectedTypeInfo expectedInfo : JavaSmartCompletionContributor.getExpectedTypes(parameters)) { - set.add(expectedInfo.getType()); - } - return set; + public static PsiType getQualifierType(LookupElement item) { + return item.getUserData(QUALIFIER_TYPE_ATTR); } - return null; - } - private static final Key>> ALL_METHODS_ATTRIBUTE = Key.create("allMethods"); + public static void completeVariableNameForRefactoring( + Project project, + Set set, + String prefix, + PsiType varType, + VariableKind varKind + ) { + CamelHumpMatcher camelHumpMatcher = new CamelHumpMatcher(prefix); + JavaMemberNameCompletionContributor.completeVariableNameForRefactoring( + project, + set, + camelHumpMatcher, + varType, + varKind, + true, + false + ); + } - public static PsiType getQualifierType(LookupElement item) { - return item.getUserData(QUALIFIER_TYPE_ATTR); - } + public static void putAllMethods(LookupElement item, List methods) { + item.putUserData( + ALL_METHODS_ATTRIBUTE, + ContainerUtil.map(methods, method -> SmartPointerManager.getInstance(method.getProject()).createSmartPsiElementPointer(method)) + ); + } - public static void completeVariableNameForRefactoring(Project project, Set set, String prefix, PsiType varType, VariableKind varKind) { - final CamelHumpMatcher camelHumpMatcher = new CamelHumpMatcher(prefix); - JavaMemberNameCompletionContributor.completeVariableNameForRefactoring(project, set, camelHumpMatcher, varType, varKind, true, false); - } + public static List getAllMethods(LookupElement item) { + List> pointers = item.getUserData(ALL_METHODS_ATTRIBUTE); + if (pointers == null) { + return null; + } - public static void putAllMethods(LookupElement item, List methods) { - item.putUserData(ALL_METHODS_ATTRIBUTE, ContainerUtil.map(methods, method -> SmartPointerManager.getInstance(method.getProject()).createSmartPsiElementPointer(method))); - } + return ContainerUtil.mapNotNull(pointers, pointer -> pointer.getElement()); + } - public static List getAllMethods(LookupElement item) { - List> pointers = item.getUserData(ALL_METHODS_ATTRIBUTE); - if (pointers == null) { - return null; + public static String[] completeVariableNameForRefactoring( + JavaCodeStyleManager codeStyleManager, + @Nullable PsiType varType, + VariableKind varKind, + SuggestedNameInfo suggestedNameInfo + ) { + return JavaMemberNameCompletionContributor.completeVariableNameForRefactoring( + codeStyleManager, + new CamelHumpMatcher(""), + varType, + varKind, + suggestedNameInfo, + true, + false + ); } - return ContainerUtil.mapNotNull(pointers, pointer -> pointer.getElement()); - } + public static boolean isInExcludedPackage(@Nonnull PsiMember member, boolean allowInstanceInnerClasses) { + String name = PsiUtil.getMemberQualifiedName(member); + if (name == null) { + return false; + } - public static String[] completeVariableNameForRefactoring(JavaCodeStyleManager codeStyleManager, @Nullable final PsiType varType, - final VariableKind varKind, - SuggestedNameInfo suggestedNameInfo) { - return JavaMemberNameCompletionContributor - .completeVariableNameForRefactoring(codeStyleManager, new CamelHumpMatcher(""), varType, varKind, suggestedNameInfo, true, false); - } + if (!member.isStatic()) { + if (member instanceof PsiMethod || member instanceof PsiField) { + return false; + } + if (allowInstanceInnerClasses && member instanceof PsiClass && member.getContainingClass() != null) { + return false; + } + } - public static boolean isInExcludedPackage(@Nonnull final PsiMember member, boolean allowInstanceInnerClasses) { - final String name = PsiUtil.getMemberQualifiedName(member); - if (name == null) { - return false; + return JavaProjectCodeInsightSettings.getSettings(member.getProject()).isExcluded(name); } - if (!member.hasModifierProperty(PsiModifier.STATIC)) { - if (member instanceof PsiMethod || member instanceof PsiField) { - return false; - } - if (allowInstanceInnerClasses && member instanceof PsiClass && member.getContainingClass() != null) { - return false; - } + @Nonnull + public static T originalize(@Nonnull T type) { + if (!type.isValid()) { + return type; + } + + T result = new PsiTypeMapper() { + private final Set myVisited = ContainerUtil.newIdentityTroveSet(); + + @Override + public PsiType visitClassType(PsiClassType classType) { + if (!myVisited.add(classType)) { + return classType; + } + + PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics(); + PsiClass psiClass = classResolveResult.getElement(); + PsiSubstitutor substitutor = classResolveResult.getSubstitutor(); + if (psiClass == null) { + return classType; + } + + return new PsiImmediateClassType(CompletionUtilCore.getOriginalOrSelf(psiClass), originalizeSubstitutor(substitutor)); + } + + private PsiSubstitutor originalizeSubstitutor(PsiSubstitutor substitutor) { + PsiSubstitutor originalSubstitutor = PsiSubstitutor.EMPTY; + for (Map.Entry entry : substitutor.getSubstitutionMap().entrySet()) { + PsiType value = entry.getValue(); + originalSubstitutor = originalSubstitutor.put( + CompletionUtilCore.getOriginalOrSelf(entry.getKey()), + value == null ? null : mapType(value) + ); + } + return originalSubstitutor; + } + + + @Override + public PsiType visitType(PsiType type) { + return type; + } + }.mapType(type); + if (result == null) { + throw new AssertionError("Null result for type " + type + " of class " + type.getClass()); + } + return result; } - return JavaProjectCodeInsightSettings.getSettings(member.getProject()).isExcluded(name); - } + @Nullable + public static List getAllPsiElements(LookupElement item) { + List allMethods = getAllMethods(item); + if (allMethods != null) { + return allMethods; + } + return item.getObject() instanceof PsiElement element ? Collections.singletonList(element) : null; + } - @Nonnull - public static T originalize(@Nonnull T type) { - if (!type.isValid()) { - return type; + @Nullable + public static PsiType getLookupElementType(LookupElement element) { + TypedLookupItem typed = element.as(TypedLookupItem.CLASS_CONDITION_KEY); + return typed != null ? typed.getType() : null; } - T result = new PsiTypeMapper() { - private final Set myVisited = ContainerUtil.newIdentityTroveSet(); + @Nullable + public static PsiType getQualifiedMemberReferenceType(@Nullable PsiType qualifierType, @Nonnull final PsiMember member) { + final SimpleReference subst = SimpleReference.create(PsiSubstitutor.EMPTY); + class MyProcessor implements PsiScopeProcessor, NameHint, ElementClassHint { + @Override + public boolean execute(@Nonnull PsiElement element, @Nonnull ResolveState state) { + if (element == member) { + subst.set(state.get(PsiSubstitutor.KEY)); + } + return true; + } + + @Override + public void handleEvent(Event event, @Nullable Object associated) { - @Override - public PsiType visitClassType(final PsiClassType classType) { - if (!myVisited.add(classType)) { - return classType; + } + + @Override + public String getName(@Nonnull ResolveState state) { + return member.getName(); + } + + @Override + public boolean shouldProcess(@Nonnull DeclarationKind kind) { + return member instanceof PsiEnumConstant ? kind == DeclarationKind.ENUM_CONST : + member instanceof PsiField ? kind == DeclarationKind.FIELD : + kind == DeclarationKind.METHOD; + } + + @Override + public T getHint(@Nonnull Key hintKey) { + //noinspection unchecked + return hintKey == NameHint.KEY || hintKey == ElementClassHint.KEY ? (T)this : null; + } + } + + PsiScopesUtil.processTypeDeclarations(qualifierType, member, new MyProcessor()); + + PsiType rawType = member instanceof PsiField field ? field.getType() : + member instanceof PsiMethod method ? method.getReturnType() : + JavaPsiFacade.getElementFactory(member.getProject()).createType((PsiClass)member); + return subst.get().substitute(rawType); + } + + @RequiredReadAction + public static Set processJavaReference( + PsiElement element, + PsiJavaReference javaReference, + ElementFilter elementFilter, + JavaCompletionProcessor.Options options, + PrefixMatcher matcher, + CompletionParameters parameters + ) { + if (element.getContext() instanceof PsiReferenceExpression refExpr + && refExpr.getQualifierExpression() instanceof PsiReferenceExpression qRefExpr + && qRefExpr.resolve() instanceof PsiParameter parameter + && parameter.getType() instanceof PsiLambdaParameterType) { + PsiLambdaExpression lambdaExpression = (PsiLambdaExpression)parameter.getDeclarationScope(); + if (PsiTypesUtil.getExpectedTypeByParent(lambdaExpression) == null) { + int parameterIndex = lambdaExpression.getParameterList().getParameterIndex(parameter); + Set set = new LinkedHashSet<>(); + boolean overloadsFound = LambdaUtil.processParentOverloads( + lambdaExpression, + functionalInterfaceType -> { + PsiType qualifierType = LambdaUtil.getLambdaParameterFromType(functionalInterfaceType, parameterIndex); + if (qualifierType instanceof PsiWildcardType wildcardType) { + qualifierType = wildcardType.getBound(); + } + if (qualifierType == null) { + return; + } + + PsiReferenceExpression fakeRef = + createReference("xxx.xxx", createContextWithXxxVariable(element, qualifierType)); + set.addAll(processJavaQualifiedReference( + fakeRef.getReferenceNameElement(), + fakeRef, + elementFilter, + options, + matcher, + parameters + )); + } + ); + if (overloadsFound) { + return set; + } + } } + return processJavaQualifiedReference(element, javaReference, elementFilter, options, matcher, parameters); + } - final PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics(); - final PsiClass psiClass = classResolveResult.getElement(); - final PsiSubstitutor substitutor = classResolveResult.getSubstitutor(); - if (psiClass == null) { - return classType; + @RequiredReadAction + private static Set processJavaQualifiedReference( + PsiElement element, + PsiJavaReference javaReference, + ElementFilter elementFilter, + JavaCompletionProcessor.Options options, + PrefixMatcher matcher, + CompletionParameters parameters + ) { + Set set = new LinkedHashSet<>(); + Predicate nameCondition = matcher::prefixMatches; + + JavaCompletionProcessor processor = new JavaCompletionProcessor(element, elementFilter, options, nameCondition); + PsiType plainQualifier = processor.getQualifierType(); + + List runtimeQualifiers = getQualifierCastTypes(javaReference, parameters); + if (!runtimeQualifiers.isEmpty()) { + PsiType[] conjuncts = JBIterable.of(plainQualifier).append(runtimeQualifiers).toList().toArray(PsiType.EMPTY_ARRAY); + PsiType composite = PsiIntersectionType.createIntersection(false, conjuncts); + PsiElement ctx = createContextWithXxxVariable(element, composite); + javaReference = createReference("xxx.xxx", ctx); + processor.setQualifierType(composite); } - return new PsiImmediateClassType(CompletionUtilCore.getOriginalOrSelf(psiClass), originalizeSubstitutor(substitutor)); - } + javaReference.processVariants(processor); + + List castItems = ContainerUtil.map(runtimeQualifiers, q -> PsiTypeLookupItem.createLookupItem(q, element)); + + boolean pkgContext = inSomePackage(element); + + PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(plainQualifier); + boolean honorExcludes = qualifierClass == null || !isInExcludedPackage(qualifierClass, false); - private PsiSubstitutor originalizeSubstitutor(final PsiSubstitutor substitutor) { - PsiSubstitutor originalSubstitutor = PsiSubstitutor.EMPTY; - for (final Map.Entry entry : substitutor.getSubstitutionMap().entrySet()) { - final PsiType value = entry.getValue(); - originalSubstitutor = originalSubstitutor.put(CompletionUtilCore.getOriginalOrSelf(entry.getKey()), - value == null ? null : mapType(value)); + Set expectedTypes = ObjectUtil.coalesce(getExpectedTypes(parameters), Collections.emptySet()); + + Set mentioned = new HashSet<>(); + for (CompletionElement completionElement : processor.getResults()) { + for (LookupElement item : createLookupElements(completionElement, javaReference)) { + item.putUserData(QUALIFIER_TYPE_ATTR, plainQualifier); + Object o = item.getObject(); + if (o instanceof PsiClass psiClass && !isSourceLevelAccessible(element, psiClass, pkgContext)) { + continue; + } + if (o instanceof PsiMember member) { + if (honorExcludes && isInExcludedPackage(member, true)) { + continue; + } + mentioned.add(CompletionUtilCore.getOriginalOrSelf(member)); + } + PsiTypeLookupItem qualifierCast = findQualifierCast(item, castItems, plainQualifier, processor, expectedTypes); + if (qualifierCast != null) { + item = castQualifier(item, qualifierCast); + } + set.add(highlightIfNeeded(qualifierCast != null ? qualifierCast.getType() : plainQualifier, item, o, element)); + } } - return originalSubstitutor; - } + if (javaReference instanceof PsiJavaCodeReferenceElement javaCodeRef) { + PsiElement refQualifier = javaCodeRef.getQualifier(); + if (refQualifier == null + && PsiTreeUtil.getParentOfType(element, PsiPackageStatement.class, PsiImportStatementBase.class) == null) { + StaticMemberProcessor memberProcessor = new JavaStaticMemberProcessor(parameters); + memberProcessor.processMembersOfRegisteredClasses(matcher, (member, psiClass) -> { + if (!mentioned.contains(member) && processor.satisfies(member, ResolveState.initial())) { + ContainerUtil.addIfNotNull(set, memberProcessor.createLookupElement(member, psiClass, true)); + } + }); + } + else if (refQualifier instanceof PsiSuperExpression superExpr && superExpr.getQualifier() == null) { + set.addAll(SuperCalls.suggestQualifyingSuperCalls(element, javaReference, elementFilter, options, nameCondition)); + } + } - @Override - public PsiType visitType(PsiType type) { - return type; - } - }.mapType(type); - if (result == null) { - throw new AssertionError("Null result for type " + type + " of class " + type.getClass()); - } - return result; - } - - @Nullable - public static List getAllPsiElements(final LookupElement item) { - List allMethods = getAllMethods(item); - if (allMethods != null) { - return allMethods; + return set; } - if (item.getObject() instanceof PsiElement) { - return Collections.singletonList((PsiElement) item.getObject()); + + @Nonnull + static PsiReferenceExpression createReference(@Nonnull String text, @Nonnull PsiElement context) { + return (PsiReferenceExpression)JavaPsiFacade.getElementFactory(context.getProject()).createExpressionFromText(text, context); } - return null; - } - - @Nullable - public static PsiType getLookupElementType(final LookupElement element) { - TypedLookupItem typed = element.as(TypedLookupItem.CLASS_CONDITION_KEY); - return typed != null ? typed.getType() : null; - } - - @Nullable - public static PsiType getQualifiedMemberReferenceType(@Nullable PsiType qualifierType, @Nonnull final PsiMember member) { - final Ref subst = Ref.create(PsiSubstitutor.EMPTY); - class MyProcessor implements PsiScopeProcessor, NameHint, ElementClassHint { - @Override - public boolean execute(@Nonnull PsiElement element, @Nonnull ResolveState state) { - if (element == member) { - subst.set(state.get(PsiSubstitutor.KEY)); + + @Nonnull + private static List getQualifierCastTypes(PsiJavaReference javaReference, CompletionParameters parameters) { + if (javaReference instanceof PsiReferenceExpression refExpr) { + PsiExpression qualifier = refExpr.getQualifierExpression(); + if (qualifier != null) { + Project project = qualifier.getProject(); + BiFunction evaluator = + refExpr.getContainingFile().getCopyableUserData(DYNAMIC_TYPE_EVALUATOR); + if (evaluator != null) { + PsiType type = evaluator.apply(qualifier, parameters); + if (type != null) { + return Collections.singletonList(type); + } + } + + return GuessManager.getInstance(project) + .getControlFlowExpressionTypeConjuncts(qualifier, parameters.getInvocationCount() > 1); + } } - return true; - } - - @Override - public void handleEvent(Event event, @Nullable Object associated) { - - } - - @Override - public String getName(@Nonnull ResolveState state) { - return member.getName(); - } - - @Override - public boolean shouldProcess(@Nonnull DeclarationKind kind) { - return member instanceof PsiEnumConstant ? kind == DeclarationKind.ENUM_CONST : - member instanceof PsiField ? kind == DeclarationKind.FIELD : - kind == DeclarationKind.METHOD; - } - - @Override - public T getHint(@Nonnull Key hintKey) { - //noinspection unchecked - return hintKey == NameHint.KEY || hintKey == ElementClassHint.KEY ? (T) this : null; - } + return Collections.emptyList(); } - PsiScopesUtil.processTypeDeclarations(qualifierType, member, new MyProcessor()); - - PsiType rawType = member instanceof PsiField ? ((PsiField) member).getType() : - member instanceof PsiMethod ? ((PsiMethod) member).getReturnType() : - JavaPsiFacade.getElementFactory(member.getProject()).createType((PsiClass) member); - return subst.get().substitute(rawType); - } - - public static Set processJavaReference(final PsiElement element, - final PsiJavaReference javaReference, - final ElementFilter elementFilter, - final JavaCompletionProcessor.Options options, - final PrefixMatcher matcher, - final CompletionParameters parameters) { - PsiElement elementParent = element.getContext(); - if (elementParent instanceof PsiReferenceExpression) { - final PsiExpression qualifierExpression = ((PsiReferenceExpression) elementParent).getQualifierExpression(); - if (qualifierExpression instanceof PsiReferenceExpression) { - final PsiElement resolve = ((PsiReferenceExpression) qualifierExpression).resolve(); - if (resolve instanceof PsiParameter) { - final PsiElement declarationScope = ((PsiParameter) resolve).getDeclarationScope(); - if (((PsiParameter) resolve).getType() instanceof PsiLambdaParameterType) { - final PsiLambdaExpression lambdaExpression = (PsiLambdaExpression) declarationScope; - if (PsiTypesUtil.getExpectedTypeByParent(lambdaExpression) == null) { - final int parameterIndex = lambdaExpression.getParameterList().getParameterIndex((PsiParameter) resolve); - final Set set = new LinkedHashSet<>(); - final boolean overloadsFound = LambdaUtil.processParentOverloads(lambdaExpression, functionalInterfaceType -> { - PsiType qualifierType = LambdaUtil.getLambdaParameterFromType(functionalInterfaceType, parameterIndex); - if (qualifierType instanceof PsiWildcardType) { - qualifierType = ((PsiWildcardType) qualifierType).getBound(); + private static boolean shouldCast( + @Nonnull LookupElement item, + @Nonnull PsiTypeLookupItem castTypeItem, + @Nullable PsiType plainQualifier, + @Nonnull JavaCompletionProcessor processor, + @Nonnull Set expectedTypes + ) { + PsiType castType = castTypeItem.getType(); + if (plainQualifier == null) { + return false; + } + Object o = item.getObject(); + if (o instanceof PsiMethod method + && plainQualifier instanceof PsiClassType qClassType + && castType instanceof PsiClassType castClassType) { + PsiClassType.ClassResolveResult plainResult = qClassType.resolveGenerics(); + PsiClass plainClass = plainResult.getElement(); + HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature(); + PsiMethod plainMethod = plainClass == null + ? null + : StreamEx.ofTree(signature, s -> StreamEx.of(s.getSuperSignatures())) + .map(sig -> MethodSignatureUtil.findMethodBySignature(plainClass, sig, true)) + .filter(Objects::nonNull) + .findFirst().orElse(null); + if (plainMethod != null) { + PsiClassType.ClassResolveResult castResult = castClassType.resolveGenerics(); + PsiClass castClass = castResult.getElement(); + + if (castClass == null || !castClass.isInheritor(plainClass, true)) { + return false; } - if (qualifierType == null) { - return; + + if (!processor.isAccessible(plainMethod)) { + return true; } - PsiReferenceExpression fakeRef = createReference("xxx.xxx", createContextWithXxxVariable(element, qualifierType)); - set.addAll(processJavaQualifiedReference(fakeRef.getReferenceNameElement(), fakeRef, elementFilter, options, matcher, parameters)); - }); - if (overloadsFound) { - return set; - } + PsiSubstitutor castSub = TypeConversionUtil.getSuperClassSubstitutor(plainClass, castClassType); + PsiType typeAfterCast = toRaw(castSub.substitute(method.getReturnType())); + PsiType typeDeclared = toRaw(plainResult.getSubstitutor().substitute(plainMethod.getReturnType())); + return typeAfterCast != null && typeDeclared != null + && !typeAfterCast.equals(typeDeclared) + && expectedTypes.stream().anyMatch(et -> et.isAssignableFrom(typeAfterCast) && !et.isAssignableFrom(typeDeclared)); } - } } - } - } - return processJavaQualifiedReference(element, javaReference, elementFilter, options, matcher, parameters); - } - - private static Set processJavaQualifiedReference(PsiElement element, PsiJavaReference javaReference, ElementFilter elementFilter, - JavaCompletionProcessor.Options options, - final PrefixMatcher matcher, CompletionParameters parameters) { - final Set set = new LinkedHashSet<>(); - final Condition nameCondition = matcher::prefixMatches; - - final JavaCompletionProcessor processor = new JavaCompletionProcessor(element, elementFilter, options, nameCondition); - final PsiType plainQualifier = processor.getQualifierType(); - - List runtimeQualifiers = getQualifierCastTypes(javaReference, parameters); - if (!runtimeQualifiers.isEmpty()) { - PsiType[] conjuncts = JBIterable.of(plainQualifier).append(runtimeQualifiers).toList().toArray(PsiType.EMPTY_ARRAY); - PsiType composite = PsiIntersectionType.createIntersection(false, conjuncts); - PsiElement ctx = createContextWithXxxVariable(element, composite); - javaReference = createReference("xxx.xxx", ctx); - processor.setQualifierType(composite); + + return containsMember(castType, o, true) && !containsMember(plainQualifier, o, true); } - javaReference.processVariants(processor); + @Nonnull + private static LookupElement castQualifier(@Nonnull LookupElement item, @Nonnull PsiTypeLookupItem castTypeItem) { + return new LookupElementDecorator<>(item) { + @Override + @RequiredReadAction + public void handleInsert(@Nonnull InsertionContext context) { + Document document = context.getEditor().getDocument(); + context.commitDocument(); + PsiFile file = context.getFile(); + PsiJavaCodeReferenceElement ref = + PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset(), PsiJavaCodeReferenceElement.class, false); + if (ref != null) { + PsiElement qualifier = ref.getQualifier(); + if (qualifier != null) { + CommonCodeStyleSettings settings = CompletionStyleUtil.getCodeStyleSettings(context); + + String parenSpace = settings.SPACE_WITHIN_PARENTHESES ? " " : ""; + document.insertString(qualifier.getTextRange().getEndOffset(), parenSpace + ")"); + + String spaceWithin = settings.SPACE_WITHIN_CAST_PARENTHESES ? " " : ""; + String prefix = "(" + parenSpace + "(" + spaceWithin; + String spaceAfter = settings.SPACE_AFTER_TYPE_CAST ? " " : ""; + int exprStart = qualifier.getTextRange().getStartOffset(); + document.insertString(exprStart, prefix + spaceWithin + ")" + spaceAfter); + + CompletionUtilCore.emulateInsertion(context, exprStart + prefix.length(), castTypeItem); + PsiDocumentManager.getInstance(file.getProject()).doPostponedOperationsAndUnblockDocument(document); + context.getEditor().getCaretModel().moveToOffset(context.getTailOffset()); + } + } - List castItems = ContainerUtil.map(runtimeQualifiers, q -> PsiTypeLookupItem.createLookupItem(q, element)); + super.handleInsert(context); + } - final boolean pkgContext = inSomePackage(element); + @Override + public void renderElement(LookupElementPresentation presentation) { + super.renderElement(presentation); - PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(plainQualifier); - final boolean honorExcludes = qualifierClass == null || !isInExcludedPackage(qualifierClass, false); + presentation.appendTailText(" on " + castTypeItem.getType().getPresentableText(), true); + } + }; + } - Set expectedTypes = ObjectUtil.coalesce(getExpectedTypes(parameters), Collections.emptySet()); + private static PsiTypeLookupItem findQualifierCast( + @Nonnull LookupElement item, + @Nonnull List castTypeItems, + @Nullable PsiType plainQualifier, + JavaCompletionProcessor processor, + Set expectedTypes + ) { + return ContainerUtil.find(castTypeItems, c -> shouldCast(item, c, plainQualifier, processor, expectedTypes)); + } - final Set mentioned = new HashSet<>(); - for (CompletionElement completionElement : processor.getResults()) { - for (LookupElement item : createLookupElements(completionElement, javaReference)) { - item.putUserData(QUALIFIER_TYPE_ATTR, plainQualifier); - final Object o = item.getObject(); - if (o instanceof PsiClass && !isSourceLevelAccessible(element, (PsiClass) o, pkgContext)) { - continue; - } - if (o instanceof PsiMember) { - if (honorExcludes && isInExcludedPackage((PsiMember) o, true)) { - continue; - } - mentioned.add(CompletionUtilCore.getOriginalOrSelf((PsiMember) o)); + @Nullable + private static PsiType toRaw(@Nullable PsiType type) { + return type instanceof PsiClassType classType ? classType.rawType() : type; + } + + @Nonnull + @RequiredReadAction + public static LookupElement highlightIfNeeded( + @Nullable PsiType qualifierType, + @Nonnull LookupElement item, + @Nonnull Object object, + @Nonnull PsiElement place + ) { + if (shouldMarkRed(object, place)) { + return PrioritizedLookupElement.withExplicitProximity( + LookupElementDecorator.withRenderer( + item, + new LookupElementRenderer<>() { + @Override + public void renderElement(LookupElementDecorator element, LookupElementPresentation presentation) { + element.getDelegate().renderElement(presentation); + presentation.setItemTextForeground(StandardColors.RED); + } + } + ), + -1 + ); } - PsiTypeLookupItem qualifierCast = findQualifierCast(item, castItems, plainQualifier, processor, expectedTypes); - if (qualifierCast != null) { - item = castQualifier(item, qualifierCast); + if (containsMember(qualifierType, object, false) && !qualifierType.equalsToText(JavaClassNames.JAVA_LANG_OBJECT)) { + LookupElementDecorator bold = LookupElementDecorator.withRenderer( + item, + new LookupElementRenderer<>() { + @Override + public void renderElement(LookupElementDecorator element, LookupElementPresentation presentation) { + element.getDelegate().renderElement(presentation); + presentation.setItemTextBold(true); + } + } + ); + return object instanceof PsiField ? bold : PrioritizedLookupElement.withExplicitProximity(bold, 1); } - set.add(highlightIfNeeded(qualifierCast != null ? qualifierCast.getType() : plainQualifier, item, o, element)); - } + return item; } - if (javaReference instanceof PsiJavaCodeReferenceElement) { - PsiElement refQualifier = ((PsiJavaCodeReferenceElement) javaReference).getQualifier(); - if (refQualifier == null && PsiTreeUtil.getParentOfType(element, PsiPackageStatement.class, PsiImportStatementBase.class) == null) { - final StaticMemberProcessor memberProcessor = new JavaStaticMemberProcessor(parameters); - memberProcessor.processMembersOfRegisteredClasses(matcher, (member, psiClass) -> { - if (!mentioned.contains(member) && processor.satisfies(member, ResolveState.initial())) { - ContainerUtil.addIfNotNull(set, memberProcessor.createLookupElement(member, psiClass, true)); - } - }); - } else if (refQualifier instanceof PsiSuperExpression && ((PsiSuperExpression) refQualifier).getQualifier() == null) { - set.addAll(SuperCalls.suggestQualifyingSuperCalls(element, javaReference, elementFilter, options, nameCondition)); - } + @RequiredReadAction + private static boolean shouldMarkRed(@Nonnull Object object, @Nonnull PsiElement place) { + if (!(object instanceof PsiMember member)) { + return false; + } + if (Java15APIUsageInspection.getLastIncompatibleLanguageLevel(member, PsiUtil.getLanguageLevel(place)) != null) { + return true; + } + + if (object instanceof PsiEnumConstant enumConst) { + return findConstantsUsedInSwitch(place).contains(CompletionUtilCore.getOriginalOrSelf(enumConst)); + } + return object instanceof PsiClass psiClass && ReferenceListWeigher.INSTANCE.getApplicability(psiClass, place) == inapplicable; } - return set; - } - - @Nonnull - static PsiReferenceExpression createReference(@Nonnull String text, @Nonnull PsiElement context) { - return (PsiReferenceExpression) JavaPsiFacade.getElementFactory(context.getProject()).createExpressionFromText(text, context); - } - - @Nonnull - private static List getQualifierCastTypes(PsiJavaReference javaReference, CompletionParameters parameters) { - if (javaReference instanceof PsiReferenceExpression) { - final PsiReferenceExpression refExpr = (PsiReferenceExpression) javaReference; - final PsiExpression qualifier = refExpr.getQualifierExpression(); - if (qualifier != null) { - final Project project = qualifier.getProject(); - PairFunction evaluator = refExpr.getContainingFile().getCopyableUserData(DYNAMIC_TYPE_EVALUATOR); - if (evaluator != null) { - PsiType type = evaluator.fun(qualifier, parameters); - if (type != null) { - return Collections.singletonList(type); - } + @Contract("null, _, _ -> false") + private static boolean containsMember(@Nullable PsiType qualifierType, @Nonnull Object object, boolean checkBases) { + if (!(object instanceof PsiMember member)) { + return false; } - return GuessManager.getInstance(project).getControlFlowExpressionTypeConjuncts(qualifier, parameters.getInvocationCount() > 1); - } + if (qualifierType instanceof PsiArrayType) { //length and clone() + PsiFile file = member.getContainingFile(); + if (file == null || file.getVirtualFile() == null) { //yes, they're a bit dummy + return true; + } + } + else if (qualifierType instanceof PsiClassType classType) { + PsiClass qualifierClass = classType.resolve(); + if (qualifierClass == null) { + return false; + } + if (object instanceof PsiMethod method && qualifierClass.findMethodBySignature(method, checkBases) != null) { + return true; + } + PsiClass memberClass = member.getContainingClass(); + return checkBases + ? InheritanceUtil.isInheritorOrSelf(qualifierClass, memberClass, true) + : qualifierClass.equals(memberClass); + } + return false; } - return Collections.emptyList(); - } - - private static boolean shouldCast(@Nonnull LookupElement item, - @Nonnull PsiTypeLookupItem castTypeItem, - @Nullable PsiType plainQualifier, - @Nonnull JavaCompletionProcessor processor, - @Nonnull Set expectedTypes) { - PsiType castType = castTypeItem.getType(); - if (plainQualifier != null) { - Object o = item.getObject(); - if (o instanceof PsiMethod) { - if (plainQualifier instanceof PsiClassType && castType instanceof PsiClassType) { - PsiMethod method = (PsiMethod) o; - PsiClassType.ClassResolveResult plainResult = ((PsiClassType) plainQualifier).resolveGenerics(); - PsiClass plainClass = plainResult.getElement(); - HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature(); - PsiMethod plainMethod = plainClass == null ? null : - StreamEx.ofTree(signature, s -> StreamEx.of(s.getSuperSignatures())) - .map(sig -> MethodSignatureUtil.findMethodBySignature(plainClass, sig, true)) - .filter(Objects::nonNull) - .findFirst().orElse(null); - if (plainMethod != null) { - PsiClassType.ClassResolveResult castResult = ((PsiClassType) castType).resolveGenerics(); - PsiClass castClass = castResult.getElement(); - - if (castClass == null || !castClass.isInheritor(plainClass, true)) { - return false; + + @RequiredReadAction + static Iterable createLookupElements(CompletionElement completionElement, PsiJavaReference reference) { + Object completion = completionElement.getElement(); + assert !(completion instanceof LookupElement); + + if (reference instanceof PsiJavaCodeReferenceElement javaCodeRef) { + if (completion instanceof PsiMethod method && javaCodeRef.getParent() instanceof PsiImportStaticStatement) { + return Collections.singletonList(JavaLookupElementBuilder.forMethod(method, PsiSubstitutor.EMPTY)); } - if (!processor.isAccessible(plainMethod)) { - return true; + if (completion instanceof PsiClass psiClass) { + List classItems = JavaClassNameCompletionContributor.createClassLookupItems( + CompletionUtilCore.getOriginalOrSelf(psiClass), + JavaClassNameCompletionContributor.AFTER_NEW.accepts(reference), + JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER, + Predicates.alwaysTrue() + ); + return JBIterable.from(classItems).flatMap(i -> JavaConstructorCallElement.wrap(i, reference.getElement())); } + } - PsiSubstitutor castSub = TypeConversionUtil.getSuperClassSubstitutor(plainClass, (PsiClassType) castType); - PsiType typeAfterCast = toRaw(castSub.substitute(method.getReturnType())); - PsiType typeDeclared = toRaw(plainResult.getSubstitutor().substitute(plainMethod.getReturnType())); - return typeAfterCast != null && typeDeclared != null && - !typeAfterCast.equals(typeDeclared) && - expectedTypes.stream().anyMatch(et -> et.isAssignableFrom(typeAfterCast) && !et.isAssignableFrom(typeDeclared)); - } + if (reference instanceof PsiMethodReferenceExpression && completion instanceof PsiMethod method && method.isConstructor()) { + return Collections.singletonList(JavaLookupElementBuilder.forMethod(method, "new", PsiSubstitutor.EMPTY, null)); } - } - return containsMember(castType, o, true) && !containsMember(plainQualifier, o, true); - } - return false; - } - - @Nonnull - private static LookupElement castQualifier(@Nonnull LookupElement item, @Nonnull PsiTypeLookupItem castTypeItem) { - return new LookupElementDecorator(item) { - @Override - public void handleInsert(@Nonnull InsertionContext context) { - final Document document = context.getEditor().getDocument(); - context.commitDocument(); - final PsiFile file = context.getFile(); - final PsiJavaCodeReferenceElement ref = - PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset(), PsiJavaCodeReferenceElement.class, false); - if (ref != null) { - final PsiElement qualifier = ref.getQualifier(); - if (qualifier != null) { - final CommonCodeStyleSettings settings = CompletionStyleUtil.getCodeStyleSettings(context); - - final String parenSpace = settings.SPACE_WITHIN_PARENTHESES ? " " : ""; - document.insertString(qualifier.getTextRange().getEndOffset(), parenSpace + ")"); - - final String spaceWithin = settings.SPACE_WITHIN_CAST_PARENTHESES ? " " : ""; - final String prefix = "(" + parenSpace + "(" + spaceWithin; - final String spaceAfter = settings.SPACE_AFTER_TYPE_CAST ? " " : ""; - final int exprStart = qualifier.getTextRange().getStartOffset(); - document.insertString(exprStart, prefix + spaceWithin + ")" + spaceAfter); - - CompletionUtilCore.emulateInsertion(context, exprStart + prefix.length(), castTypeItem); - PsiDocumentManager.getInstance(file.getProject()).doPostponedOperationsAndUnblockDocument(document); - context.getEditor().getCaretModel().moveToOffset(context.getTailOffset()); - } + PsiSubstitutor substitutor = completionElement.getSubstitutor(); + if (substitutor == null) { + substitutor = PsiSubstitutor.EMPTY; + } + if (completion instanceof PsiClass psiClass) { + JavaPsiClassReferenceElement classItem = + JavaClassNameCompletionContributor.createClassLookupItem(psiClass, true).setSubstitutor(substitutor); + return JavaConstructorCallElement.wrap(classItem, reference.getElement()); } + if (completion instanceof PsiMethod method) { + if (reference instanceof PsiMethodReferenceExpression methodRefExpr) { + return Collections.singleton(new JavaMethodReferenceElement(method, methodRefExpr)); + } - super.handleInsert(context); - } - - @Override - public void renderElement(LookupElementPresentation presentation) { - super.renderElement(presentation); - - presentation.appendTailText(" on " + castTypeItem.getType().getPresentableText(), true); - } - }; - } - - private static PsiTypeLookupItem findQualifierCast(@Nonnull LookupElement item, - @Nonnull List castTypeItems, - @Nullable PsiType plainQualifier, JavaCompletionProcessor processor, Set expectedTypes) { - return ContainerUtil.find(castTypeItems, c -> shouldCast(item, c, plainQualifier, processor, expectedTypes)); - } - - @Nullable - private static PsiType toRaw(@Nullable PsiType type) { - return type instanceof PsiClassType ? ((PsiClassType) type).rawType() : type; - } - - @Nonnull - public static LookupElement highlightIfNeeded(@Nullable PsiType qualifierType, - @Nonnull LookupElement item, - @Nonnull Object object, - @Nonnull PsiElement place) { - if (shouldMarkRed(object, place)) { - return PrioritizedLookupElement.withExplicitProximity(LookupElementDecorator.withRenderer(item, new LookupElementRenderer>() { - @Override - public void renderElement(LookupElementDecorator element, LookupElementPresentation presentation) { - element.getDelegate().renderElement(presentation); - presentation.setItemTextForeground(StandardColors.RED); + JavaMethodCallElement item = new JavaMethodCallElement(method).setQualifierSubstitutor(substitutor); + item.setForcedQualifier(completionElement.getQualifierText()); + return Collections.singletonList(item); } - }), -1); - } - if (containsMember(qualifierType, object, false) && !qualifierType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { - LookupElementDecorator bold = LookupElementDecorator.withRenderer(item, new LookupElementRenderer>() { - @Override - public void renderElement(LookupElementDecorator element, LookupElementPresentation presentation) { - element.getDelegate().renderElement(presentation); - presentation.setItemTextBold(true); + if (completion instanceof PsiVariable variable) { + return Collections.singletonList(new VariableLookupItem(variable).setSubstitutor(substitutor)); + } + if (completion instanceof PsiPackage psiPackage) { + return Collections.singletonList(new PackageLookupItem(psiPackage, reference.getElement())); } - }); - return object instanceof PsiField ? bold : PrioritizedLookupElement.withExplicitProximity(bold, 1); - } - return item; - } - private static boolean shouldMarkRed(@Nonnull Object object, @Nonnull PsiElement place) { - if (!(object instanceof PsiMember)) { - return false; - } - if (Java15APIUsageInspection.getLastIncompatibleLanguageLevel((PsiMember) object, PsiUtil.getLanguageLevel(place)) != null) { - return true; + return Collections.singletonList(LookupItemUtil.objectToLookupItem(completion)); } - if (object instanceof PsiEnumConstant) { - return findConstantsUsedInSwitch(place).contains(CompletionUtilCore.getOriginalOrSelf((PsiEnumConstant) object)); - } - if (object instanceof PsiClass && ReferenceListWeigher.INSTANCE.getApplicability((PsiClass) object, place) == inapplicable) { - return true; - } - return false; - } + public static boolean hasAccessibleConstructor(@Nonnull PsiType type, @Nonnull PsiElement place) { + if (type instanceof PsiArrayType) { + return true; + } - @Contract("null, _, _ -> false") - private static boolean containsMember(@Nullable PsiType qualifierType, @Nonnull Object object, boolean checkBases) { - if (!(object instanceof PsiMember)) { - return false; - } + PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (psiClass == null || psiClass.isEnum() || psiClass.isAnnotationType()) { + return false; + } - if (qualifierType instanceof PsiArrayType) { //length and clone() - PsiFile file = ((PsiMember) object).getContainingFile(); - if (file == null || file.getVirtualFile() == null) { //yes, they're a bit dummy - return true; - } - } else if (qualifierType instanceof PsiClassType) { - PsiClass qualifierClass = ((PsiClassType) qualifierType).resolve(); - if (qualifierClass == null) { - return false; - } - if (object instanceof PsiMethod && qualifierClass.findMethodBySignature((PsiMethod) object, checkBases) != null) { - return true; - } - PsiClass memberClass = ((PsiMember) object).getContainingClass(); - return checkBases ? InheritanceUtil.isInheritorOrSelf(qualifierClass, memberClass, true) : qualifierClass.equals(memberClass); - } - return false; - } - - static Iterable createLookupElements(CompletionElement completionElement, PsiJavaReference reference) { - Object completion = completionElement.getElement(); - assert !(completion instanceof LookupElement); - - if (reference instanceof PsiJavaCodeReferenceElement) { - if (completion instanceof PsiMethod && - ((PsiJavaCodeReferenceElement) reference).getParent() instanceof PsiImportStaticStatement) { - return Collections.singletonList(JavaLookupElementBuilder.forMethod((PsiMethod) completion, PsiSubstitutor.EMPTY)); - } - - if (completion instanceof PsiClass) { - List classItems = JavaClassNameCompletionContributor.createClassLookupItems( - CompletionUtilCore.getOriginalOrSelf((PsiClass) completion), - JavaClassNameCompletionContributor.AFTER_NEW.accepts(reference), - JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER, - Conditions.alwaysTrue()); - return JBIterable.from(classItems).flatMap(i -> JavaConstructorCallElement.wrap(i, reference.getElement())); - } + PsiMethod[] methods = psiClass.getConstructors(); + return methods.length == 0 || Arrays.stream(methods).anyMatch(constructor -> isConstructorCompletable(constructor, place)); } - if (reference instanceof PsiMethodReferenceExpression && completion instanceof PsiMethod && ((PsiMethod) completion).isConstructor()) { - return Collections.singletonList(JavaLookupElementBuilder.forMethod((PsiMethod) completion, "new", PsiSubstitutor.EMPTY, null)); + private static boolean isConstructorCompletable(@Nonnull PsiMethod constructor, @Nonnull PsiElement place) { + if (!(constructor instanceof PsiCompiledElement)) { + return true; // it's possible to use a quick fix to make accessible after completion + } + //noinspection SimplifiableIfStatement + if (constructor.isPrivate()) { + return false; + } + return !constructor.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) + || PsiUtil.isAccessible(constructor, place, null); } - PsiSubstitutor substitutor = completionElement.getSubstitutor(); - if (substitutor == null) { - substitutor = PsiSubstitutor.EMPTY; - } - if (completion instanceof PsiClass) { - JavaPsiClassReferenceElement classItem = - JavaClassNameCompletionContributor.createClassLookupItem((PsiClass) completion, true).setSubstitutor(substitutor); - return JavaConstructorCallElement.wrap(classItem, reference.getElement()); - } - if (completion instanceof PsiMethod) { - if (reference instanceof PsiMethodReferenceExpression) { - return Collections.singleton(new JavaMethodReferenceElement((PsiMethod) completion, (PsiMethodReferenceExpression) reference)); - } - - JavaMethodCallElement item = new JavaMethodCallElement((PsiMethod) completion).setQualifierSubstitutor(substitutor); - item.setForcedQualifier(completionElement.getQualifierText()); - return Collections.singletonList(item); - } - if (completion instanceof PsiVariable) { - return Collections.singletonList(new VariableLookupItem((PsiVariable) completion).setSubstitutor(substitutor)); - } - if (completion instanceof PsiPackage) { - return Collections.singletonList(new PackageLookupItem((PsiPackage) completion, reference.getElement())); + @RequiredReadAction + public static LinkedHashSet getAllLookupStrings(@Nonnull PsiMember member) { + LinkedHashSet allLookupStrings = new LinkedHashSet<>(); + String name = member.getName(); + allLookupStrings.add(name); + PsiClass containingClass = member.getContainingClass(); + while (containingClass != null) { + String className = containingClass.getName(); + if (className == null) { + break; + } + name = className + "." + name; + allLookupStrings.add(name); + PsiElement parent = containingClass.getParent(); + if (!(parent instanceof PsiClass psiClass)) { + break; + } + containingClass = psiClass; + } + return allLookupStrings; } - return Collections.singletonList(LookupItemUtil.objectToLookupItem(completion)); - } - - public static boolean hasAccessibleConstructor(@Nonnull PsiType type, @Nonnull PsiElement place) { - if (type instanceof PsiArrayType) { - return true; + public static boolean mayHaveSideEffects(@Nullable PsiElement element) { + return element instanceof PsiExpression expression && SideEffectChecker.mayHaveSideEffects(expression); } - final PsiClass psiClass = PsiUtil.resolveClassInType(type); - if (psiClass == null || psiClass.isEnum() || psiClass.isAnnotationType()) { - return false; + @RequiredWriteAction + public static void insertClassReference(@Nonnull PsiClass psiClass, @Nonnull PsiFile file, int offset) { + insertClassReference(psiClass, file, offset, offset); } - PsiMethod[] methods = psiClass.getConstructors(); - return methods.length == 0 || Arrays.stream(methods).anyMatch(constructor -> isConstructorCompletable(constructor, place)); - } + @RequiredWriteAction + public static int insertClassReference(PsiClass psiClass, PsiFile file, int startOffset, int endOffset) { + Project project = file.getProject(); + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + documentManager.commitAllDocuments(); - private static boolean isConstructorCompletable(@Nonnull PsiMethod constructor, @Nonnull PsiElement place) { - if (!(constructor instanceof PsiCompiledElement)) { - return true; // it's possible to use a quick fix to make accessible after completion - } - if (constructor.hasModifierProperty(PsiModifier.PRIVATE)) { - return false; - } - if (constructor.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { - return PsiUtil.isAccessible(constructor, place, null); - } - return true; - } - - public static LinkedHashSet getAllLookupStrings(@Nonnull PsiMember member) { - LinkedHashSet allLookupStrings = new LinkedHashSet<>(); - String name = member.getName(); - allLookupStrings.add(name); - PsiClass containingClass = member.getContainingClass(); - while (containingClass != null) { - final String className = containingClass.getName(); - if (className == null) { - break; - } - name = className + "." + name; - allLookupStrings.add(name); - final PsiElement parent = containingClass.getParent(); - if (!(parent instanceof PsiClass)) { - break; - } - containingClass = (PsiClass) parent; - } - return allLookupStrings; - } + PsiManager manager = file.getManager(); - public static boolean mayHaveSideEffects(@Nullable final PsiElement element) { - return element instanceof PsiExpression && SideEffectChecker.mayHaveSideEffects((PsiExpression) element); - } + Document document = FileDocumentManager.getInstance().getDocument(file.getViewProvider().getVirtualFile()); - public static void insertClassReference(@Nonnull PsiClass psiClass, @Nonnull PsiFile file, int offset) { - insertClassReference(psiClass, file, offset, offset); - } + PsiReference reference = file.findReferenceAt(startOffset); + if (reference != null && manager.areElementsEquivalent(psiClass, reference.resolve())) { + return endOffset; + } - public static int insertClassReference(PsiClass psiClass, PsiFile file, int startOffset, int endOffset) { - final Project project = file.getProject(); - PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); - documentManager.commitAllDocuments(); + String name = psiClass.getName(); + if (name == null) { + return endOffset; + } - final PsiManager manager = file.getManager(); + if (reference != null && !psiClass.isStatic()) { + PsiClass containingClass = psiClass.getContainingClass(); + if (containingClass != null && containingClass.hasTypeParameters()) { + PsiModifierListOwner enclosingStaticElement = PsiUtil.getEnclosingStaticElement(reference.getElement(), null); + if (enclosingStaticElement != null && !PsiTreeUtil.isAncestor(enclosingStaticElement, psiClass, false)) { + return endOffset; + } + } + } - final Document document = FileDocumentManager.getInstance().getDocument(file.getViewProvider().getVirtualFile()); + assert document != null; + document.replaceString(startOffset, endOffset, name); + + int newEndOffset = startOffset + name.length(); + RangeMarker toDelete = insertTemporary(newEndOffset, document, " "); + + documentManager.commitAllDocuments(); + + PsiElement element = file.findElementAt(startOffset); + if (element instanceof PsiIdentifier identifier + && identifier.getParent() instanceof PsiJavaCodeReferenceElement ref + && !ref.isQualified() && !(ref.getParent() instanceof PsiPackageStatement) + && psiClass.isValid() && !psiClass.getManager().areElementsEquivalent(psiClass, resolveReference(ref))) { + boolean staticImport = ref instanceof PsiImportStaticReferenceElement; + PsiElement newElement; + try { + newElement = staticImport + ? ((PsiImportStaticReferenceElement)ref).bindToTargetClass(psiClass) + : ref.bindToElement(psiClass); + } + catch (IncorrectOperationException e) { + return endOffset; // can happen if fqn contains reserved words, for example + } - PsiReference reference = file.findReferenceAt(startOffset); - if (reference != null && manager.areElementsEquivalent(psiClass, reference.resolve())) { - return endOffset; - } + RangeMarker rangeMarker = document.createRangeMarker(newElement.getTextRange()); + documentManager.doPostponedOperationsAndUnblockDocument(document); + documentManager.commitDocument(document); + + newElement = CodeInsightUtilCore.findElementInRange( + file, + rangeMarker.getStartOffset(), + rangeMarker.getEndOffset(), + PsiJavaCodeReferenceElement.class, + JavaLanguage.INSTANCE + ); + rangeMarker.dispose(); + if (newElement != null) { + newEndOffset = newElement.getTextRange().getEndOffset(); + if (!(newElement instanceof PsiReferenceExpression)) { + PsiReferenceParameterList parameterList = ((PsiJavaCodeReferenceElement)newElement).getParameterList(); + if (parameterList != null) { + newEndOffset = parameterList.getTextRange().getStartOffset(); + } + } - String name = psiClass.getName(); - if (name == null) { - return endOffset; - } + if (!staticImport + && !psiClass.getManager().areElementsEquivalent(psiClass, resolveReference((PsiReference)newElement)) + && !PsiUtil.isInnerClass(psiClass)) { + String qName = psiClass.getQualifiedName(); + if (qName != null) { + document.replaceString(newElement.getTextRange().getStartOffset(), newEndOffset, qName); + newEndOffset = newElement.getTextRange().getStartOffset() + qName.length(); + } + } + } + } - if (reference != null && !psiClass.hasModifierProperty(PsiModifier.STATIC)) { - PsiClass containingClass = psiClass.getContainingClass(); - if (containingClass != null && containingClass.hasTypeParameters()) { - PsiModifierListOwner enclosingStaticElement = PsiUtil.getEnclosingStaticElement(reference.getElement(), null); - if (enclosingStaticElement != null && !PsiTreeUtil.isAncestor(enclosingStaticElement, psiClass, false)) { - return endOffset; + if (toDelete != null && toDelete.isValid()) { + document.deleteString(toDelete.getStartOffset(), toDelete.getEndOffset()); } - } - } - assert document != null; - document.replaceString(startOffset, endOffset, name); - - int newEndOffset = startOffset + name.length(); - final RangeMarker toDelete = insertTemporary(newEndOffset, document, " "); - - documentManager.commitAllDocuments(); - - PsiElement element = file.findElementAt(startOffset); - if (element instanceof PsiIdentifier) { - PsiElement parent = element.getParent(); - if (parent instanceof PsiJavaCodeReferenceElement && - !((PsiJavaCodeReferenceElement) parent).isQualified() && - !(parent.getParent() instanceof PsiPackageStatement)) { - PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement) parent; - - if (psiClass.isValid() && !psiClass.getManager().areElementsEquivalent(psiClass, resolveReference(ref))) { - final boolean staticImport = ref instanceof PsiImportStaticReferenceElement; - PsiElement newElement; - try { - newElement = staticImport - ? ((PsiImportStaticReferenceElement) ref).bindToTargetClass(psiClass) - : ref.bindToElement(psiClass); - } catch (IncorrectOperationException e) { - return endOffset; // can happen if fqn contains reserved words, for example - } - - final RangeMarker rangeMarker = document.createRangeMarker(newElement.getTextRange()); - documentManager.doPostponedOperationsAndUnblockDocument(document); - documentManager.commitDocument(document); - - newElement = CodeInsightUtilCore.findElementInRange(file, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), - PsiJavaCodeReferenceElement.class, - JavaLanguage.INSTANCE); - rangeMarker.dispose(); - if (newElement != null) { - newEndOffset = newElement.getTextRange().getEndOffset(); - if (!(newElement instanceof PsiReferenceExpression)) { - PsiReferenceParameterList parameterList = ((PsiJavaCodeReferenceElement) newElement).getParameterList(); - if (parameterList != null) { - newEndOffset = parameterList.getTextRange().getStartOffset(); - } - } + return newEndOffset; + } - if (!staticImport && - !psiClass.getManager().areElementsEquivalent(psiClass, resolveReference((PsiReference) newElement)) && - !PsiUtil.isInnerClass(psiClass)) { - final String qName = psiClass.getQualifiedName(); - if (qName != null) { - document.replaceString(newElement.getTextRange().getStartOffset(), newEndOffset, qName); - newEndOffset = newElement.getTextRange().getStartOffset() + qName.length(); - } + @Nullable + @RequiredReadAction + static PsiElement resolveReference(PsiReference psiReference) { + if (psiReference instanceof PsiPolyVariantReference polyVariantReference) { + ResolveResult[] results = polyVariantReference.multiResolve(true); + if (results.length == 1) { + return results[0].getElement(); } - } } - } + return psiReference.resolve(); } - if (toDelete != null && toDelete.isValid()) { - document.deleteString(toDelete.getStartOffset(), toDelete.getEndOffset()); + @Nullable + public static RangeMarker insertTemporary(int endOffset, Document document, String temporary) { + CharSequence chars = document.getCharsSequence(); + if (endOffset < chars.length() && Character.isJavaIdentifierPart(chars.charAt(endOffset))) { + document.insertString(endOffset, temporary); + RangeMarker toDelete = document.createRangeMarker(endOffset, endOffset + 1); + toDelete.setGreedyToLeft(true); + toDelete.setGreedyToRight(true); + return toDelete; + } + return null; } - return newEndOffset; - } - - @Nullable - static PsiElement resolveReference(final PsiReference psiReference) { - if (psiReference instanceof PsiPolyVariantReference) { - final ResolveResult[] results = ((PsiPolyVariantReference) psiReference).multiResolve(true); - if (results.length == 1) { - return results[0].getElement(); - } - } - return psiReference.resolve(); - } - - @Nullable - public static RangeMarker insertTemporary(int endOffset, Document document, String temporary) { - final CharSequence chars = document.getCharsSequence(); - if (endOffset < chars.length() && Character.isJavaIdentifierPart(chars.charAt(endOffset))) { - document.insertString(endOffset, temporary); - RangeMarker toDelete = document.createRangeMarker(endOffset, endOffset + 1); - toDelete.setGreedyToLeft(true); - toDelete.setGreedyToRight(true); - return toDelete; - } - return null; - } - - public static void insertParentheses(@Nonnull InsertionContext context, - @Nonnull LookupElement item, - boolean overloadsMatter, - boolean hasParams) { - insertParentheses(context, item, overloadsMatter, hasParams, false); - } - - public static void insertParentheses(@Nonnull InsertionContext context, - @Nonnull LookupElement item, - boolean overloadsMatter, - boolean hasParams, - final boolean forceClosingParenthesis) { - final Editor editor = context.getEditor(); - final char completionChar = context.getCompletionChar(); - final PsiFile file = context.getFile(); - - final TailType tailType = completionChar == '(' ? TailType.NONE : - completionChar == ':' ? TailType.COND_EXPR_COLON : - LookupItem.handleCompletionChar(context.getEditor(), item, completionChar); - final boolean hasTail = tailType != TailType.NONE && tailType != TailType.UNKNOWN; - final boolean smart = completionChar == Lookup.COMPLETE_STATEMENT_SELECT_CHAR; - - if (completionChar == '(' || completionChar == '.' || completionChar == ',' || completionChar == ';' || completionChar == ':' || completionChar == ' ') { - context.setAddCompletionChar(false); + @RequiredUIAccess + public static void insertParentheses( + @Nonnull InsertionContext context, + @Nonnull LookupElement item, + boolean overloadsMatter, + boolean hasParams + ) { + insertParentheses(context, item, overloadsMatter, hasParams, false); } - if (hasTail) { - hasParams = false; - } - final boolean needRightParenth = forceClosingParenthesis || - !smart && (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET || - !hasParams && completionChar != '('); - - context.commitDocument(); - - final CommonCodeStyleSettings styleSettings = CompletionStyleUtil.getCodeStyleSettings(context); - final PsiElement elementAt = file.findElementAt(context.getStartOffset()); - if (elementAt == null || !(elementAt.getParent() instanceof PsiMethodReferenceExpression)) { - final boolean hasParameters = hasParams; - final boolean spaceBetweenParentheses = styleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES && hasParams; - new ParenthesesInsertHandler(styleSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES, spaceBetweenParentheses, - needRightParenth, styleSettings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE) { - @Override - protected boolean placeCaretInsideParentheses(InsertionContext context1, LookupElement item1) { - return hasParameters; + @RequiredUIAccess + public static void insertParentheses( + @Nonnull InsertionContext context, + @Nonnull LookupElement item, + boolean overloadsMatter, + boolean hasParams, + boolean forceClosingParenthesis + ) { + Editor editor = context.getEditor(); + char completionChar = context.getCompletionChar(); + PsiFile file = context.getFile(); + + TailType tailType = completionChar == '(' ? TailType.NONE : + completionChar == ':' ? TailType.COND_EXPR_COLON : + LookupItem.handleCompletionChar(context.getEditor(), item, completionChar); + boolean hasTail = tailType != TailType.NONE && tailType != TailType.UNKNOWN; + boolean smart = completionChar == Lookup.COMPLETE_STATEMENT_SELECT_CHAR; + + if (completionChar == '(' || completionChar == '.' || completionChar == ',' + || completionChar == ';' || completionChar == ':' || completionChar == ' ') { + context.setAddCompletionChar(false); } - @Override - protected PsiElement findExistingLeftParenthesis(@Nonnull InsertionContext context) { - PsiElement token = super.findExistingLeftParenthesis(context); - return isPartOfLambda(token) ? null : token; + if (hasTail) { + hasParams = false; } + final boolean needRightParenth = forceClosingParenthesis + || !smart && (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET || !hasParams && completionChar != '('); - private boolean isPartOfLambda(PsiElement token) { - return token != null && token.getParent() instanceof PsiExpressionList && - PsiUtilCore.getElementType(PsiTreeUtil.nextVisibleLeaf(token.getParent())) == JavaTokenType.ARROW; - } - }.handleInsert(context, item); - } + context.commitDocument(); - if (hasParams) { - // Invoke parameters popup - AutoPopupController.getInstance(file.getProject()).autoPopupParameterInfo(editor, overloadsMatter ? null : (PsiElement) item.getObject()); - } + final CommonCodeStyleSettings styleSettings = CompletionStyleUtil.getCodeStyleSettings(context); + PsiElement elementAt = file.findElementAt(context.getStartOffset()); + if (elementAt == null || !(elementAt.getParent() instanceof PsiMethodReferenceExpression)) { + final boolean hasParameters = hasParams; + final boolean spaceBetweenParentheses = styleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES && hasParams; + new ParenthesesInsertHandler<>( + styleSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES, + spaceBetweenParentheses, + needRightParenth, + styleSettings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE + ) { + @Override + protected boolean placeCaretInsideParentheses(InsertionContext context1, LookupElement item1) { + return hasParameters; + } - if (smart || !needRightParenth || !insertTail(context, item, tailType, hasTail)) { - return; - } + @Override + @RequiredReadAction + protected PsiElement findExistingLeftParenthesis(@Nonnull InsertionContext context) { + PsiElement token = super.findExistingLeftParenthesis(context); + return isPartOfLambda(token) ? null : token; + } - if (completionChar == '.') { - AutoPopupController.getInstance(file.getProject()).autoPopupMemberLookup(context.getEditor(), null); - } else if (completionChar == ',') { - AutoPopupController.getInstance(file.getProject()).autoPopupParameterInfo(context.getEditor(), null); - } - } - - public static boolean insertTail(InsertionContext context, LookupElement item, TailType tailType, boolean hasTail) { - TailType toInsert = tailType; - LookupItem lookupItem = item.as(LookupItem.CLASS_CONDITION_KEY); - if (lookupItem == null || lookupItem.getAttribute(LookupItem.TAIL_TYPE_ATTR) != TailType.UNKNOWN) { - if (!hasTail && item.getObject() instanceof PsiMethod && PsiType.VOID.equals(((PsiMethod) item.getObject()).getReturnType())) { - PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); - if (psiElement().beforeLeaf(psiElement().withText(".")).accepts(context.getFile().findElementAt(context.getTailOffset() - 1))) { - return false; + @RequiredReadAction + private boolean isPartOfLambda(PsiElement token) { + return token != null && token.getParent() instanceof PsiExpressionList expressionList + && PsiUtilCore.getElementType(PsiTreeUtil.nextVisibleLeaf(expressionList)) == JavaTokenType.ARROW; + } + }.handleInsert(context, item); } - boolean insertAdditionalSemicolon = true; - PsiElement leaf = context.getFile().findElementAt(context.getStartOffset()); - PsiElement composite = leaf == null ? null : leaf.getParent(); - if (composite instanceof PsiMethodReferenceExpression && LambdaHighlightingUtil.insertSemicolon(composite.getParent())) { - insertAdditionalSemicolon = false; - } else if (composite instanceof PsiReferenceExpression) { - PsiElement parent = composite.getParent(); - if (parent instanceof PsiMethodCallExpression) { - parent = parent.getParent(); - } - if (parent instanceof PsiLambdaExpression && !LambdaHighlightingUtil.insertSemicolonAfter((PsiLambdaExpression) parent)) { - insertAdditionalSemicolon = false; - } - if (parent instanceof PsiMethodReferenceExpression && LambdaHighlightingUtil.insertSemicolon(parent.getParent())) { - insertAdditionalSemicolon = false; - } + if (hasParams) { + // Invoke parameters popup + AutoPopupController.getInstance(file.getProject()) + .autoPopupParameterInfo(editor, overloadsMatter ? null : (PsiElement)item.getObject()); } - if (insertAdditionalSemicolon) { - toInsert = TailType.SEMICOLON; + + if (smart || !needRightParenth || !insertTail(context, item, tailType, hasTail)) { + return; } - } + if (completionChar == '.') { + AutoPopupController.getInstance(file.getProject()).autoPopupMemberLookup(context.getEditor(), null); + } + else if (completionChar == ',') { + AutoPopupController.getInstance(file.getProject()).autoPopupParameterInfo(context.getEditor(), null); + } } - Editor editor = context.getEditor(); - int tailOffset = context.getTailOffset(); - int afterTailOffset = toInsert.processTail(editor, tailOffset); - int caretOffset = editor.getCaretModel().getOffset(); - if (afterTailOffset > tailOffset && - tailOffset > caretOffset && - TabOutScopesTracker.getInstance().removeScopeEndingAt(editor, caretOffset) > 0) { - TabOutScopesTracker.getInstance().registerEmptyScope(editor, caretOffset, afterTailOffset); + + @RequiredUIAccess + public static boolean insertTail(InsertionContext context, LookupElement item, TailType tailType, boolean hasTail) { + TailType toInsert = tailType; + LookupItem lookupItem = item.as(LookupItem.CLASS_CONDITION_KEY); + if (lookupItem == null || lookupItem.getAttribute(LookupItem.TAIL_TYPE_ATTR) != TailType.UNKNOWN) { + if (!hasTail && item.getObject() instanceof PsiMethod method && PsiType.VOID.equals(method.getReturnType())) { + PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); + if (psiElement().beforeLeaf(psiElement().withText(".")) + .accepts(context.getFile().findElementAt(context.getTailOffset() - 1))) { + return false; + } + + boolean insertAdditionalSemicolon = true; + PsiElement leaf = context.getFile().findElementAt(context.getStartOffset()); + PsiElement composite = leaf == null ? null : leaf.getParent(); + if (composite instanceof PsiMethodReferenceExpression && LambdaHighlightingUtil.insertSemicolon(composite.getParent())) { + insertAdditionalSemicolon = false; + } + else if (composite instanceof PsiReferenceExpression refExpr) { + PsiElement parent = refExpr.getParent(); + if (parent instanceof PsiMethodCallExpression) { + parent = parent.getParent(); + } + if (parent instanceof PsiLambdaExpression lambda && !LambdaHighlightingUtil.insertSemicolonAfter(lambda)) { + insertAdditionalSemicolon = false; + } + if (parent instanceof PsiMethodReferenceExpression && LambdaHighlightingUtil.insertSemicolon(parent.getParent())) { + insertAdditionalSemicolon = false; + } + } + if (insertAdditionalSemicolon) { + toInsert = TailType.SEMICOLON; + } + + } + } + Editor editor = context.getEditor(); + int tailOffset = context.getTailOffset(); + int afterTailOffset = toInsert.processTail(editor, tailOffset); + int caretOffset = editor.getCaretModel().getOffset(); + if (afterTailOffset > tailOffset && tailOffset > caretOffset && + TabOutScopesTracker.getInstance().removeScopeEndingAt(editor, caretOffset) > 0) { + TabOutScopesTracker.getInstance().registerEmptyScope(editor, caretOffset, afterTailOffset); + } + return true; } - return true; - } - - //need to shorten references in type argument list - public static void shortenReference(final PsiFile file, final int offset) throws IncorrectOperationException { - Project project = file.getProject(); - final PsiDocumentManager manager = PsiDocumentManager.getInstance(project); - Document document = manager.getDocument(file); - if (document == null) { - PsiUtilCore.ensureValid(file); - LOG.error("No document for " + file); - return; + + //need to shorten references in type argument list + @RequiredReadAction + public static void shortenReference(PsiFile file, int offset) throws IncorrectOperationException { + Project project = file.getProject(); + PsiDocumentManager manager = PsiDocumentManager.getInstance(project); + Document document = manager.getDocument(file); + if (document == null) { + PsiUtilCore.ensureValid(file); + LOG.error("No document for " + file); + return; + } + + manager.commitDocument(document); + PsiReference ref = file.findReferenceAt(offset); + if (ref != null) { + JavaCodeStyleManager.getInstance(project).shortenClassReferences(ref.getElement()); + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document); + } } - manager.commitDocument(document); - PsiReference ref = file.findReferenceAt(offset); - if (ref != null) { - JavaCodeStyleManager.getInstance(project).shortenClassReferences(ref.getElement()); - PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document); + public static boolean inSomePackage(PsiElement context) { + return context.getContainingFile() instanceof PsiClassOwner contextFile && StringUtil.isNotEmpty(contextFile.getPackageName()); } - } - public static boolean inSomePackage(PsiElement context) { - PsiFile contextFile = context.getContainingFile(); - return contextFile instanceof PsiClassOwner && StringUtil.isNotEmpty(((PsiClassOwner) contextFile).getPackageName()); - } + public static boolean isSourceLevelAccessible(PsiElement context, PsiClass psiClass, boolean pkgContext) { + if (!JavaPsiFacade.getInstance(psiClass.getProject()) + .getResolveHelper() + .isAccessible(psiClass, context, psiClass.getContainingClass())) { + return false; + } - public static boolean isSourceLevelAccessible(PsiElement context, PsiClass psiClass, final boolean pkgContext) { - if (!JavaPsiFacade.getInstance(psiClass.getProject()).getResolveHelper().isAccessible(psiClass, context, psiClass.getContainingClass())) { - return false; + if (pkgContext) { + PsiClass topLevel = PsiUtil.getTopLevelClass(psiClass); + if (topLevel != null) { + String fqName = topLevel.getQualifiedName(); + if (fqName != null && StringUtil.isEmpty(StringUtil.getPackageName(fqName))) { + return false; + } + } + } + + return true; } - if (pkgContext) { - PsiClass topLevel = PsiUtil.getTopLevelClass(psiClass); - if (topLevel != null) { - String fqName = topLevel.getQualifiedName(); - if (fqName != null && StringUtil.isEmpty(StringUtil.getPackageName(fqName))) { - return false; + public static boolean promptTypeArgs(InsertionContext context, int offset) { + if (offset < 0) { + return false; } - } - } - return true; - } + OffsetKey key = context.trackOffset(offset, false); + PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(); + offset = context.getOffset(key); + if (offset < 0) { + return false; + } - public static boolean promptTypeArgs(InsertionContext context, int offset) { - if (offset < 0) { - return false; + String open = escapeXmlIfNeeded(context, "<"); + context.getDocument().insertString(offset, open); + context.getEditor().getCaretModel().moveToOffset(offset + open.length()); + if (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) { + context.getDocument().insertString(offset + open.length(), escapeXmlIfNeeded(context, ">")); + } + if (context.getCompletionChar() != Lookup.COMPLETE_STATEMENT_SELECT_CHAR) { + context.setAddCompletionChar(false); + } + return true; } - OffsetKey key = context.trackOffset(offset, false); - PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(); - offset = context.getOffset(key); - if (offset < 0) { - return false; - } + public static FakePsiElement createContextWithXxxVariable(@Nonnull PsiElement place, @Nonnull PsiType varType) { + return new FakePsiElement() { + @Override + public boolean processDeclarations( + @Nonnull PsiScopeProcessor processor, + @Nonnull ResolveState state, + PsiElement lastParent, + @Nonnull PsiElement place + ) { + return processor.execute(new LightVariableBuilder("xxx", varType, place), ResolveState.initial()); + } - String open = escapeXmlIfNeeded(context, "<"); - context.getDocument().insertString(offset, open); - context.getEditor().getCaretModel().moveToOffset(offset + open.length()); - if (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) { - context.getDocument().insertString(offset + open.length(), escapeXmlIfNeeded(context, ">")); - } - if (context.getCompletionChar() != Lookup.COMPLETE_STATEMENT_SELECT_CHAR) { - context.setAddCompletionChar(false); + @Override + public PsiElement getParent() { + return place; + } + }; } - return true; - } - - public static FakePsiElement createContextWithXxxVariable(@Nonnull PsiElement place, @Nonnull PsiType varType) { - return new FakePsiElement() { - @Override - public boolean processDeclarations(@Nonnull PsiScopeProcessor processor, - @Nonnull ResolveState state, - PsiElement lastParent, - @Nonnull PsiElement place) { - return processor.execute(new LightVariableBuilder("xxx", varType, place), ResolveState.initial()); - } - - @Override - public PsiElement getParent() { - return place; - } - }; - } - - @Nonnull - public static String escapeXmlIfNeeded(InsertionContext context, @Nonnull String generics) { -// if(context.getFile().getViewProvider().getBaseLanguage() == StdLanguages.JSPX) -// { -// return StringUtil.escapeXmlEntities(generics); -// } - return generics; - } - - public static boolean isEffectivelyDeprecated(PsiDocCommentOwner member) { - if (member.isDeprecated()) { - return true; + + @Nonnull + public static String escapeXmlIfNeeded(InsertionContext context, @Nonnull String generics) { + //if (context.getFile().getViewProvider().getBaseLanguage() == StdLanguages.JSPX) + //{ + // return StringUtil.escapeXmlEntities(generics); + //} + return generics; } - PsiClass aClass = member.getContainingClass(); - while (aClass != null) { - if (aClass.isDeprecated()) { - return true; - } - aClass = aClass.getContainingClass(); + public static boolean isEffectivelyDeprecated(PsiDocCommentOwner member) { + if (member.isDeprecated()) { + return true; + } + + PsiClass aClass = member.getContainingClass(); + while (aClass != null) { + if (aClass.isDeprecated()) { + return true; + } + aClass = aClass.getContainingClass(); + } + return false; } - return false; - } - - public static int findQualifiedNameStart(@Nonnull InsertionContext context) { - int start = context.getTailOffset() - 1; - while (start >= 0) { - char ch = context.getDocument().getCharsSequence().charAt(start); - if (!Character.isJavaIdentifierPart(ch) && ch != '.') { - break; - } - start--; + + public static int findQualifiedNameStart(@Nonnull InsertionContext context) { + int start = context.getTailOffset() - 1; + while (start >= 0) { + char ch = context.getDocument().getCharsSequence().charAt(start); + if (!Character.isJavaIdentifierPart(ch) && ch != '.') { + break; + } + start--; + } + return start + 1; } - return start + 1; - } } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/SmartCastProvider.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/SmartCastProvider.java index 484d041729..35e43465fc 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/SmartCastProvider.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/SmartCastProvider.java @@ -33,6 +33,7 @@ import consulo.util.collection.ContainerUtil; import jakarta.annotation.Nonnull; + import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -43,169 +44,206 @@ * @author peter */ class SmartCastProvider implements CompletionProvider { - static final ElementPattern TYPECAST_TYPE_CANDIDATE = psiElement().afterLeaf("("); + static final ElementPattern TYPECAST_TYPE_CANDIDATE = psiElement().afterLeaf("("); - static boolean shouldSuggestCast(CompletionParameters parameters) { - PsiElement position = parameters.getPosition(); - PsiElement parent = getParenthesisOwner(position); - if (parent instanceof PsiTypeCastExpression) { - return true; + static boolean shouldSuggestCast(CompletionParameters parameters) { + PsiElement position = parameters.getPosition(); + PsiElement parent = getParenthesisOwner(position); + if (parent instanceof PsiTypeCastExpression) { + return true; + } + if (parent instanceof PsiParenthesizedExpression) { + return parameters.getOffset() == position.getTextRange().getStartOffset(); + } + return false; } - if (parent instanceof PsiParenthesizedExpression) { - return parameters.getOffset() == position.getTextRange().getStartOffset(); + + private static PsiElement getParenthesisOwner(PsiElement position) { + PsiElement lParen = PsiTreeUtil.prevVisibleLeaf(position); + return lParen == null || !lParen.textMatches("(") ? null : lParen.getParent(); } - return false; - } - - private static PsiElement getParenthesisOwner(PsiElement position) { - PsiElement lParen = PsiTreeUtil.prevVisibleLeaf(position); - return lParen == null || !lParen.textMatches("(") ? null : lParen.getParent(); - } - - @RequiredReadAction - @Override - public void addCompletions(@Nonnull final CompletionParameters parameters, @Nonnull final ProcessingContext context, @Nonnull final CompletionResultSet result) { - addCastVariants(parameters, result.getPrefixMatcher(), result, false); - } - - static void addCastVariants(@Nonnull CompletionParameters parameters, PrefixMatcher matcher, @Nonnull Consumer result, boolean quick) { - if (!shouldSuggestCast(parameters)) { - return; + + @RequiredReadAction + @Override + public void addCompletions( + @Nonnull final CompletionParameters parameters, + @Nonnull final ProcessingContext context, + @Nonnull final CompletionResultSet result + ) { + addCastVariants(parameters, result.getPrefixMatcher(), result, false); } - PsiElement position = parameters.getPosition(); - PsiElement parenthesisOwner = getParenthesisOwner(position); - final boolean insideCast = parenthesisOwner instanceof PsiTypeCastExpression; - - if (insideCast) { - PsiElement parent = parenthesisOwner.getParent(); - if (parent instanceof PsiParenthesizedExpression) { - if (parent.getParent() instanceof PsiReferenceExpression) { - for (ExpectedTypeInfo info : ExpectedTypesProvider.getExpectedTypes((PsiParenthesizedExpression) parent, false)) { - result.accept(PsiTypeLookupItem.createLookupItem(info.getType(), parent)); - } + static void addCastVariants( + @Nonnull CompletionParameters parameters, + PrefixMatcher matcher, + @Nonnull Consumer result, + boolean quick + ) { + if (!shouldSuggestCast(parameters)) { + return; } - for (ExpectedTypeInfo info : getParenthesizedCastExpectationByOperandType(position)) { - addHierarchyTypes(parameters, matcher, info, type -> result.accept(PsiTypeLookupItem.createLookupItem(type, parent)), quick); + + PsiElement position = parameters.getPosition(); + PsiElement parenthesisOwner = getParenthesisOwner(position); + final boolean insideCast = parenthesisOwner instanceof PsiTypeCastExpression; + + if (insideCast) { + PsiElement parent = parenthesisOwner.getParent(); + if (parent instanceof PsiParenthesizedExpression) { + if (parent.getParent() instanceof PsiReferenceExpression) { + for (ExpectedTypeInfo info : ExpectedTypesProvider.getExpectedTypes((PsiParenthesizedExpression)parent, false)) { + result.accept(PsiTypeLookupItem.createLookupItem(info.getType(), parent)); + } + } + for (ExpectedTypeInfo info : getParenthesizedCastExpectationByOperandType(position)) { + addHierarchyTypes( + parameters, + matcher, + info, + type -> result.accept(PsiTypeLookupItem.createLookupItem(type, parent)), + quick + ); + } + return; + } } - return; - } - } - for (final ExpectedTypeInfo info : JavaSmartCompletionContributor.getExpectedTypes(parameters)) { - PsiType type = info.getDefaultType(); - if (type instanceof PsiWildcardType) { - type = ((PsiWildcardType) type).getBound(); - } - - if (type == null || PsiType.VOID.equals(type)) { - continue; - } - - if (type instanceof PsiPrimitiveType) { - final PsiType castedType = getCastedExpressionType(parenthesisOwner); - if (castedType != null && !(castedType instanceof PsiPrimitiveType)) { - final PsiClassType boxedType = ((PsiPrimitiveType) type).getBoxedType(position); - if (boxedType != null) { - type = boxedType; - } + for (final ExpectedTypeInfo info : JavaSmartCompletionContributor.getExpectedTypes(parameters)) { + PsiType type = info.getDefaultType(); + if (type instanceof PsiWildcardType) { + type = ((PsiWildcardType)type).getBound(); + } + + if (type == null || PsiType.VOID.equals(type)) { + continue; + } + + if (type instanceof PsiPrimitiveType) { + final PsiType castedType = getCastedExpressionType(parenthesisOwner); + if (castedType != null && !(castedType instanceof PsiPrimitiveType)) { + final PsiClassType boxedType = ((PsiPrimitiveType)type).getBoxedType(position); + if (boxedType != null) { + type = boxedType; + } + } + } + result.accept(createSmartCastElement(parameters, insideCast, type)); } - } - result.accept(createSmartCastElement(parameters, insideCast, type)); - } - } - - @Nonnull - static List getParenthesizedCastExpectationByOperandType(PsiElement position) { - PsiElement parenthesisOwner = getParenthesisOwner(position); - PsiExpression operand = getCastedExpression(parenthesisOwner); - if (operand == null || !(parenthesisOwner.getParent() instanceof PsiParenthesizedExpression)) { - return Collections.emptyList(); } - List dfaTypes = GuessManager.getInstance(operand.getProject()).getControlFlowExpressionTypeConjuncts(operand); - if (!dfaTypes.isEmpty()) { - return ContainerUtil.map(dfaTypes, dfaType -> - new ExpectedTypeInfoImpl(dfaType, ExpectedTypeInfo.TYPE_OR_SUPERTYPE, dfaType, TailType.NONE, null, () -> null)); + @Nonnull + static List getParenthesizedCastExpectationByOperandType(PsiElement position) { + PsiElement parenthesisOwner = getParenthesisOwner(position); + PsiExpression operand = getCastedExpression(parenthesisOwner); + if (operand == null || !(parenthesisOwner.getParent() instanceof PsiParenthesizedExpression)) { + return Collections.emptyList(); + } + + List dfaTypes = GuessManager.getInstance(operand.getProject()).getControlFlowExpressionTypeConjuncts(operand); + if (!dfaTypes.isEmpty()) { + return ContainerUtil.map(dfaTypes, dfaType -> + new ExpectedTypeInfoImpl(dfaType, ExpectedTypeInfo.TYPE_OR_SUPERTYPE, dfaType, TailType.NONE, null, () -> null)); + } + + PsiType type = operand.getType(); + return type == null || type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) + ? Collections.emptyList() + : Collections.singletonList(new ExpectedTypeInfoImpl( + type, + ExpectedTypeInfo.TYPE_OR_SUBTYPE, + type, + TailType.NONE, + null, + () -> null + )); } - PsiType type = operand.getType(); - return type == null || type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) ? Collections.emptyList() : - Collections.singletonList(new ExpectedTypeInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.NONE, null, () -> null)); - } - - private static void addHierarchyTypes(CompletionParameters parameters, PrefixMatcher matcher, ExpectedTypeInfo info, Consumer result, boolean quick) { - PsiType infoType = info.getType(); - PsiClass infoClass = PsiUtil.resolveClassInClassTypeOnly(infoType); - if (info.getKind() == ExpectedTypeInfo.TYPE_OR_SUPERTYPE) { - InheritanceUtil.processSupers(infoClass, true, superClass -> { - if (!CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) { - result.accept(JavaPsiFacade.getElementFactory(superClass.getProject()).createType(CompletionUtilCore.getOriginalOrSelf(superClass))); + private static void addHierarchyTypes( + CompletionParameters parameters, + PrefixMatcher matcher, + ExpectedTypeInfo info, + Consumer result, + boolean quick + ) { + PsiType infoType = info.getType(); + PsiClass infoClass = PsiUtil.resolveClassInClassTypeOnly(infoType); + if (info.getKind() == ExpectedTypeInfo.TYPE_OR_SUPERTYPE) { + InheritanceUtil.processSupers(infoClass, true, superClass -> { + if (!CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) { + result.accept(JavaPsiFacade.getElementFactory(superClass.getProject()) + .createType(CompletionUtilCore.getOriginalOrSelf(superClass))); + } + return true; + }); } - return true; - }); - } else if (infoType instanceof PsiClassType && !quick) { - JavaInheritorsGetter.processInheritors(parameters, Collections.singleton((PsiClassType) infoType), matcher, type -> { - if (!infoType.equals(type)) { - result.accept(type); + else if (infoType instanceof PsiClassType && !quick) { + JavaInheritorsGetter.processInheritors(parameters, Collections.singleton((PsiClassType)infoType), matcher, type -> { + if (!infoType.equals(type)) { + result.accept(type); + } + }); } - }); } - } - - private static PsiType getCastedExpressionType(PsiElement parenthesisOwner) { - PsiExpression operand = getCastedExpression(parenthesisOwner); - return operand == null ? null : operand.getType(); - } - private static PsiExpression getCastedExpression(PsiElement parenthesisOwner) { - if (parenthesisOwner instanceof PsiTypeCastExpression) { - return ((PsiTypeCastExpression) parenthesisOwner).getOperand(); + private static PsiType getCastedExpressionType(PsiElement parenthesisOwner) { + PsiExpression operand = getCastedExpression(parenthesisOwner); + return operand == null ? null : operand.getType(); } - if (parenthesisOwner instanceof PsiParenthesizedExpression) { - PsiElement next = parenthesisOwner.getNextSibling(); - while ((next instanceof PsiEmptyExpressionImpl || next instanceof PsiErrorElement || next instanceof PsiWhiteSpace)) { - next = next.getNextSibling(); - } - if (next instanceof PsiExpression) { - return (PsiExpression) next; - } - } - return null; - } - - private static LookupElement createSmartCastElement(final CompletionParameters parameters, final boolean overwrite, final PsiType type) { - return AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE.applyPolicy(new LookupElementDecorator( - PsiTypeLookupItem.createLookupItem(type, parameters.getPosition())) { - - @Override - public void handleInsert(@Nonnull InsertionContext context) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.smarttype.casting"); - - final Editor editor = context.getEditor(); - final Document document = editor.getDocument(); - if (overwrite) { - document.deleteString(context.getSelectionEndOffset(), - context.getOffsetMap().getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET)); + private static PsiExpression getCastedExpression(PsiElement parenthesisOwner) { + if (parenthesisOwner instanceof PsiTypeCastExpression) { + return ((PsiTypeCastExpression)parenthesisOwner).getOperand(); } - final CommonCodeStyleSettings csSettings = CompletionStyleUtil.getCodeStyleSettings(context); - final int oldTail = context.getTailOffset(); - context.setTailOffset(RParenthTailType.addRParenth(editor, oldTail, csSettings.SPACE_WITHIN_CAST_PARENTHESES)); - - getDelegate().handleInsert(CompletionUtilCore.newContext(context, getDelegate(), context.getStartOffset(), oldTail)); - - PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(); - if (csSettings.SPACE_AFTER_TYPE_CAST) { - context.setTailOffset(TailType.insertChar(editor, context.getTailOffset(), ' ')); + if (parenthesisOwner instanceof PsiParenthesizedExpression) { + PsiElement next = parenthesisOwner.getNextSibling(); + while ((next instanceof PsiEmptyExpressionImpl || next instanceof PsiErrorElement || next instanceof PsiWhiteSpace)) { + next = next.getNextSibling(); + } + if (next instanceof PsiExpression) { + return (PsiExpression)next; + } } + return null; + } - if (parameters.getCompletionType() == CompletionType.SMART) { - editor.getCaretModel().moveToOffset(context.getTailOffset()); - } - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - } - }); - } + private static LookupElement createSmartCastElement( + final CompletionParameters parameters, + final boolean overwrite, + final PsiType type + ) { + return AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE.applyPolicy(new LookupElementDecorator( + PsiTypeLookupItem.createLookupItem(type, parameters.getPosition())) { + + @Override + public void handleInsert(@Nonnull InsertionContext context) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.smarttype.casting"); + + final Editor editor = context.getEditor(); + final Document document = editor.getDocument(); + if (overwrite) { + document.deleteString( + context.getSelectionEndOffset(), + context.getOffsetMap().getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) + ); + } + + final CommonCodeStyleSettings csSettings = CompletionStyleUtil.getCodeStyleSettings(context); + final int oldTail = context.getTailOffset(); + context.setTailOffset(RParenthTailType.addRParenth(editor, oldTail, csSettings.SPACE_WITHIN_CAST_PARENTHESES)); + + getDelegate().handleInsert(CompletionUtilCore.newContext(context, getDelegate(), context.getStartOffset(), oldTail)); + + PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(); + if (csSettings.SPACE_AFTER_TYPE_CAST) { + context.setTailOffset(TailType.insertChar(editor, context.getTailOffset(), ' ')); + } + + if (parameters.getCompletionType() == CompletionType.SMART) { + editor.getCaretModel().moveToOffset(context.getTailOffset()); + } + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + }); + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/SuperCalls.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/SuperCalls.java index 34c3265e76..127d9c3ff2 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/SuperCalls.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/SuperCalls.java @@ -18,6 +18,7 @@ import com.intellij.java.impl.codeInsight.completion.scope.CompletionElement; import com.intellij.java.impl.codeInsight.completion.scope.JavaCompletionProcessor; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.language.editor.completion.lookup.*; import consulo.language.psi.PsiElement; import consulo.language.psi.filter.ElementFilter; @@ -27,69 +28,85 @@ import consulo.util.lang.function.Condition; import jakarta.annotation.Nonnull; + import java.util.LinkedHashSet; import java.util.Set; +import java.util.function.Predicate; /** * @author peter */ class SuperCalls { - static Set suggestQualifyingSuperCalls(PsiElement element, - PsiJavaReference javaReference, - ElementFilter elementFilter, - JavaCompletionProcessor.Options options, - Condition nameCondition) { - Set set = new LinkedHashSet<>(); - for (final String className : getContainingClassNames(element)) { - PsiReferenceExpression fakeSuper = JavaCompletionUtil.createReference(className + ".super.rulez", element); - PsiElement leaf = ObjectUtil.assertNotNull(fakeSuper.getReferenceNameElement()); + @RequiredReadAction + static Set suggestQualifyingSuperCalls( + PsiElement element, + PsiJavaReference javaReference, + ElementFilter elementFilter, + JavaCompletionProcessor.Options options, + Predicate nameCondition + ) { + Set set = new LinkedHashSet<>(); + for (String className : getContainingClassNames(element)) { + PsiReferenceExpression fakeSuper = JavaCompletionUtil.createReference(className + ".super.rulez", element); + PsiElement leaf = ObjectUtil.assertNotNull(fakeSuper.getReferenceNameElement()); - JavaCompletionProcessor superProcessor = new JavaCompletionProcessor(leaf, elementFilter, options, nameCondition); - fakeSuper.processVariants(superProcessor); + JavaCompletionProcessor superProcessor = new JavaCompletionProcessor(leaf, elementFilter, options, nameCondition); + fakeSuper.processVariants(superProcessor); - for (CompletionElement completionElement : superProcessor.getResults()) { - for (LookupElement item : JavaCompletionUtil.createLookupElements(completionElement, javaReference)) { - set.add(withQualifiedSuper(className, item)); + for (CompletionElement completionElement : superProcessor.getResults()) { + for (LookupElement item : JavaCompletionUtil.createLookupElements(completionElement, javaReference)) { + set.add(withQualifiedSuper(className, item)); + } + } } - } + return set; } - return set; - } - @Nonnull - private static LookupElement withQualifiedSuper(final String className, LookupElement item) { - return PrioritizedLookupElement.withExplicitProximity(new LookupElementDecorator(item) { + @Nonnull + @RequiredReadAction + private static LookupElement withQualifiedSuper(final String className, LookupElement item) { + return PrioritizedLookupElement.withExplicitProximity( + new LookupElementDecorator(item) { - @Override - public void renderElement(LookupElementPresentation presentation) { - super.renderElement(presentation); - presentation.setItemText(className + ".super." + presentation.getItemText()); - } + @Override + public void renderElement(LookupElementPresentation presentation) { + super.renderElement(presentation); + presentation.setItemText(className + ".super." + presentation.getItemText()); + } - @Override - public void handleInsert(InsertionContext context) { - context.commitDocument(); - PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiJavaCodeReferenceElement.class, false); - if (ref != null) { - context.getDocument().insertString(ref.getTextRange().getStartOffset(), className + "."); - } + @Override + public void handleInsert(InsertionContext context) { + context.commitDocument(); + PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset( + context.getFile(), + context.getStartOffset(), + PsiJavaCodeReferenceElement.class, + false + ); + if (ref != null) { + context.getDocument().insertString(ref.getTextRange().getStartOffset(), className + "."); + } - super.handleInsert(context); - } - }, -1); - } + super.handleInsert(context); + } + }, + -1 + ); + } - private static Set getContainingClassNames(PsiElement position) { - Set result = new LinkedHashSet<>(); - boolean add = false; - while (position != null) { - if (position instanceof PsiAnonymousClass) { - add = true; - } else if (add && position instanceof PsiClass) { - ContainerUtil.addIfNotNull(result, ((PsiClass) position).getName()); - } - position = position.getParent(); + @RequiredReadAction + private static Set getContainingClassNames(PsiElement position) { + Set result = new LinkedHashSet<>(); + boolean add = false; + while (position != null) { + if (position instanceof PsiAnonymousClass) { + add = true; + } + else if (add && position instanceof PsiClass psiClass) { + ContainerUtil.addIfNotNull(result, psiClass.getName()); + } + position = position.getParent(); + } + return result; } - return result; - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/scope/JavaCompletionProcessor.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/scope/JavaCompletionProcessor.java index beb6bfb1e3..71f5d51f97 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/scope/JavaCompletionProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/scope/JavaCompletionProcessor.java @@ -26,6 +26,7 @@ import com.intellij.java.language.psi.scope.JavaScopeProcessorEvent; import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.language.psi.*; import consulo.language.psi.filter.ElementFilter; import consulo.language.psi.resolve.BaseScopeProcessor; @@ -34,325 +35,328 @@ import consulo.util.collection.ContainerUtil; import consulo.util.dataholder.Key; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.Condition; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; /** - * User: ik - * Date: 20.01.2003 - * Time: 16:13:27 + * @author ik + * @since 2003-01-20 */ public class JavaCompletionProcessor extends BaseScopeProcessor implements ElementClassHint { - private final boolean myInJavaDoc; - private boolean myStatic; - private PsiElement myDeclarationHolder; - private final Map myResults = new LinkedHashMap<>(); - private final Set mySecondRateResults = ContainerUtil.newIdentityTroveSet(); - private final Set myShadowedNames = new HashSet<>(); - private final Set myCurrentScopeMethodNames = new HashSet<>(); - private final Set myFinishedScopesMethodNames = new HashSet<>(); - private final PsiElement myElement; - private final PsiElement myScope; - private final ElementFilter myFilter; - private boolean myMembersFlag; - private boolean myQualified; - private PsiType myQualifierType; - private PsiClass myQualifierClass; - private final Condition myMatcher; - private final Options myOptions; - private final boolean myAllowStaticWithInstanceQualifier; - - public JavaCompletionProcessor(@Nonnull PsiElement element, ElementFilter filter, Options options, @Nonnull Condition nameCondition) { - myOptions = options; - myElement = element; - myMatcher = nameCondition; - myFilter = filter; - PsiElement scope = element; - myInJavaDoc = JavaResolveUtil.isInJavaDoc(myElement); - if (myInJavaDoc) { - myMembersFlag = true; - } - while (scope != null && !(scope instanceof PsiFile) && !(scope instanceof PsiClass)) { - scope = scope.getContext(); - } - myScope = scope; - - PsiElement elementParent = element.getContext(); - if (elementParent instanceof PsiReferenceExpression) { - PsiExpression qualifier = ((PsiReferenceExpression) elementParent).getQualifierExpression(); - if (qualifier instanceof PsiSuperExpression) { - final PsiJavaCodeReferenceElement qSuper = ((PsiSuperExpression) qualifier).getQualifier(); - if (qSuper == null) { - myQualifierClass = JavaResolveUtil.getContextClass(myElement); - } else { - final PsiElement target = qSuper.resolve(); - myQualifierClass = target instanceof PsiClass ? (PsiClass) target : null; + private final boolean myInJavaDoc; + private boolean myStatic; + private PsiElement myDeclarationHolder; + private final Map myResults = new LinkedHashMap<>(); + private final Set mySecondRateResults = ContainerUtil.newIdentityTroveSet(); + private final Set myShadowedNames = new HashSet<>(); + private final Set myCurrentScopeMethodNames = new HashSet<>(); + private final Set myFinishedScopesMethodNames = new HashSet<>(); + private final PsiElement myElement; + private final PsiElement myScope; + private final ElementFilter myFilter; + private boolean myMembersFlag; + private boolean myQualified; + private PsiType myQualifierType; + private PsiClass myQualifierClass; + private final Predicate myMatcher; + private final Options myOptions; + private final boolean myAllowStaticWithInstanceQualifier; + + @RequiredReadAction + public JavaCompletionProcessor( + @Nonnull PsiElement element, + ElementFilter filter, + Options options, + @Nonnull Predicate nameCondition + ) { + myOptions = options; + myElement = element; + myMatcher = nameCondition; + myFilter = filter; + PsiElement scope = element; + myInJavaDoc = JavaResolveUtil.isInJavaDoc(myElement); + if (myInJavaDoc) { + myMembersFlag = true; } - } else if (qualifier != null) { - myQualified = true; - setQualifierType(qualifier.getType()); - if (myQualifierType == null && qualifier instanceof PsiJavaCodeReferenceElement) { - final PsiElement target = ((PsiJavaCodeReferenceElement) qualifier).resolve(); - if (target instanceof PsiClass) { - myQualifierClass = (PsiClass) target; - } + while (scope != null && !(scope instanceof PsiFile) && !(scope instanceof PsiClass)) { + scope = scope.getContext(); } - } else { - myQualifierClass = JavaResolveUtil.getContextClass(myElement); - } + myScope = scope; + + if (element.getContext() instanceof PsiReferenceExpression refExpr) { + PsiExpression qualifier = refExpr.getQualifierExpression(); + if (qualifier instanceof PsiSuperExpression superExpr) { + PsiJavaCodeReferenceElement qSuper = superExpr.getQualifier(); + if (qSuper == null) { + myQualifierClass = JavaResolveUtil.getContextClass(myElement); + } + else { + PsiElement target = qSuper.resolve(); + myQualifierClass = target instanceof PsiClass psiClass ? psiClass : null; + } + } + else if (qualifier != null) { + myQualified = true; + setQualifierType(qualifier.getType()); + if (myQualifierType == null && qualifier instanceof PsiJavaCodeReferenceElement javaCodeRef + && javaCodeRef.resolve() instanceof PsiClass psiClass) { + myQualifierClass = psiClass; + } + } + else { + myQualifierClass = JavaResolveUtil.getContextClass(myElement); + } + } + if (myQualifierClass != null && myQualifierType == null) { + myQualifierType = JavaPsiFacade.getElementFactory(element.getProject()).createType(myQualifierClass); + } + + myAllowStaticWithInstanceQualifier = !options.filterStaticAfterInstance + || SuppressManager.getInstance().isSuppressedFor(element, AccessStaticViaInstanceBase.ACCESS_STATIC_VIA_INSTANCE); } - if (myQualifierClass != null && myQualifierType == null) { - myQualifierType = JavaPsiFacade.getElementFactory(element.getProject()).createType(myQualifierClass); + + @Override + public void handleEvent(@Nonnull Event event, Object associated) { + if (event == JavaScopeProcessorEvent.START_STATIC) { + myStatic = true; + } + if (event == JavaScopeProcessorEvent.CHANGE_LEVEL) { + myMembersFlag = true; + myFinishedScopesMethodNames.addAll(myCurrentScopeMethodNames); + myCurrentScopeMethodNames.clear(); + } + if (event == JavaScopeProcessorEvent.SET_CURRENT_FILE_CONTEXT) { + myDeclarationHolder = (PsiElement)associated; + } } - myAllowStaticWithInstanceQualifier = !options.filterStaticAfterInstance || SuppressManager.getInstance().isSuppressedFor(element, AccessStaticViaInstanceBase.ACCESS_STATIC_VIA_INSTANCE); - } + @Override + @RequiredReadAction + public boolean execute(@Nonnull PsiElement element, @Nonnull ResolveState state) { + if (element instanceof PsiPackage psiPackage && !isQualifiedContext()) { + if (myScope instanceof PsiClass) { + return true; + } + if (psiPackage.getQualifiedName().contains(".") + && PsiTreeUtil.getParentOfType(myElement, PsiImportStatementBase.class) != null) { + return true; + } + } - @Override - public void handleEvent(@Nonnull Event event, Object associated) { - if (event == JavaScopeProcessorEvent.START_STATIC) { - myStatic = true; - } - if (event == JavaScopeProcessorEvent.CHANGE_LEVEL) { - myMembersFlag = true; - myFinishedScopesMethodNames.addAll(myCurrentScopeMethodNames); - myCurrentScopeMethodNames.clear(); - } - if (event == JavaScopeProcessorEvent.SET_CURRENT_FILE_CONTEXT) { - myDeclarationHolder = (PsiElement) associated; - } - } + if (element instanceof PsiMethod method && PsiTypesUtil.isGetClass(method) && PsiUtil.isLanguageLevel5OrHigher(myElement)) { + PsiType patchedType = PsiTypesUtil.createJavaLangClassType(myElement, myQualifierType, false); + if (patchedType != null) { + element = new LightMethodBuilder(element.getManager(), method.getName()). + addModifier(PsiModifier.PUBLIC). + setMethodReturnType(patchedType). + setContainingClass(method.getContainingClass()); + } + } - @Override - public boolean execute(@Nonnull PsiElement element, @Nonnull ResolveState state) { - if (element instanceof PsiPackage && !isQualifiedContext()) { - if (myScope instanceof PsiClass) { - return true; - } - if (((PsiPackage) element).getQualifiedName().contains(".") && PsiTreeUtil.getParentOfType(myElement, PsiImportStatementBase.class) != null) { - return true; - } - } + if (element instanceof PsiVariable variable) { + String name = variable.getName(); + if (myShadowedNames.contains(name)) { + return true; + } + if (element instanceof PsiLocalVariable || element instanceof PsiParameter) { + myShadowedNames.add(name); + } + } - if (element instanceof PsiMethod) { - PsiMethod method = (PsiMethod) element; - if (PsiTypesUtil.isGetClass(method) && PsiUtil.isLanguageLevel5OrHigher(myElement)) { - PsiType patchedType = PsiTypesUtil.createJavaLangClassType(myElement, myQualifierType, false); - if (patchedType != null) { - element = new LightMethodBuilder(element.getManager(), method.getName()). - addModifier(PsiModifier.PUBLIC). - setMethodReturnType(patchedType). - setContainingClass(method.getContainingClass()); + if (element instanceof PsiMethod method) { + myCurrentScopeMethodNames.add(method.getName()); + } + + if (!satisfies(element, state) || !isAccessible(element)) { + return true; + } + + StaticProblem sp = myElement.getParent() instanceof PsiMethodReferenceExpression ? StaticProblem.none : getStaticProblem(element); + if (sp == StaticProblem.instanceAfterStatic) { + return true; + } + + CompletionElement completion = new CompletionElement(element, state.get(PsiSubstitutor.KEY), getCallQualifierText(element)); + CompletionElement prev = myResults.get(completion); + if (prev == null || completion.isMoreSpecificThan(prev)) { + myResults.put(completion, completion); + if (sp == StaticProblem.staticAfterInstance) { + mySecondRateResults.add(completion); + } } - } - } - if (element instanceof PsiVariable) { - String name = ((PsiVariable) element).getName(); - if (myShadowedNames.contains(name)) { return true; - } - if (element instanceof PsiLocalVariable || element instanceof PsiParameter) { - myShadowedNames.add(name); - } } - if (element instanceof PsiMethod) { - myCurrentScopeMethodNames.add(((PsiMethod) element).getName()); + @Nonnull + @RequiredReadAction + private String getCallQualifierText(@Nonnull PsiElement element) { + if (element instanceof PsiMethod method) { + if (myFinishedScopesMethodNames.contains(method.getName())) { + String className = myDeclarationHolder instanceof PsiClass psiClass ? psiClass.getName() : null; + if (className != null) { + return className + (method.isStatic() ? "." : ".this."); + } + } + } + return ""; } - if (!satisfies(element, state) || !isAccessible(element)) { - return true; + private boolean isQualifiedContext() { + PsiElement elementParent = myElement.getParent(); + return elementParent instanceof PsiQualifiedReference qualifiedRef && qualifiedRef.getQualifier() != null; } - StaticProblem sp = myElement.getParent() instanceof PsiMethodReferenceExpression ? StaticProblem.none : getStaticProblem(element); - if (sp == StaticProblem.instanceAfterStatic) { - return true; + private StaticProblem getStaticProblem(PsiElement element) { + if (myOptions.showInstanceInStaticContext && !isQualifiedContext()) { + return StaticProblem.none; + } + if (element instanceof PsiModifierListOwner modifierListOwner) { + if (myStatic) { + if (!(element instanceof PsiClass) && !modifierListOwner.hasModifierProperty(PsiModifier.STATIC)) { + // we don't need non static method in static context. + return StaticProblem.instanceAfterStatic; + } + } + else if (!myAllowStaticWithInstanceQualifier && modifierListOwner.hasModifierProperty(PsiModifier.STATIC) && !myMembersFlag) { + // according settings we don't need to process such fields/methods + return StaticProblem.staticAfterInstance; + } + } + return StaticProblem.none; } - CompletionElement completion = new CompletionElement(element, state.get(PsiSubstitutor.KEY), getCallQualifierText(element)); - CompletionElement prev = myResults.get(completion); - if (prev == null || completion.isMoreSpecificThan(prev)) { - myResults.put(completion, completion); - if (sp == StaticProblem.staticAfterInstance) { - mySecondRateResults.add(completion); - } + public boolean satisfies(@Nonnull PsiElement element, @Nonnull ResolveState state) { + String name = PsiUtilCore.getName(element); + return name != null + && StringUtil.isNotEmpty(name) + && myMatcher.test(name) + && myFilter.isClassAcceptable(element.getClass()) + && myFilter.isAcceptable(new CandidateInfo(element, state.get(PsiSubstitutor.KEY)), myElement); } - return true; - } - - @Nonnull - private String getCallQualifierText(@Nonnull PsiElement element) { - if (element instanceof PsiMethod) { - PsiMethod method = (PsiMethod) element; - if (myFinishedScopesMethodNames.contains(method.getName())) { - String className = myDeclarationHolder instanceof PsiClass ? ((PsiClass) myDeclarationHolder).getName() : null; - if (className != null) { - return className + (method.hasModifierProperty(PsiModifier.STATIC) ? "." : ".this."); - } - } + public void setQualifierType(@Nullable PsiType qualifierType) { + myQualifierType = qualifierType; + myQualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifierType); } - return ""; - } - - private boolean isQualifiedContext() { - final PsiElement elementParent = myElement.getParent(); - return elementParent instanceof PsiQualifiedReference && ((PsiQualifiedReference) elementParent).getQualifier() != null; - } - private StaticProblem getStaticProblem(PsiElement element) { - if (myOptions.showInstanceInStaticContext && !isQualifiedContext()) { - return StaticProblem.none; + @Nullable + public PsiType getQualifierType() { + return myQualifierType; } - if (element instanceof PsiModifierListOwner) { - PsiModifierListOwner modifierListOwner = (PsiModifierListOwner) element; - if (myStatic) { - if (!(element instanceof PsiClass) && !modifierListOwner.hasModifierProperty(PsiModifier.STATIC)) { - // we don't need non static method in static context. - return StaticProblem.instanceAfterStatic; + + public boolean isAccessible(@Nullable PsiElement element) { + // if checkAccess is false, we only show inaccessible source elements because their access modifiers can be changed later by the user. + // compiled element can't be changed so we don't pollute the completion with them. In Javadoc, everything is allowed. + if (!myOptions.checkAccess && myInJavaDoc) { + return true; } - } else { - if (!myAllowStaticWithInstanceQualifier && modifierListOwner.hasModifierProperty(PsiModifier.STATIC) && !myMembersFlag) { - // according settings we don't need to process such fields/methods - return StaticProblem.staticAfterInstance; + if (!(element instanceof PsiMember member)) { + return true; } - } - } - return StaticProblem.none; - } - public boolean satisfies(@Nonnull PsiElement element, @Nonnull ResolveState state) { - final String name = PsiUtilCore.getName(element); - if (name != null && StringUtil.isNotEmpty(name) && myMatcher.value(name)) { - if (myFilter.isClassAcceptable(element.getClass()) && myFilter.isAcceptable(new CandidateInfo(element, state.get(PsiSubstitutor.KEY)), myElement)) { - return true; - } - } - return false; - } - - public void setQualifierType(@Nullable PsiType qualifierType) { - myQualifierType = qualifierType; - myQualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifierType); - } - - @Nullable - public PsiType getQualifierType() { - return myQualifierType; - } - - public boolean isAccessible(@Nullable final PsiElement element) { - // if checkAccess is false, we only show inaccessible source elements because their access modifiers can be changed later by the user. - // compiled element can't be changed so we don't pollute the completion with them. In Javadoc, everything is allowed. - if (!myOptions.checkAccess && myInJavaDoc) { - return true; - } - if (!(element instanceof PsiMember)) { - return true; + PsiClass accessObjectClass = myQualified ? myQualifierClass : null; + //noinspection SimplifiableIfStatement + if (JavaPsiFacade.getInstance(element.getProject()).getResolveHelper() + .isAccessible(member, member.getModifierList(), myElement, accessObjectClass, myDeclarationHolder)) { + return true; + } + return !myOptions.checkAccess && !(element instanceof PsiCompiledElement); } - PsiMember member = (PsiMember) element; - PsiClass accessObjectClass = myQualified ? myQualifierClass : null; - if (JavaPsiFacade.getInstance(element.getProject()).getResolveHelper().isAccessible(member, member.getModifierList(), myElement, accessObjectClass, myDeclarationHolder)) { - return true; + public void setCompletionElements(@Nonnull Object[] elements) { + for (Object element : elements) { + CompletionElement completion = new CompletionElement(element, PsiSubstitutor.EMPTY); + myResults.put(completion, completion); + } } - return !myOptions.checkAccess && !(element instanceof PsiCompiledElement); - } - public void setCompletionElements(@Nonnull Object[] elements) { - for (Object element : elements) { - CompletionElement completion = new CompletionElement(element, PsiSubstitutor.EMPTY); - myResults.put(completion, completion); + public Iterable getResults() { + if (mySecondRateResults.size() == myResults.size()) { + return mySecondRateResults; + } + return ContainerUtil.filter(myResults.values(), element -> !mySecondRateResults.contains(element)); } - } - public Iterable getResults() { - if (mySecondRateResults.size() == myResults.size()) { - return mySecondRateResults; + public void clear() { + myResults.clear(); + mySecondRateResults.clear(); } - return ContainerUtil.filter(myResults.values(), element -> !mySecondRateResults.contains(element)); - } - public void clear() { - myResults.clear(); - mySecondRateResults.clear(); - } + @Override + public boolean shouldProcess(DeclarationKind kind) { + switch (kind) { + case CLASS: + return myFilter.isClassAcceptable(PsiClass.class); - @Override - public boolean shouldProcess(DeclarationKind kind) { - switch (kind) { - case CLASS: - return myFilter.isClassAcceptable(PsiClass.class); + case FIELD: + return myFilter.isClassAcceptable(PsiField.class); - case FIELD: - return myFilter.isClassAcceptable(PsiField.class); + case METHOD: + return myFilter.isClassAcceptable(PsiMethod.class); - case METHOD: - return myFilter.isClassAcceptable(PsiMethod.class); + case PACKAGE: + return myFilter.isClassAcceptable(PsiPackage.class); - case PACKAGE: - return myFilter.isClassAcceptable(PsiPackage.class); + case VARIABLE: + return myFilter.isClassAcceptable(PsiVariable.class); - case VARIABLE: - return myFilter.isClassAcceptable(PsiVariable.class); + case ENUM_CONST: + return myFilter.isClassAcceptable(PsiEnumConstant.class); + } - case ENUM_CONST: - return myFilter.isClassAcceptable(PsiEnumConstant.class); + return false; } - return false; - } + @Override + public T getHint(@Nonnull Key hintKey) { + if (hintKey == ElementClassHint.KEY) { + //noinspection unchecked + return (T)this; + } + if (hintKey == JavaCompletionHints.NAME_FILTER) { + //noinspection unchecked + return (T)myMatcher; + } - @Override - public T getHint(@Nonnull Key hintKey) { - if (hintKey == ElementClassHint.KEY) { - //noinspection unchecked - return (T) this; + return super.getHint(hintKey); } - if (hintKey == JavaCompletionHints.NAME_FILTER) { - //noinspection unchecked - return (T) myMatcher; - } - - return super.getHint(hintKey); - } - public static class Options { - public static final Options DEFAULT_OPTIONS = new Options(true, true, false); - public static final Options CHECK_NOTHING = new Options(false, false, false); - final boolean checkAccess; - final boolean filterStaticAfterInstance; - final boolean showInstanceInStaticContext; + public static class Options { + public static final Options DEFAULT_OPTIONS = new Options(true, true, false); + public static final Options CHECK_NOTHING = new Options(false, false, false); + final boolean checkAccess; + final boolean filterStaticAfterInstance; + final boolean showInstanceInStaticContext; + + private Options(boolean checkAccess, boolean filterStaticAfterInstance, boolean showInstanceInStaticContext) { + this.checkAccess = checkAccess; + this.filterStaticAfterInstance = filterStaticAfterInstance; + this.showInstanceInStaticContext = showInstanceInStaticContext; + } - private Options(boolean checkAccess, boolean filterStaticAfterInstance, boolean showInstanceInStaticContext) { - this.checkAccess = checkAccess; - this.filterStaticAfterInstance = filterStaticAfterInstance; - this.showInstanceInStaticContext = showInstanceInStaticContext; - } + public Options withCheckAccess(boolean checkAccess) { + return new Options(checkAccess, filterStaticAfterInstance, showInstanceInStaticContext); + } - public Options withCheckAccess(boolean checkAccess) { - return new Options(checkAccess, filterStaticAfterInstance, showInstanceInStaticContext); - } + public Options withFilterStaticAfterInstance(boolean filterStaticAfterInstance) { + return new Options(checkAccess, filterStaticAfterInstance, showInstanceInStaticContext); + } - public Options withFilterStaticAfterInstance(boolean filterStaticAfterInstance) { - return new Options(checkAccess, filterStaticAfterInstance, showInstanceInStaticContext); + public Options withShowInstanceInStaticContext(boolean showInstanceInStaticContext) { + return new Options(checkAccess, filterStaticAfterInstance, showInstanceInStaticContext); + } } - public Options withShowInstanceInStaticContext(boolean showInstanceInStaticContext) { - return new Options(checkAccess, filterStaticAfterInstance, showInstanceInStaticContext); + private enum StaticProblem { + none, + staticAfterInstance, + instanceAfterStatic } - } - - private enum StaticProblem { - none, - staticAfterInstance, - instanceAfterStatic - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/JavaLineMarkerProvider.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/JavaLineMarkerProvider.java index 36df331eed..b8b980d211 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/JavaLineMarkerProvider.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/JavaLineMarkerProvider.java @@ -28,19 +28,17 @@ import com.intellij.java.language.psi.util.PsiUtil; import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.application.AllIcons; -import consulo.application.ApplicationManager; import consulo.application.progress.ProgressIndicator; import consulo.application.progress.ProgressIndicatorProvider; import consulo.application.progress.ProgressManager; import consulo.application.util.concurrent.JobLauncher; -import consulo.application.util.function.Computable; import consulo.codeEditor.markup.GutterIconRenderer; import consulo.colorScheme.EditorColorsManager; import consulo.document.Document; import consulo.document.util.TextRange; -import consulo.java.impl.JavaBundle; import consulo.java.language.impl.icon.JavaPsiImplIconGroup; +import consulo.java.language.module.util.JavaClassNames; +import consulo.java.localize.JavaLocalize; import consulo.language.Language; import consulo.language.editor.DaemonCodeAnalyzerSettings; import consulo.language.editor.Pass; @@ -52,385 +50,455 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.PsiNameIdentifierOwner; +import consulo.localize.LocalizeValue; +import consulo.platform.base.icon.PlatformIconGroup; +import consulo.ui.UIAccess; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.action.IdeActions; import consulo.ui.image.Image; import consulo.util.collection.ContainerUtil; import consulo.util.collection.MultiMap; import consulo.util.lang.StringUtil; +import jakarta.annotation.Nonnull; import jakarta.inject.Inject; -import jakarta.annotation.Nonnull; import java.util.*; import java.util.function.Function; +import java.util.function.Supplier; @ExtensionImpl public class JavaLineMarkerProvider extends LineMarkerProviderDescriptor { - public static final Option LAMBDA_OPTION = new Option("java.lambda", JavaBundle.message("title.lambda"), AllIcons.Gutter.ImplementingFunctional) { - @Override - public boolean isEnabledByDefault() { - return false; - } - }; - - private final Option myOverriddenOption = new Option("java.overridden", JavaBundle.message("gutter.overridden.method"), AllIcons.Gutter.OverridenMethod); - private final Option myImplementedOption = new Option("java.implemented", JavaBundle.message("gutter.implemented.method"), AllIcons.Gutter.ImplementedMethod); - private final Option myOverridingOption = new Option("java.overriding", JavaBundle.message("gutter.overriding.method"), AllIcons.Gutter.OverridingMethod); - private final Option myImplementingOption = new Option("java.implementing", JavaBundle.message("gutter.implementing.method"), AllIcons.Gutter.ImplementingMethod); - private final Option mySiblingsOption = new Option("java.sibling.inherited", JavaBundle.message("gutter.sibling.inherited.method"), AllIcons.Gutter.SiblingInheritedMethod); - private final Option myServiceOption = new Option("java.service", JavaBundle.message("gutter.service"), JavaPsiImplIconGroup.gutterJava9service()); - - protected final DaemonCodeAnalyzerSettings myDaemonSettings; - protected final EditorColorsManager myColorsManager; - - @Inject - public JavaLineMarkerProvider(DaemonCodeAnalyzerSettings daemonSettings, EditorColorsManager colorsManager) { - myDaemonSettings = daemonSettings; - myColorsManager = colorsManager; - } - - @RequiredReadAction - @Override - public LineMarkerInfo getLineMarkerInfo(final @Nonnull PsiElement element) { - PsiElement parent = element.getParent(); - if (element instanceof PsiIdentifier && parent instanceof PsiMethod) { - if (!myOverridingOption.isEnabled() && !myImplementingOption.isEnabled()) { - return null; - } - PsiMethod method = (PsiMethod) parent; - MethodSignatureBackedByPsiMethod superSignature = SuperMethodsSearch.search(method, null, true, false).findFirst(); - if (superSignature != null) { - boolean overrides = - method.hasModifierProperty(PsiModifier.ABSTRACT) == superSignature.getMethod().hasModifierProperty(PsiModifier.ABSTRACT); - - final Image icon; - if (overrides) { - if (!myOverridingOption.isEnabled()) { - return null; - } - icon = AllIcons.Gutter.OverridingMethod; - } else { - if (!myImplementingOption.isEnabled()) { - return null; - } - icon = AllIcons.Gutter.ImplementingMethod; - } - return createSuperMethodLineMarkerInfo(element, icon); - } - } - // in case of ()->{}, anchor to "->" - // in case of (xxx)->{}, anchor to "->" - // in case of Type::method, anchor to "method" - if (LAMBDA_OPTION.isEnabled() && - parent instanceof PsiFunctionalExpression && - (element instanceof PsiJavaToken && ((PsiJavaToken) element).getTokenType() == JavaTokenType.ARROW && parent instanceof PsiLambdaExpression || - element instanceof PsiIdentifier && parent instanceof PsiMethodReferenceExpression && ((PsiMethodReferenceExpression) parent).getReferenceNameElement() == element) - ) { - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(parent); - if (interfaceMethod != null) { - return createSuperMethodLineMarkerInfo(element, AllIcons.Gutter.ImplementingFunctional); - } + public static final Option LAMBDA_OPTION = + new Option("java.lambda", JavaLocalize.titleLambda().get(), PlatformIconGroup.gutterImplementingfunctionalinterface()) { + @Override + public boolean isEnabledByDefault() { + return false; + } + }; + + private final Option myOverriddenOption = + new Option("java.overridden", JavaLocalize.gutterOverriddenMethod().get(), PlatformIconGroup.gutterOverridenmethod()); + private final Option myImplementedOption = + new Option("java.implemented", JavaLocalize.gutterImplementedMethod().get(), PlatformIconGroup.gutterImplementedmethod()); + private final Option myOverridingOption = + new Option("java.overriding", JavaLocalize.gutterOverridingMethod().get(), PlatformIconGroup.gutterOverridingmethod()); + private final Option myImplementingOption = + new Option("java.implementing", JavaLocalize.gutterImplementingMethod().get(), PlatformIconGroup.gutterImplementingmethod()); + private final Option mySiblingsOption = + new Option( + "java.sibling.inherited", + JavaLocalize.gutterSiblingInheritedMethod().get(), + PlatformIconGroup.gutterSiblinginheritedmethod() + ); + private final Option myServiceOption = + new Option("java.service", JavaLocalize.gutterService().get(), JavaPsiImplIconGroup.gutterJava9service()); + + protected final DaemonCodeAnalyzerSettings myDaemonSettings; + protected final EditorColorsManager myColorsManager; + + @Inject + public JavaLineMarkerProvider(DaemonCodeAnalyzerSettings daemonSettings, EditorColorsManager colorsManager) { + myDaemonSettings = daemonSettings; + myColorsManager = colorsManager; } - if (myDaemonSettings.SHOW_METHOD_SEPARATORS && element.getFirstChild() == null) { - PsiElement element1 = element; - boolean isMember = false; - while (element1 != null && !(element1 instanceof PsiFile) && element1.getPrevSibling() == null) { - element1 = element1.getParent(); - if (element1 instanceof PsiMember) { - isMember = true; - break; + @RequiredReadAction + @Override + public LineMarkerInfo getLineMarkerInfo(@Nonnull PsiElement element) { + PsiElement parent = element.getParent(); + if (element instanceof PsiIdentifier && parent instanceof PsiMethod) { + if (!myOverridingOption.isEnabled() && !myImplementingOption.isEnabled()) { + return null; + } + PsiMethod method = (PsiMethod)parent; + MethodSignatureBackedByPsiMethod superSignature = SuperMethodsSearch.search(method, null, true, false).findFirst(); + if (superSignature != null) { + boolean overrides = method.isAbstract() == + superSignature.getMethod().isAbstract(); + + Image icon; + if (overrides) { + if (!myOverridingOption.isEnabled()) { + return null; + } + icon = PlatformIconGroup.gutterOverridingmethod(); + } + else { + if (!myImplementingOption.isEnabled()) { + return null; + } + icon = PlatformIconGroup.gutterImplementingmethod(); + } + return createSuperMethodLineMarkerInfo(element, icon); + } } - } - if (isMember && !(element1 instanceof PsiAnonymousClass || element1.getParent() instanceof PsiAnonymousClass)) { - PsiFile file = element1.getContainingFile(); - Document document = file == null ? null : PsiDocumentManager.getInstance(file.getProject()).getLastCommittedDocument(file); - boolean drawSeparator = false; - - if (document != null) { - CharSequence documentChars = document.getCharsSequence(); - int category = getCategory(element1, documentChars); - for (PsiElement child = element1.getPrevSibling(); child != null; child = child.getPrevSibling()) { - int category1 = getCategory(child, documentChars); - if (category1 == 0) { - continue; + // in case of ()->{}, anchor to "->" + // in case of (xxx)->{}, anchor to "->" + // in case of Type::method, anchor to "method" + if (LAMBDA_OPTION.isEnabled() + && parent instanceof PsiFunctionalExpression + && (element instanceof PsiJavaToken javaToken + && javaToken.getTokenType() == JavaTokenType.ARROW + && parent instanceof PsiLambdaExpression + || element instanceof PsiIdentifier + && parent instanceof PsiMethodReferenceExpression methodRefExpr + && methodRefExpr.getReferenceNameElement() == element) + ) { + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(parent); + if (interfaceMethod != null) { + return createSuperMethodLineMarkerInfo(element, PlatformIconGroup.gutterImplementingfunctionalinterface()); } - drawSeparator = category != 1 || category1 != 1; - break; - } } - if (drawSeparator) { - return LineMarkerInfo.createMethodSeparatorLineMarker(element, myColorsManager); + if (myDaemonSettings.SHOW_METHOD_SEPARATORS && element.getFirstChild() == null) { + PsiElement element1 = element; + boolean isMember = false; + while (element1 != null && !(element1 instanceof PsiFile) && element1.getPrevSibling() == null) { + element1 = element1.getParent(); + if (element1 instanceof PsiMember) { + isMember = true; + break; + } + } + if (isMember && !(element1 instanceof PsiAnonymousClass || element1.getParent() instanceof PsiAnonymousClass)) { + PsiFile file = element1.getContainingFile(); + Document document = file == null ? null : PsiDocumentManager.getInstance(file.getProject()).getLastCommittedDocument(file); + boolean drawSeparator = false; + + if (document != null) { + CharSequence documentChars = document.getCharsSequence(); + int category = getCategory(element1, documentChars); + for (PsiElement child = element1.getPrevSibling(); child != null; child = child.getPrevSibling()) { + int category1 = getCategory(child, documentChars); + if (category1 == 0) { + continue; + } + drawSeparator = category != 1 || category1 != 1; + break; + } + } + + if (drawSeparator) { + return LineMarkerInfo.createMethodSeparatorLineMarker(element, myColorsManager); + } + } } - } - } - return null; - } - - @Nonnull - private static LineMarkerInfo createSuperMethodLineMarkerInfo(@Nonnull PsiElement name, @Nonnull Image icon) { - ArrowUpLineMarkerInfo info = new ArrowUpLineMarkerInfo(name, icon, MarkerType.OVERRIDING_METHOD); - return NavigateAction.setNavigateAction(info, "Go to super method", IdeActions.ACTION_GOTO_SUPER); - } - - private static int getCategory(@Nonnull PsiElement element, @Nonnull CharSequence documentChars) { - if (element instanceof PsiField || element instanceof PsiTypeParameter) { - return 1; - } - if (element instanceof PsiClass || element instanceof PsiClassInitializer) { - return 2; + return null; } - if (element instanceof PsiMethod) { - if (((PsiMethod) element).hasModifierProperty(PsiModifier.ABSTRACT)) { - return 1; - } - TextRange textRange = element.getTextRange(); - int start = textRange.getStartOffset(); - int end = Math.min(documentChars.length(), textRange.getEndOffset()); - int crlf = StringUtil.getLineBreakCount(documentChars.subSequence(start, end)); - return crlf == 0 ? 1 : 2; + + @Nonnull + @RequiredReadAction + private static LineMarkerInfo createSuperMethodLineMarkerInfo(@Nonnull PsiElement name, @Nonnull Image icon) { + ArrowUpLineMarkerInfo info = new ArrowUpLineMarkerInfo(name, icon, MarkerType.OVERRIDING_METHOD); + return NavigateAction.setNavigateAction(info, "Go to super method", IdeActions.ACTION_GOTO_SUPER); } - return 0; - } - - @Override - public void collectSlowLineMarkers(@Nonnull final List elements, @Nonnull final Collection result) { - ApplicationManager.getApplication().assertReadAccessAllowed(); - - List>>> tasks = new ArrayList<>(); - - MultiMap canBeOverridden = MultiMap.createSet(); - MultiMap canHaveSiblings = MultiMap.create(); - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < elements.size(); i++) { - PsiElement element = elements.get(i); - ProgressManager.checkCanceled(); - if (!(element instanceof PsiIdentifier)) { - continue; - } - PsiElement parent = element.getParent(); - if (parent instanceof PsiMethod) { - final PsiMethod method = (PsiMethod) parent; - PsiClass containingClass = method.getContainingClass(); - if (containingClass != null && PsiUtil.canBeOverridden(method)) { - canBeOverridden.putValue(containingClass, method); - } - if (mySiblingsOption.isEnabled() && FindSuperElementsHelper.canHaveSiblingSuper(method, containingClass)) { - canHaveSiblings.putValue(containingClass, method); + + @RequiredReadAction + private static int getCategory(@Nonnull PsiElement element, @Nonnull CharSequence documentChars) { + if (element instanceof PsiField || element instanceof PsiTypeParameter) { + return 1; } - if (JavaServiceUtil.isServiceProviderMethod(method)) { - tasks.add(() -> JavaServiceUtil.collectServiceProviderMethod(method)); + if (element instanceof PsiClass || element instanceof PsiClassInitializer) { + return 2; } - } else if (parent instanceof PsiClass && !(parent instanceof PsiTypeParameter)) { - tasks.add(() -> collectInheritingClasses((PsiClass) parent)); - tasks.add(() -> JavaServiceUtil.collectServiceImplementationClass((PsiClass) parent)); - } else if (parent instanceof PsiReferenceExpression && parent.getParent() instanceof PsiMethodCallExpression) { - PsiMethodCallExpression grandParent = (PsiMethodCallExpression) parent.getParent(); - if (JavaServiceUtil.SERVICE_LOADER_LOAD.test(grandParent)) { - tasks.add(() -> JavaServiceUtil.collectServiceLoaderLoadCall((PsiIdentifier) element, grandParent)); + if (element instanceof PsiMethod method) { + if (method.isAbstract()) { + return 1; + } + TextRange textRange = element.getTextRange(); + int start = textRange.getStartOffset(); + int end = Math.min(documentChars.length(), textRange.getEndOffset()); + int crlf = StringUtil.getLineBreakCount(documentChars.subSequence(start, end)); + return crlf == 0 ? 1 : 2; } - } + return 0; } - for (Map.Entry> entry : canBeOverridden.entrySet()) { - PsiClass psiClass = entry.getKey(); - Set methods = (Set) entry.getValue(); - tasks.add(() -> collectOverridingMethods(methods, psiClass)); - } - for (PsiClass psiClass : canHaveSiblings.keySet()) { - Collection methods = canHaveSiblings.get(psiClass); - tasks.add(() -> collectSiblingInheritedMethods(methods)); - } + @Override + @RequiredUIAccess + public void collectSlowLineMarkers(@Nonnull List elements, @Nonnull Collection result) { + UIAccess.assertIsUIThread(); + + List>>> tasks = new ArrayList<>(); + + MultiMap canBeOverridden = MultiMap.createSet(); + MultiMap canHaveSiblings = MultiMap.create(); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < elements.size(); i++) { + ProgressManager.checkCanceled(); + if (!(elements.get(i) instanceof PsiIdentifier identifier)) { + continue; + } + PsiElement parent = identifier.getParent(); + if (parent instanceof PsiMethod method) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass != null && PsiUtil.canBeOverridden(method)) { + canBeOverridden.putValue(containingClass, method); + } + if (mySiblingsOption.isEnabled() && FindSuperElementsHelper.canHaveSiblingSuper(method, containingClass)) { + canHaveSiblings.putValue(containingClass, method); + } + if (JavaServiceUtil.isServiceProviderMethod(method)) { + tasks.add(() -> JavaServiceUtil.collectServiceProviderMethod(method)); + } + } + else if (parent instanceof PsiClass psiClass && !(parent instanceof PsiTypeParameter)) { + tasks.add(() -> collectInheritingClasses(psiClass)); + tasks.add(() -> JavaServiceUtil.collectServiceImplementationClass(psiClass)); + } + else if (parent instanceof PsiReferenceExpression && parent.getParent() instanceof PsiMethodCallExpression methodCall) { + if (JavaServiceUtil.SERVICE_LOADER_LOAD.test(methodCall)) { + tasks.add(() -> JavaServiceUtil.collectServiceLoaderLoadCall(identifier, methodCall)); + } + } + } - Object lock = new Object(); - ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator(); - List> found = new ArrayList<>(); - JobLauncher.getInstance().invokeConcurrentlyUnderProgress(tasks, indicator, computable -> { - List> infos = computable.compute(); - synchronized (lock) { - found.addAll(infos); - } - return true; - }); - synchronized (lock) { - result.addAll(found); - } - } - - @Nonnull - private static List> collectSiblingInheritedMethods(@Nonnull final Collection methods) { - Map map = FindSuperElementsHelper.getSiblingInheritanceInfos(methods); - return ContainerUtil.map(map.keySet(), method -> { - PsiElement range = getMethodRange(method); - ArrowUpLineMarkerInfo upInfo = - new ArrowUpLineMarkerInfo(range, AllIcons.Gutter.SiblingInheritedMethod, MarkerType.SIBLING_OVERRIDING_METHOD); - return NavigateAction.setNavigateAction(upInfo, JavaBundle.message("action.go.to.super.method.text"), IdeActions.ACTION_GOTO_SUPER); - }); - } - - @Nonnull - private static PsiElement getMethodRange(@Nonnull PsiMethod method) { - PsiElement range; - if (method.isPhysical()) { - range = method.getNameIdentifier(); - } else { - final PsiElement navigationElement = method.getNavigationElement(); - range = navigationElement instanceof PsiNameIdentifierOwner ? ((PsiNameIdentifierOwner) navigationElement).getNameIdentifier() : navigationElement; - } - if (range == null) { - range = method; - } - return range; - } + for (Map.Entry> entry : canBeOverridden.entrySet()) { + PsiClass psiClass = entry.getKey(); + Set methods = (Set)entry.getValue(); + tasks.add(() -> collectOverridingMethods(methods, psiClass)); + } + for (PsiClass psiClass : canHaveSiblings.keySet()) { + Collection methods = canHaveSiblings.get(psiClass); + tasks.add(() -> collectSiblingInheritedMethods(methods)); + } - @Nonnull - protected List> collectInheritingClasses(@Nonnull PsiClass aClass) { - if (aClass.hasModifierProperty(PsiModifier.FINAL)) { - return Collections.emptyList(); + Object lock = new Object(); + ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator(); + List> found = new ArrayList<>(); + JobLauncher.getInstance().invokeConcurrentlyUnderProgress( + tasks, + indicator, + computable -> { + List> infos = computable.get(); + synchronized (lock) { + found.addAll(infos); + } + return true; + } + ); + synchronized (lock) { + result.addAll(found); + } } - if (CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName())) { - return Collections.emptyList(); // It's useless to have overridden markers for object. + + @Nonnull + private static List> collectSiblingInheritedMethods(@Nonnull Collection methods) { + Map map = FindSuperElementsHelper.getSiblingInheritanceInfos(methods); + return ContainerUtil.map( + map.keySet(), + method -> { + PsiElement range = getMethodRange(method); + ArrowUpLineMarkerInfo upInfo = new ArrowUpLineMarkerInfo( + range, + PlatformIconGroup.gutterSiblinginheritedmethod(), + MarkerType.SIBLING_OVERRIDING_METHOD + ); + return NavigateAction.setNavigateAction( + upInfo, + JavaLocalize.actionGoToSuperMethodText().get(), + IdeActions.ACTION_GOTO_SUPER + ); + } + ); } - PsiClass subClass = DirectClassInheritorsSearch.search(aClass).findFirst(); - if (subClass != null || FunctionalExpressionSearch.search(aClass).findFirst() != null) { - final Image icon; - if (aClass.isInterface()) { - if (!myImplementedOption.isEnabled()) { - return Collections.emptyList(); + @Nonnull + @RequiredReadAction + private static PsiElement getMethodRange(@Nonnull PsiMethod method) { + PsiElement range; + if (method.isPhysical()) { + range = method.getNameIdentifier(); } - icon = AllIcons.Gutter.ImplementedMethod; - } else { - if (!myOverriddenOption.isEnabled()) { - return Collections.emptyList(); + else { + PsiElement navigationElement = method.getNavigationElement(); + range = navigationElement instanceof PsiNameIdentifierOwner nameIdentifierOwner + ? nameIdentifierOwner.getNameIdentifier() + : navigationElement; } - icon = AllIcons.Gutter.OverridenMethod; - } - PsiElement range = aClass.getNameIdentifier(); - if (range == null) { - range = aClass; - } - MarkerType type = MarkerType.SUBCLASSED_CLASS; - LineMarkerInfo info = new LineMarkerInfo<>(range, range.getTextRange(), - icon, Pass.LINE_MARKERS, type.getTooltip(), - type.getNavigationHandler(), - GutterIconRenderer.Alignment.RIGHT); - NavigateAction.setNavigateAction(info, aClass.isInterface() ? JavaBundle.message("action.go.to.implementation.text") - : JavaBundle.message("action.go.to.subclass.text"), IdeActions.ACTION_GOTO_IMPLEMENTATION); - return Collections.singletonList(info); + if (range == null) { + range = method; + } + return range; } - return Collections.emptyList(); - } - @Nonnull - private List> collectOverridingMethods(@Nonnull final Set methodSet, @Nonnull PsiClass containingClass) { - if (!myOverriddenOption.isEnabled() && !myImplementedOption.isEnabled()) { - return Collections.emptyList(); - } - final Set overridden = new HashSet<>(); - - AllOverridingMethodsSearch.search(containingClass).forEach(pair -> { - ProgressManager.checkCanceled(); - - final PsiMethod superMethod = pair.getFirst(); - if (methodSet.remove(superMethod)) { - overridden.add(superMethod); - } - return !methodSet.isEmpty(); - }); - - if (!methodSet.isEmpty()) { - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(containingClass); - if (interfaceMethod != null && - methodSet.contains(interfaceMethod) && - FunctionalExpressionSearch.search(containingClass).findFirst() != null) { - overridden.add(interfaceMethod); - } - } + @Nonnull + @RequiredReadAction + protected List> collectInheritingClasses(@Nonnull PsiClass aClass) { + if (aClass.isFinal()) { + return Collections.emptyList(); + } + if (JavaClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName())) { + return Collections.emptyList(); // It's useless to have overridden markers for object. + } - List> result = new ArrayList<>(overridden.size()); - for (PsiMethod method : overridden) { - ProgressManager.checkCanceled(); - boolean overrides = !method.hasModifierProperty(PsiModifier.ABSTRACT); - if (overrides && !myOverriddenOption.isEnabled()) { - continue; - } - if (!overrides && !myImplementedOption.isEnabled()) { - continue; - } - PsiElement range = getMethodRange(method); - final MarkerType type = MarkerType.OVERRIDDEN_METHOD; - final Image icon = overrides ? AllIcons.Gutter.OverridenMethod : AllIcons.Gutter.ImplementedMethod; - LineMarkerInfo info = new LineMarkerInfo<>(range, range.getTextRange(), - icon, Pass.LINE_MARKERS, type.getTooltip(), - type.getNavigationHandler(), - GutterIconRenderer.Alignment.RIGHT); - NavigateAction.setNavigateAction(info, overrides ? JavaBundle.message("action.go.to.overriding.methods.text") - : JavaBundle.message("action.go.to.implementation.text"), IdeActions.ACTION_GOTO_IMPLEMENTATION); - result.add(info); + PsiClass subClass = DirectClassInheritorsSearch.search(aClass).findFirst(); + if (subClass != null || FunctionalExpressionSearch.search(aClass).findFirst() != null) { + Image icon; + if (aClass.isInterface()) { + if (!myImplementedOption.isEnabled()) { + return Collections.emptyList(); + } + icon = PlatformIconGroup.gutterImplementedmethod(); + } + else { + if (!myOverriddenOption.isEnabled()) { + return Collections.emptyList(); + } + icon = PlatformIconGroup.gutterOverridenmethod(); + } + PsiElement range = aClass.getNameIdentifier(); + if (range == null) { + range = aClass; + } + MarkerType type = MarkerType.SUBCLASSED_CLASS; + LineMarkerInfo info = new LineMarkerInfo<>( + range, + range.getTextRange(), + icon, + Pass.LINE_MARKERS, + type.getTooltip(), + type.getNavigationHandler(), + GutterIconRenderer.Alignment.RIGHT + ); + LocalizeValue text = aClass.isInterface() + ? JavaLocalize.actionGoToImplementationText() + : JavaLocalize.actionGoToSubclassText(); + NavigateAction.setNavigateAction(info, text.get(), IdeActions.ACTION_GOTO_IMPLEMENTATION); + return Collections.singletonList(info); + } + return Collections.emptyList(); } - return result; - } - - @Override - public String getName() { - return "Java line markers"; - } - - @Override - public Option[] getOptions() { - return new Option[]{ - LAMBDA_OPTION, - myOverriddenOption, - myImplementedOption, - myOverridingOption, - myImplementingOption, - mySiblingsOption, - myServiceOption - }; - } - - @Nonnull - @Override - public Language getLanguage() { - return JavaLanguage.INSTANCE; - } - - private static class ArrowUpLineMarkerInfo extends MergeableLineMarkerInfo { - private ArrowUpLineMarkerInfo(@Nonnull PsiElement element, @Nonnull Image icon, @Nonnull MarkerType markerType) { - super(element, element.getTextRange(), icon, Pass.LINE_MARKERS, markerType.getTooltip(), markerType.getNavigationHandler(), GutterIconRenderer.Alignment.LEFT); + + @Nonnull + @RequiredReadAction + private List> collectOverridingMethods( + @Nonnull Set methodSet, + @Nonnull PsiClass containingClass + ) { + if (!myOverriddenOption.isEnabled() && !myImplementedOption.isEnabled()) { + return Collections.emptyList(); + } + Set overridden = new HashSet<>(); + + AllOverridingMethodsSearch.search(containingClass).forEach(pair -> { + ProgressManager.checkCanceled(); + + PsiMethod superMethod = pair.getFirst(); + if (methodSet.remove(superMethod)) { + overridden.add(superMethod); + } + return !methodSet.isEmpty(); + }); + + if (!methodSet.isEmpty()) { + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(containingClass); + if (interfaceMethod != null && + methodSet.contains(interfaceMethod) && + FunctionalExpressionSearch.search(containingClass).findFirst() != null) { + overridden.add(interfaceMethod); + } + } + + List> result = new ArrayList<>(overridden.size()); + for (PsiMethod method : overridden) { + ProgressManager.checkCanceled(); + boolean overrides = !method.isAbstract(); + if (overrides && !myOverriddenOption.isEnabled()) { + continue; + } + if (!overrides && !myImplementedOption.isEnabled()) { + continue; + } + PsiElement range = getMethodRange(method); + MarkerType type = MarkerType.OVERRIDDEN_METHOD; + Image icon = overrides ? PlatformIconGroup.gutterOverridenmethod() : PlatformIconGroup.gutterImplementedmethod(); + LineMarkerInfo info = new LineMarkerInfo<>( + range, + range.getTextRange(), + icon, + Pass.LINE_MARKERS, + type.getTooltip(), + type.getNavigationHandler(), + GutterIconRenderer.Alignment.RIGHT + ); + LocalizeValue text = overrides + ? JavaLocalize.actionGoToOverridingMethodsText() + : JavaLocalize.actionGoToImplementationText(); + NavigateAction.setNavigateAction(info, text.get(), IdeActions.ACTION_GOTO_IMPLEMENTATION); + result.add(info); + } + return result; } @Override - public boolean canMergeWith(@Nonnull MergeableLineMarkerInfo info) { - if (!(info instanceof ArrowUpLineMarkerInfo)) { - return false; - } - PsiElement otherElement = info.getElement(); - PsiElement myElement = getElement(); - return otherElement != null && myElement != null; + public String getName() { + return "Java line markers"; } - @Nonnull @Override - public Image getCommonIcon(@Nonnull List infos) { - return myIcon; + public Option[] getOptions() { + return new Option[]{ + LAMBDA_OPTION, + myOverriddenOption, + myImplementedOption, + myOverridingOption, + myImplementingOption, + mySiblingsOption, + myServiceOption + }; } @Nonnull @Override - public Function getCommonTooltip(@Nonnull List infos) { - return element -> "Multiple method overrides"; + public Language getLanguage() { + return JavaLanguage.INSTANCE; } - @Override - public String getElementPresentation(PsiElement element) { - final PsiElement parent = element.getParent(); - if (parent instanceof PsiFunctionalExpression) { - return PsiExpressionTrimRenderer.render((PsiExpression) parent); - } - return super.getElementPresentation(element); + private static class ArrowUpLineMarkerInfo extends MergeableLineMarkerInfo { + @RequiredReadAction + private ArrowUpLineMarkerInfo(@Nonnull PsiElement element, @Nonnull Image icon, @Nonnull MarkerType markerType) { + super( + element, + element.getTextRange(), + icon, + Pass.LINE_MARKERS, + markerType.getTooltip(), + markerType.getNavigationHandler(), + GutterIconRenderer.Alignment.LEFT + ); + } + + @Override + public boolean canMergeWith(@Nonnull MergeableLineMarkerInfo info) { + if (!(info instanceof ArrowUpLineMarkerInfo)) { + return false; + } + PsiElement otherElement = info.getElement(); + PsiElement myElement = getElement(); + return otherElement != null && myElement != null; + } + + @Nonnull + @Override + public Image getCommonIcon(@Nonnull List infos) { + return myIcon; + } + + @Nonnull + @Override + public Function getCommonTooltip(@Nonnull List infos) { + return element -> "Multiple method overrides"; + } + + @Override + public String getElementPresentation(PsiElement element) { + PsiElement parent = element.getParent(); + if (parent instanceof PsiFunctionalExpression functionalExpr) { + return PsiExpressionTrimRenderer.render(functionalExpr); + } + return super.getElementPresentation(element); + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/MarkerType.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/MarkerType.java index 5e1364d03a..2440d86eb8 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/MarkerType.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/MarkerType.java @@ -19,6 +19,7 @@ import consulo.application.progress.ProgressIndicator; import consulo.application.progress.ProgressManager; import consulo.application.util.function.CommonProcessors; +import consulo.java.language.module.util.JavaClassNames; import consulo.java.localize.JavaLocalize; import consulo.language.editor.CodeInsightBundle; import consulo.language.editor.DaemonBundle; @@ -35,6 +36,7 @@ import consulo.language.psi.resolve.PsiElementProcessorAdapter; import consulo.language.psi.scope.GlobalSearchScope; import consulo.project.DumbService; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.action.IdeActions; import consulo.util.collection.ArrayUtil; import consulo.util.collection.ContainerUtil; @@ -57,14 +59,14 @@ public class MarkerType { * @deprecated use {@link #MarkerType(String, Function, LineMarkerNavigator)} instead */ @Deprecated - public MarkerType(@Nonnull Function tooltip, @Nonnull final LineMarkerNavigator navigator) { + public MarkerType(@Nonnull Function tooltip, @Nonnull LineMarkerNavigator navigator) { this("Unknown", tooltip, navigator); } public MarkerType( @Nonnull String debugName, @Nonnull Function tooltip, - @Nonnull final LineMarkerNavigator navigator + @Nonnull LineMarkerNavigator navigator ) { myTooltip = tooltip; myDebugName = debugName; @@ -86,45 +88,30 @@ public Function getTooltip() { return myTooltip; } - public static final MarkerType OVERRIDING_METHOD = new MarkerType("OVERRIDING_METHOD", element -> { - PsiElement parent = getParentMethod(element); - if (!(parent instanceof PsiMethod)) { - return null; - } - PsiMethod method = (PsiMethod)parent; - - return calculateOverridingMethodTooltip(method, method != element.getParent()); - }, new LineMarkerNavigator() { - @Override - public void browse(MouseEvent e, PsiElement element) { - PsiElement parent = getParentMethod(element); - if (!(parent instanceof PsiMethod)) { - return; + public static final MarkerType OVERRIDING_METHOD = new MarkerType( + "OVERRIDING_METHOD", + element -> getParentMethod(element) instanceof PsiMethod method + ? calculateOverridingMethodTooltip(method, method != element.getParent()) : null, + new LineMarkerNavigator() { + @Override + public void browse(MouseEvent e, PsiElement element) { + PsiElement parent = getParentMethod(element); + if (!(parent instanceof PsiMethod method)) { + return; + } + navigateToOverridingMethod(e, method, method != element.getParent()); } - PsiMethod method = (PsiMethod)parent; - navigateToOverridingMethod(e, method, method != element.getParent()); } - }); + ); public static final MarkerType SIBLING_OVERRIDING_METHOD = new MarkerType( "SIBLING_OVERRIDING_METHOD", - element -> { - PsiElement parent = getParentMethod(element); - if (!(parent instanceof PsiMethod)) { - return null; - } - PsiMethod method = (PsiMethod)parent; - - return calculateOverridingSiblingMethodTooltip(method); - }, + element -> getParentMethod(element) instanceof PsiMethod method ? calculateOverridingSiblingMethodTooltip(method) : null, new LineMarkerNavigator() { @Override public void browse(MouseEvent e, PsiElement element) { - PsiElement parent = getParentMethod(element); - if (!(parent instanceof PsiMethod)) { - return; + if (getParentMethod(element) instanceof PsiMethod method) { + navigateToSiblingOverridingMethod(e, method); } - PsiMethod method = (PsiMethod)parent; - navigateToSiblingOverridingMethod(e, method); } } ); @@ -164,8 +151,8 @@ private static String calculateOverridingSiblingMethodTooltip(@Nonnull PsiMethod @Nonnull private static String getTooltipPrefix(@Nonnull PsiMethod method, @Nonnull PsiMethod superMethod, @Nonnull String prefix) { StringBuilder sb = new StringBuilder(prefix); - boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); - boolean isSuperAbstract = superMethod.hasModifierProperty(PsiModifier.ABSTRACT); + boolean isAbstract = method.isAbstract(); + boolean isSuperAbstract = superMethod.isAbstract(); sb.append(isSuperAbstract && !isAbstract ? "Implements method " : "Overrides method "); if (isSameSignature(method, superMethod)) { sb.append("in "); @@ -179,7 +166,8 @@ private static boolean isSameSignature(@Nonnull PsiMethod method, @Nonnull PsiMe @Nonnull private static PsiElementProcessor.CollectElementsWithLimit getProcessor(int limit, boolean set) { - return set ? new PsiElementProcessor.CollectElementsWithLimit<>(limit, new HashSet<>()) + return set + ? new PsiElementProcessor.CollectElementsWithLimit<>(limit, new HashSet<>()) : new PsiElementProcessor.CollectElementsWithLimit<>(limit); } @@ -246,38 +234,28 @@ private static PsiMethod[] composeSuperMethods(@Nonnull PsiMethod method, boolea } private static PsiElement getParentMethod(@Nonnull PsiElement element) { - final PsiElement parent = element.getParent(); - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(parent); + PsiElement parent = element.getParent(); + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(parent); return interfaceMethod != null ? interfaceMethod : parent; } public static final String SEARCHING_FOR_OVERRIDING_METHODS = "Searching for Overriding Methods"; public static final MarkerType OVERRIDDEN_METHOD = new MarkerType( "OVERRIDDEN_METHOD", - element -> { - PsiElement parent = element.getParent(); - if (!(parent instanceof PsiMethod)) { - return null; - } - PsiMethod method = (PsiMethod)parent; - - return getOverriddenMethodTooltip(method); - }, + element -> element.getParent() instanceof PsiMethod method ? getOverriddenMethodTooltip(method) : null, new LineMarkerNavigator() { @Override public void browse(MouseEvent e, PsiElement element) { - PsiElement parent = element.getParent(); - if (!(parent instanceof PsiMethod)) { - return; + if (element.getParent() instanceof PsiMethod method) { + navigateToOverriddenMethod(e, method); } - navigateToOverriddenMethod(e, (PsiMethod)parent); } } ); private static String getOverriddenMethodTooltip(@Nonnull PsiMethod method) { - final PsiClass aClass = method.getContainingClass(); - if (aClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName())) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null && JavaClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName())) { return getImplementationTooltip("Is implemented in several subclasses"); } @@ -285,7 +263,7 @@ private static String getOverriddenMethodTooltip(@Nonnull PsiMethod method) { GlobalSearchScope scope = GlobalSearchScope.allScope(PsiUtilCore.getProjectInReadAction(method)); OverridingMethodsSearch.search(method, scope, true).forEach(new PsiElementProcessorAdapter<>(processor)); - boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); + boolean isAbstract = method.isAbstract(); if (processor.isOverflow()) { return getImplementationTooltip(isAbstract ? "Is implemented in several subclasses" : "Is overridden in several subclasses"); @@ -302,7 +280,7 @@ private static String getOverriddenMethodTooltip(@Nonnull PsiMethod method) { return getImplementationTooltip(isAbstract ? "Is implemented in" : "Is overridden in", overridings); } - private static void navigateToOverriddenMethod(MouseEvent e, @Nonnull final PsiMethod method) { + private static void navigateToOverriddenMethod(MouseEvent e, @Nonnull PsiMethod method) { if (DumbService.isDumb(method.getProject())) { DumbService.getInstance(method.getProject()) .showDumbModeNotification(JavaLocalize.notificationNavigationToOverridingClasses()); @@ -311,13 +289,13 @@ private static void navigateToOverriddenMethod(MouseEvent e, @Nonnull final PsiM PsiElementProcessor.CollectElementsWithLimit collectProcessor = getProcessor(2, true); PsiElementProcessor.CollectElementsWithLimit collectExprProcessor = getProcessor(2, true); - final boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); + boolean isAbstract = method.isAbstract(); if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( () -> { GlobalSearchScope scope = GlobalSearchScope.allScope(PsiUtilCore.getProjectInReadAction(method)); OverridingMethodsSearch.search(method, scope, true).forEach(new PsiElementProcessorAdapter<>(collectProcessor)); if (isAbstract && collectProcessor.getCollection().size() < 2) { - final PsiClass aClass = ReadAction.compute(method::getContainingClass); + PsiClass aClass = ReadAction.compute(method::getContainingClass); if (aClass != null) { FunctionalExpressionSearch.search(aClass).forEach(new PsiElementProcessorAdapter<>(collectExprProcessor)); } @@ -331,8 +309,8 @@ private static void navigateToOverriddenMethod(MouseEvent e, @Nonnull final PsiM return; } - final PsiMethod[] methodOverriders = collectProcessor.toArray(PsiMethod.EMPTY_ARRAY); - final List overridings = new ArrayList<>(); + PsiMethod[] methodOverriders = collectProcessor.toArray(PsiMethod.EMPTY_ARRAY); + List overridings = new ArrayList<>(); overridings.addAll(collectProcessor.getCollection()); overridings.addAll(collectExprProcessor.getCollection()); if (overridings.isEmpty()) { @@ -341,7 +319,7 @@ private static void navigateToOverriddenMethod(MouseEvent e, @Nonnull final PsiM boolean showMethodNames = !PsiUtil.allMethodsHaveSameSignature(methodOverriders); MethodOrFunctionalExpressionCellRenderer renderer = new MethodOrFunctionalExpressionCellRenderer(showMethodNames); Collections.sort(overridings, renderer.getComparator()); - final OverridingMethodsUpdater methodsUpdater = new OverridingMethodsUpdater(method, renderer); + OverridingMethodsUpdater methodsUpdater = new OverridingMethodsUpdater(method, renderer); PsiElementListNavigator.openTargets( e, overridings.toArray(NavigatablePsiElement.EMPTY_ARRAY), @@ -355,24 +333,13 @@ private static void navigateToOverriddenMethod(MouseEvent e, @Nonnull final PsiM private static final String SEARCHING_FOR_OVERRIDDEN_METHODS = "Searching for Overridden Methods"; public static final MarkerType SUBCLASSED_CLASS = new MarkerType( "SUBCLASSED_CLASS", - element -> { - PsiElement parent = element.getParent(); - if (!(parent instanceof PsiClass)) { - return null; - } - PsiClass aClass = (PsiClass)parent; - return getSubclassedClassTooltip(aClass); - }, + element -> element.getParent() instanceof PsiClass aClass ? getSubclassedClassTooltip(aClass) : null, new LineMarkerNavigator() { @Override public void browse(MouseEvent e, PsiElement element) { - final PsiElement parent = element.getParent(); - if (!(parent instanceof PsiClass)) { - return; + if (element.getParent() instanceof PsiClass aClass) { + navigateToSubclassedClass(e, aClass); } - final PsiClass aClass = (PsiClass)parent; - - navigateToSubclassedClass(e, aClass); } } ); @@ -398,14 +365,14 @@ public static String getSubclassedClassTooltip(@Nonnull PsiClass aClass) { } // Used in Kotlin, please don't make private - public static void navigateToSubclassedClass(MouseEvent e, @Nonnull final PsiClass aClass) { + public static void navigateToSubclassedClass(MouseEvent e, @Nonnull PsiClass aClass) { navigateToSubclassedClass(e, aClass, new PsiClassOrFunctionalExpressionListCellRenderer()); } // Used in Kotlin, please don't make private public static void navigateToSubclassedClass( MouseEvent e, - @Nonnull final PsiClass aClass, + @Nonnull PsiClass aClass, PsiElementListCellRenderer renderer ) { if (DumbService.isDumb(aClass.getProject())) { @@ -414,24 +381,30 @@ public static void navigateToSubclassedClass( return; } - final PsiElementProcessor.FindElement collectProcessor = new PsiElementProcessor.FindElement<>(); - final PsiElementProcessor.FindElement collectExprProcessor = new PsiElementProcessor.FindElement<>(); - if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { - ClassInheritorsSearch.search(aClass).forEach(new PsiElementProcessorAdapter<>(collectProcessor)); - if (collectProcessor.getFoundElement() == null) { - FunctionalExpressionSearch.search(aClass).forEach(new PsiElementProcessorAdapter<>(collectExprProcessor)); - } - }, SEARCHING_FOR_OVERRIDDEN_METHODS, true, aClass.getProject(), (JComponent)e.getComponent())) { + PsiElementProcessor.FindElement collectProcessor = new PsiElementProcessor.FindElement<>(); + PsiElementProcessor.FindElement collectExprProcessor = new PsiElementProcessor.FindElement<>(); + if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( + () -> { + ClassInheritorsSearch.search(aClass).forEach(new PsiElementProcessorAdapter<>(collectProcessor)); + if (collectProcessor.getFoundElement() == null) { + FunctionalExpressionSearch.search(aClass).forEach(new PsiElementProcessorAdapter<>(collectExprProcessor)); + } + }, + SEARCHING_FOR_OVERRIDDEN_METHODS, + true, + aClass.getProject(), + (JComponent)e.getComponent() + )) { return; } - final List inheritors = new ArrayList<>(); + List inheritors = new ArrayList<>(); ContainerUtil.addIfNotNull(inheritors, collectProcessor.getFoundElement()); ContainerUtil.addIfNotNull(inheritors, collectExprProcessor.getFoundElement()); if (inheritors.isEmpty()) { return; } - final SubclassUpdater subclassUpdater = new SubclassUpdater(aClass, renderer); + SubclassUpdater subclassUpdater = new SubclassUpdater(aClass, renderer); Collections.sort(inheritors, renderer.getComparator()); PsiElementListNavigator.openTargets( e, @@ -460,6 +433,7 @@ public String getCaption(int size) { } @Override + @RequiredUIAccess public void onSuccess() { super.onSuccess(); PsiElement oneElement = getTheOnlyOneElement(); @@ -475,7 +449,7 @@ public void run(@Nonnull final ProgressIndicator indicator) { ClassInheritorsSearch.search(myClass, ReadAction.compute(myClass::getUseScope), true) .forEach(new CommonProcessors.CollectProcessor<>() { @Override - public boolean process(final PsiClass o) { + public boolean process(PsiClass o) { if (!updateComponent(o)) { indicator.cancel(); } @@ -486,7 +460,7 @@ public boolean process(final PsiClass o) { FunctionalExpressionSearch.search(myClass).forEach(new CommonProcessors.CollectProcessor<>() { @Override - public boolean process(final PsiFunctionalExpression expr) { + public boolean process(PsiFunctionalExpression expr) { if (!updateComponent(expr)) { indicator.cancel(); } @@ -507,12 +481,13 @@ private OverridingMethodsUpdater(@Nonnull PsiMethod method, @Nonnull PsiElementL @Override public String getCaption(int size) { - return myMethod.hasModifierProperty(PsiModifier.ABSTRACT) + return myMethod.isAbstract() ? DaemonLocalize.navigationTitleImplementationMethod(myMethod.getName(), size).get() : DaemonLocalize.navigationTitleOverriderMethod(myMethod.getName(), size).get(); } @Override + @RequiredUIAccess public void onSuccess() { super.onSuccess(); PsiElement oneElement = getTheOnlyOneElement(); @@ -537,11 +512,11 @@ public boolean process(PsiMethod psiMethod) { return super.process(psiMethod); } }); - if (ReadAction.compute(() -> myMethod.hasModifierProperty(PsiModifier.ABSTRACT))) { + if (ReadAction.compute(myMethod::isAbstract)) { PsiClass psiClass = ReadAction.compute(myMethod::getContainingClass); FunctionalExpressionSearch.search(psiClass).forEach(new CommonProcessors.CollectProcessor<>() { @Override - public boolean process(final PsiFunctionalExpression expr) { + public boolean process(PsiFunctionalExpression expr) { if (!updateComponent(expr)) { indicator.cancel(); } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java index b3e46a08a3..68f3b7b278 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java @@ -13,9 +13,11 @@ import com.siyeh.ig.psiutils.CommentTracker; import com.siyeh.ig.psiutils.ControlFlowUtils; import com.siyeh.ig.psiutils.ParenthesesUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; -import consulo.language.editor.intention.CommonQuickFixBundle; +import consulo.java.language.module.util.JavaClassNames; import consulo.language.editor.intention.SyntheticIntentionAction; +import consulo.language.editor.localize.CommonQuickFixLocalize; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.PsiWhiteSpace; @@ -25,329 +27,352 @@ import consulo.project.Project; import consulo.util.collection.ContainerUtil; import consulo.util.lang.ObjectUtil; +import jakarta.annotation.Nonnull; import one.util.streamex.StreamEx; -import org.jetbrains.annotations.NonNls; -import jakarta.annotation.Nonnull; import java.util.*; import java.util.stream.Collectors; public class ConvertSwitchToIfIntention implements SyntheticIntentionAction { - private final PsiSwitchStatement mySwitchStatement; - - public ConvertSwitchToIfIntention(@Nonnull PsiSwitchStatement switchStatement) { - mySwitchStatement = switchStatement; - } - - @Nonnull - @Override - public String getText() { - return CommonQuickFixBundle.message("fix.replace.x.with.y", PsiKeyword.SWITCH, PsiKeyword.IF); - } - - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return isAvailable(mySwitchStatement); - } - - public static boolean isAvailable(PsiSwitchStatement switchStatement) { - final PsiCodeBlock body = switchStatement.getBody(); - return body != null && !body.isEmpty() && BreakConverter.from(switchStatement) != null && !mayFallThroughNonTerminalDefaultCase(body); - } - - private static boolean mayFallThroughNonTerminalDefaultCase(PsiCodeBlock body) { - List labels = PsiTreeUtil.getChildrenOfTypeAsList(body, PsiSwitchLabelStatementBase.class); - return StreamEx.of(labels).pairMap((prev, next) -> { - if (prev.isDefaultCase()) { - Set targets = getFallThroughTargets(body); - return targets.contains(prev) || targets.contains(next); - } - return false; - }).has(true); - } + private final PsiSwitchStatement mySwitchStatement; - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) { - doProcessIntention(mySwitchStatement); - } - - @Nonnull - @Override - public PsiElement getElementToMakeWritable(@Nonnull PsiFile file) { - return mySwitchStatement; - } - - @Override - public boolean startInWriteAction() { - return true; - } - - public static void doProcessIntention(@Nonnull PsiSwitchStatement switchStatement) { - final PsiExpression switchExpression = switchStatement.getExpression(); - if (switchExpression == null) { - return; - } - final PsiType switchExpressionType = RefactoringUtil.getTypeByExpressionWithExpectedType(switchExpression); - if (switchExpressionType == null) { - return; - } - CommentTracker commentTracker = new CommentTracker(); - final boolean isSwitchOnString = switchExpressionType.equalsToText(CommonClassNames.JAVA_LANG_STRING); - boolean useEquals = isSwitchOnString; - if (!useEquals) { - final PsiClass aClass = PsiUtil.resolveClassInType(switchExpressionType); - useEquals = aClass != null && !aClass.isEnum() && !TypeConversionUtil.isPrimitiveWrapper(aClass.getQualifiedName()); - } - PsiCodeBlock body = switchStatement.getBody(); - if (body == null) { - return; - } - // Should execute getFallThroughTargets and statementMayCompleteNormally before converting breaks - Set fallThroughTargets = getFallThroughTargets(body); - boolean mayCompleteNormally = ControlFlowUtils.statementMayCompleteNormally(switchStatement); - BreakConverter converter = BreakConverter.from(switchStatement); - if (converter == null) { - return; + public ConvertSwitchToIfIntention(@Nonnull PsiSwitchStatement switchStatement) { + mySwitchStatement = switchStatement; } - converter.process(); - final List allBranches = extractBranches(commentTracker, body, fallThroughTargets); - final String declarationString; - final boolean hadSideEffects; - final String expressionText; - final Project project = switchStatement.getProject(); - int totalCases = allBranches.stream().mapToInt(br -> br.getCaseValues().size()).sum(); - if (totalCases > 0) { - commentTracker.markUnchanged(switchExpression); + @Nonnull + @Override + public String getText() { + return CommonQuickFixLocalize.fixReplaceXWithY(PsiKeyword.SWITCH, PsiKeyword.IF).get(); } - if (totalCases > 1 && RemoveUnusedVariableUtil.checkSideEffects(switchExpression, null, new ArrayList<>())) { - hadSideEffects = true; - final String variableName = new VariableNameGenerator(switchExpression, VariableKind.LOCAL_VARIABLE) - .byExpression(switchExpression).byType(switchExpressionType).byName(isSwitchOnString ? "s" : "i").generate(true); - expressionText = variableName; - declarationString = switchExpressionType.getCanonicalText() + ' ' + variableName + " = " + switchExpression.getText() + ';'; - } else { - hadSideEffects = false; - declarationString = null; - expressionText = ParenthesesUtils.getPrecedence(switchExpression) > ParenthesesUtils.EQUALITY_PRECEDENCE - ? '(' + switchExpression.getText() + ')' - : switchExpression.getText(); + @Override + @RequiredReadAction + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + return isAvailable(mySwitchStatement); } - final StringBuilder ifStatementBuilder = new StringBuilder(); - boolean firstBranch = true; - SwitchStatementBranch defaultBranch = null; - for (SwitchStatementBranch branch : allBranches) { - if (branch.isDefault()) { - defaultBranch = branch; - } else { - dumpBranch(branch, expressionText, firstBranch, useEquals, ifStatementBuilder, commentTracker); - firstBranch = false; - } + @RequiredReadAction + public static boolean isAvailable(PsiSwitchStatement switchStatement) { + PsiCodeBlock body = switchStatement.getBody(); + return body != null && !body.isEmpty() && BreakConverter.from(switchStatement) != null && !mayFallThroughNonTerminalDefaultCase(body); } - boolean unwrapDefault = false; - if (defaultBranch != null) { - unwrapDefault = defaultBranch.isAlwaysExecuted() || (switchStatement.getParent() instanceof PsiCodeBlock && !mayCompleteNormally); - if (!unwrapDefault && defaultBranch.hasStatements()) { - ifStatementBuilder.append("else "); - dumpBody(defaultBranch, ifStatementBuilder, commentTracker); - } - } - String ifStatementText = ifStatementBuilder.toString(); - if (ifStatementText.isEmpty()) { - if (!unwrapDefault) { - return; - } - ifStatementText = ";"; + + @RequiredReadAction + private static boolean mayFallThroughNonTerminalDefaultCase(PsiCodeBlock body) { + List labels = PsiTreeUtil.getChildrenOfTypeAsList(body, PsiSwitchLabelStatementBase.class); + return StreamEx.of(labels).pairMap((prev, next) -> { + if (prev.isDefaultCase()) { + Set targets = getFallThroughTargets(body); + return targets.contains(prev) || targets.contains(next); + } + return false; + }).has(true); } - final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); - PsiCodeBlock parent = ObjectUtil.tryCast(switchStatement.getParent(), PsiCodeBlock.class); - if (unwrapDefault || hadSideEffects) { - if (parent == null) { - commentTracker.grabComments(switchStatement); - switchStatement = BlockUtils.expandSingleStatementToBlockStatement(switchStatement); - parent = (PsiCodeBlock) (switchStatement.getParent()); - } + + @Override + @RequiredReadAction + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) { + doProcessIntention(mySwitchStatement); } - JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); - if (hadSideEffects) { - final PsiStatement declarationStatement = factory.createStatementFromText(declarationString, switchStatement); - javaCodeStyleManager.shortenClassReferences(parent.addBefore(declarationStatement, switchStatement)); + + @Nonnull + @Override + public PsiElement getElementToMakeWritable(@Nonnull PsiFile file) { + return mySwitchStatement; } - final PsiStatement ifStatement = factory.createStatementFromText(ifStatementText, switchStatement); - if (unwrapDefault) { - PsiElement addedIf = parent.addBefore(ifStatement, switchStatement); - StringBuilder sb = new StringBuilder(); - dumpBody(defaultBranch, sb, commentTracker); - PsiBlockStatement defaultBody = (PsiBlockStatement) factory.createStatementFromText(sb.toString(), switchStatement); - if (!BlockUtils.containsConflictingDeclarations(Objects.requireNonNull(switchStatement.getBody()), parent)) { - commentTracker.grabComments(switchStatement); - BlockUtils.inlineCodeBlock(switchStatement, defaultBody.getCodeBlock()); - } else { - commentTracker.replace(switchStatement, defaultBody); - } - commentTracker.insertCommentsBefore(addedIf); - if (ifStatementText.equals(";")) { - addedIf.delete(); - } else { - javaCodeStyleManager.shortenClassReferences(addedIf); - } - } else { - javaCodeStyleManager.shortenClassReferences(commentTracker.replaceAndRestoreComments(switchStatement, ifStatement)); + + @Override + public boolean startInWriteAction() { + return true; } - } - @Nonnull - private static List extractBranches(CommentTracker commentTracker, - PsiCodeBlock body, - Set fallThroughTargets) { - final List openBranches = new ArrayList<>(); - final Set declaredElements = new HashSet<>(); - final List allBranches = new ArrayList<>(); - SwitchStatementBranch currentBranch = null; - final PsiElement[] children = body.getChildren(); - List labels = PsiTreeUtil.getChildrenOfTypeAsList(body, PsiSwitchLabelStatementBase.class); - boolean defaultAlwaysExecuted = !labels.isEmpty() && - Objects.requireNonNull(ContainerUtil.getLastItem(labels)).isDefaultCase() && - fallThroughTargets.containsAll(labels.subList(1, labels.size())); - for (int i = 1; i < children.length - 1; i++) { - final PsiElement statement = children[i]; - if (statement instanceof PsiSwitchLabelStatement) { - final PsiSwitchLabelStatement label = (PsiSwitchLabelStatement) statement; - if (currentBranch == null || !fallThroughTargets.contains(statement)) { - openBranches.clear(); - currentBranch = new SwitchStatementBranch(); - currentBranch.addPendingDeclarations(declaredElements); - allBranches.add(currentBranch); - openBranches.add(currentBranch); - } else if (currentBranch.hasStatements()) { - currentBranch = new SwitchStatementBranch(); - allBranches.add(currentBranch); - openBranches.add(currentBranch); + @RequiredReadAction + public static void doProcessIntention(@Nonnull PsiSwitchStatement switchStatement) { + PsiExpression switchExpression = switchStatement.getExpression(); + if (switchExpression == null) { + return; + } + PsiType switchExpressionType = RefactoringUtil.getTypeByExpressionWithExpectedType(switchExpression); + if (switchExpressionType == null) { + return; + } + CommentTracker commentTracker = new CommentTracker(); + boolean isSwitchOnString = switchExpressionType.equalsToText(JavaClassNames.JAVA_LANG_STRING); + boolean useEquals = isSwitchOnString; + if (!useEquals) { + PsiClass aClass = PsiUtil.resolveClassInType(switchExpressionType); + useEquals = aClass != null && !aClass.isEnum() && !TypeConversionUtil.isPrimitiveWrapper(aClass.getQualifiedName()); + } + PsiCodeBlock body = switchStatement.getBody(); + if (body == null) { + return; + } + // Should execute getFallThroughTargets and statementMayCompleteNormally before converting breaks + Set fallThroughTargets = getFallThroughTargets(body); + boolean mayCompleteNormally = ControlFlowUtils.statementMayCompleteNormally(switchStatement); + BreakConverter converter = BreakConverter.from(switchStatement); + if (converter == null) { + return; + } + converter.process(); + List allBranches = extractBranches(commentTracker, body, fallThroughTargets); + + String declarationString; + boolean hadSideEffects; + String expressionText; + Project project = switchStatement.getProject(); + int totalCases = allBranches.stream().mapToInt(br -> br.getCaseValues().size()).sum(); + if (totalCases > 0) { + commentTracker.markUnchanged(switchExpression); + } + if (totalCases > 1 && RemoveUnusedVariableUtil.checkSideEffects(switchExpression, null, new ArrayList<>())) { + hadSideEffects = true; + + String variableName = new VariableNameGenerator(switchExpression, VariableKind.LOCAL_VARIABLE) + .byExpression(switchExpression) + .byType(switchExpressionType) + .byName(isSwitchOnString ? "s" : "i") + .generate(true); + expressionText = variableName; + declarationString = switchExpressionType.getCanonicalText() + ' ' + variableName + " = " + switchExpression.getText() + ';'; } - if (label.isDefaultCase() && defaultAlwaysExecuted) { - openBranches.retainAll(Collections.singleton(currentBranch)); + else { + hadSideEffects = false; + declarationString = null; + expressionText = ParenthesesUtils.getPrecedence(switchExpression) > ParenthesesUtils.EQUALITY_PRECEDENCE + ? '(' + switchExpression.getText() + ')' + : switchExpression.getText(); } - currentBranch.addCaseValues(label, defaultAlwaysExecuted, commentTracker); - } else if (statement instanceof PsiSwitchLabeledRuleStatement) { - openBranches.clear(); - PsiSwitchLabeledRuleStatement rule = (PsiSwitchLabeledRuleStatement) statement; - currentBranch = new SwitchStatementBranch(); - PsiStatement ruleBody = rule.getBody(); - if (ruleBody != null) { - currentBranch.addStatement(ruleBody); + StringBuilder ifStatementBuilder = new StringBuilder(); + boolean firstBranch = true; + SwitchStatementBranch defaultBranch = null; + for (SwitchStatementBranch branch : allBranches) { + if (branch.isDefault()) { + defaultBranch = branch; + } + else { + dumpBranch(branch, expressionText, firstBranch, useEquals, ifStatementBuilder, commentTracker); + firstBranch = false; + } + } + boolean unwrapDefault = false; + if (defaultBranch != null) { + unwrapDefault = + defaultBranch.isAlwaysExecuted() || (switchStatement.getParent() instanceof PsiCodeBlock && !mayCompleteNormally); + if (!unwrapDefault && defaultBranch.hasStatements()) { + ifStatementBuilder.append("else "); + dumpBody(defaultBranch, ifStatementBuilder, commentTracker); + } + } + String ifStatementText = ifStatementBuilder.toString(); + if (ifStatementText.isEmpty()) { + if (!unwrapDefault) { + return; + } + ifStatementText = ";"; + } + PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); + PsiCodeBlock parent = ObjectUtil.tryCast(switchStatement.getParent(), PsiCodeBlock.class); + if (unwrapDefault || hadSideEffects) { + if (parent == null) { + commentTracker.grabComments(switchStatement); + switchStatement = BlockUtils.expandSingleStatementToBlockStatement(switchStatement); + parent = (PsiCodeBlock)(switchStatement.getParent()); + } + } + JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); + if (hadSideEffects) { + PsiStatement declarationStatement = factory.createStatementFromText(declarationString, switchStatement); + javaCodeStyleManager.shortenClassReferences(parent.addBefore(declarationStatement, switchStatement)); } - currentBranch.addCaseValues(rule, defaultAlwaysExecuted, commentTracker); - openBranches.add(currentBranch); - allBranches.add(currentBranch); - } else { - if (statement instanceof PsiStatement) { - if (statement instanceof PsiDeclarationStatement) { - final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement) statement; - Collections.addAll(declaredElements, declarationStatement.getDeclaredElements()); - } - for (SwitchStatementBranch branch : openBranches) { - branch.addStatement((PsiStatement) statement); - } - } else { - for (SwitchStatementBranch branch : openBranches) { - if (statement instanceof PsiWhiteSpace) { - branch.addWhiteSpace(statement); - } else { - branch.addComment(statement); + PsiStatement ifStatement = factory.createStatementFromText(ifStatementText, switchStatement); + if (unwrapDefault) { + PsiElement addedIf = parent.addBefore(ifStatement, switchStatement); + StringBuilder sb = new StringBuilder(); + dumpBody(defaultBranch, sb, commentTracker); + PsiBlockStatement defaultBody = (PsiBlockStatement)factory.createStatementFromText(sb.toString(), switchStatement); + if (!BlockUtils.containsConflictingDeclarations(Objects.requireNonNull(switchStatement.getBody()), parent)) { + commentTracker.grabComments(switchStatement); + BlockUtils.inlineCodeBlock(switchStatement, defaultBody.getCodeBlock()); + } + else { + commentTracker.replace(switchStatement, defaultBody); + } + commentTracker.insertCommentsBefore(addedIf); + if (ifStatementText.equals(";")) { + addedIf.delete(); } - } + else { + javaCodeStyleManager.shortenClassReferences(addedIf); + } + } + else { + javaCodeStyleManager.shortenClassReferences(commentTracker.replaceAndRestoreComments(switchStatement, ifStatement)); } - } } - return allBranches; - } - private static Set getFallThroughTargets(PsiCodeBlock body) { - return StreamEx.of(body.getStatements()) - .pairMap((s1, s2) -> s2 instanceof PsiSwitchLabelStatement && !(s1 instanceof PsiSwitchLabeledRuleStatement) && - ControlFlowUtils.statementMayCompleteNormally(s1) ? (PsiSwitchLabelStatement) s2 : null) - .nonNull().collect(Collectors.toSet()); - } + @Nonnull + @RequiredReadAction + private static List extractBranches( + CommentTracker commentTracker, + PsiCodeBlock body, + Set fallThroughTargets + ) { + List openBranches = new ArrayList<>(); + Set declaredElements = new HashSet<>(); + List allBranches = new ArrayList<>(); + SwitchStatementBranch currentBranch = null; + PsiElement[] children = body.getChildren(); + List labels = PsiTreeUtil.getChildrenOfTypeAsList(body, PsiSwitchLabelStatementBase.class); + boolean defaultAlwaysExecuted = !labels.isEmpty() && + Objects.requireNonNull(ContainerUtil.getLastItem(labels)).isDefaultCase() && + fallThroughTargets.containsAll(labels.subList(1, labels.size())); + for (int i = 1; i < children.length - 1; i++) { + PsiElement statement = children[i]; + if (statement instanceof PsiSwitchLabelStatement label) { + if (currentBranch == null || !fallThroughTargets.contains(statement)) { + openBranches.clear(); + currentBranch = new SwitchStatementBranch(); + currentBranch.addPendingDeclarations(declaredElements); + allBranches.add(currentBranch); + openBranches.add(currentBranch); + } + else if (currentBranch.hasStatements()) { + currentBranch = new SwitchStatementBranch(); + allBranches.add(currentBranch); + openBranches.add(currentBranch); + } + if (label.isDefaultCase() && defaultAlwaysExecuted) { + openBranches.retainAll(Collections.singleton(currentBranch)); + } + currentBranch.addCaseValues(label, defaultAlwaysExecuted, commentTracker); + } + else if (statement instanceof PsiSwitchLabeledRuleStatement rule) { + openBranches.clear(); + currentBranch = new SwitchStatementBranch(); - private static void dumpBranch(SwitchStatementBranch branch, - String expressionText, - boolean firstBranch, - boolean useEquals, - @NonNls StringBuilder out, - CommentTracker commentTracker) { - if (!firstBranch) { - out.append("else "); + PsiStatement ruleBody = rule.getBody(); + if (ruleBody != null) { + currentBranch.addStatement(ruleBody); + } + currentBranch.addCaseValues(rule, defaultAlwaysExecuted, commentTracker); + openBranches.add(currentBranch); + allBranches.add(currentBranch); + } + else if (statement instanceof PsiStatement psiStatement) { + if (statement instanceof PsiDeclarationStatement declarationStatement) { + Collections.addAll(declaredElements, declarationStatement.getDeclaredElements()); + } + for (SwitchStatementBranch branch : openBranches) { + branch.addStatement(psiStatement); + } + } + else { + for (SwitchStatementBranch branch : openBranches) { + if (statement instanceof PsiWhiteSpace) { + branch.addWhiteSpace(statement); + } + else { + branch.addComment(statement); + } + } + } + } + return allBranches; } - dumpCaseValues(expressionText, branch.getCaseValues(), useEquals, out); - dumpBody(branch, out, commentTracker); - } - private static void dumpCaseValues(String expressionText, - List caseValues, - boolean useEquals, - @NonNls StringBuilder out) { - out.append("if("); - boolean firstCaseValue = true; - for (String caseValue : caseValues) { - if (!firstCaseValue) { - out.append("||"); - } - firstCaseValue = false; - if (useEquals) { - out.append(caseValue).append(".equals(").append(expressionText).append(')'); - } else { - out.append(expressionText).append("==").append(caseValue); - } + private static Set getFallThroughTargets(PsiCodeBlock body) { + return StreamEx.of(body.getStatements()) + .pairMap( + (s1, s2) -> s2 instanceof PsiSwitchLabelStatement switchLabelStmt2 + && !(s1 instanceof PsiSwitchLabeledRuleStatement) + && ControlFlowUtils.statementMayCompleteNormally(s1) ? switchLabelStmt2 : null + ) + .nonNull() + .collect(Collectors.toSet()); } - out.append(')'); - } - private static void dumpBody(SwitchStatementBranch branch, @NonNls StringBuilder out, CommentTracker commentTracker) { - final List bodyStatements = branch.getBodyElements(); - out.append('{'); - if (!bodyStatements.isEmpty()) { - PsiElement firstBodyElement = bodyStatements.get(0); - PsiElement prev = PsiTreeUtil.skipWhitespacesAndCommentsBackward(firstBodyElement); - if (prev instanceof PsiSwitchLabelStatementBase) { - PsiExpressionList values = ((PsiSwitchLabelStatementBase) prev).getCaseValues(); - if (values != null) { - out.append(CommentTracker.commentsBetween(values, firstBodyElement)); + @RequiredReadAction + private static void dumpBranch( + SwitchStatementBranch branch, + String expressionText, + boolean firstBranch, + boolean useEquals, + StringBuilder out, + CommentTracker commentTracker + ) { + if (!firstBranch) { + out.append("else "); } - } + dumpCaseValues(expressionText, branch.getCaseValues(), useEquals, out); + dumpBody(branch, out, commentTracker); } - for (PsiElement element : branch.getPendingDeclarations()) { - if (ReferencesSearch.search(element, new LocalSearchScope(bodyStatements.toArray(PsiElement.EMPTY_ARRAY))).findFirst() != null) { - if (element instanceof PsiVariable) { - PsiVariable var = (PsiVariable) element; - out.append(var.getType().getCanonicalText()).append(' ').append(var.getName()).append(';'); - } else { - // Class - out.append(element.getText()); + + private static void dumpCaseValues(String expressionText, List caseValues, boolean useEquals, StringBuilder out) { + out.append("if("); + boolean firstCaseValue = true; + for (String caseValue : caseValues) { + if (!firstCaseValue) { + out.append("||"); + } + firstCaseValue = false; + if (useEquals) { + out.append(caseValue).append(".equals(").append(expressionText).append(')'); + } + else { + out.append(expressionText).append("==").append(caseValue); + } } - } + out.append(')'); } - for (PsiElement bodyStatement : bodyStatements) { - if (bodyStatement instanceof PsiBlockStatement) { - final PsiBlockStatement blockStatement = (PsiBlockStatement) bodyStatement; - final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); - PsiElement start = PsiTreeUtil.skipWhitespacesForward(codeBlock.getFirstBodyElement()); - PsiElement end = PsiTreeUtil.skipWhitespacesBackward(codeBlock.getLastBodyElement()); - if (start != null && end != null && start != codeBlock.getRBrace()) { - for (PsiElement child = start; child != null; child = child.getNextSibling()) { - out.append(commentTracker.text(child)); - if (child == end) { - break; + @RequiredReadAction + private static void dumpBody(SwitchStatementBranch branch, StringBuilder out, CommentTracker commentTracker) { + List bodyStatements = branch.getBodyElements(); + out.append('{'); + if (!bodyStatements.isEmpty()) { + PsiElement firstBodyElement = bodyStatements.get(0); + PsiElement prev = PsiTreeUtil.skipWhitespacesAndCommentsBackward(firstBodyElement); + if (prev instanceof PsiSwitchLabelStatementBase switchLabelStatementBase) { + PsiExpressionList values = switchLabelStatementBase.getCaseValues(); + if (values != null) { + out.append(CommentTracker.commentsBetween(values, firstBodyElement)); + } + } + } + for (PsiElement element : branch.getPendingDeclarations()) { + if (ReferencesSearch.search(element, new LocalSearchScope(bodyStatements.toArray(PsiElement.EMPTY_ARRAY))) + .findFirst() != null) { + if (element instanceof PsiVariable variable) { + out.append(variable.getType().getCanonicalText()).append(' ').append(variable.getName()).append(';'); + } + else { + // Class + out.append(element.getText()); + } + } + } + + for (PsiElement bodyStatement : bodyStatements) { + if (bodyStatement instanceof PsiBlockStatement blockStmt) { + PsiCodeBlock codeBlock = blockStmt.getCodeBlock(); + PsiElement start = PsiTreeUtil.skipWhitespacesForward(codeBlock.getFirstBodyElement()); + PsiElement end = PsiTreeUtil.skipWhitespacesBackward(codeBlock.getLastBodyElement()); + if (start != null && end != null && start != codeBlock.getRBrace()) { + for (PsiElement child = start; child != null; child = child.getNextSibling()) { + out.append(commentTracker.text(child)); + if (child == end) { + break; + } + } + } + } + else { + out.append(commentTracker.text(bodyStatement)); } - } } - } else { - out.append(commentTracker.text(bodyStatement)); - } + out.append("\n").append("}"); } - out.append("\n").append("}"); - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/FormatPostfixTemplate.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/FormatPostfixTemplate.java index 8f0ceda124..bcab99966f 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/FormatPostfixTemplate.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/FormatPostfixTemplate.java @@ -2,24 +2,27 @@ package com.intellij.java.impl.codeInsight.template.postfix.templates; import com.intellij.java.language.LanguageLevel; -import com.intellij.java.language.psi.CommonClassNames; import consulo.application.dumb.DumbAware; +import consulo.java.language.module.util.JavaClassNames; import jakarta.annotation.Nonnull; import java.util.Collections; public class FormatPostfixTemplate extends JavaEditablePostfixTemplate implements DumbAware { - public FormatPostfixTemplate(@Nonnull JavaPostfixTemplateProvider provider) { - super("format", - "String.format($EXPR$, $END$)", - "String.format(expr)", - Collections.singleton( - new JavaPostfixTemplateExpressionCondition.JavaPostfixTemplateExpressionFqnCondition(CommonClassNames.JAVA_LANG_STRING)), - LanguageLevel.JDK_1_3, false, provider); - } + public FormatPostfixTemplate(@Nonnull JavaPostfixTemplateProvider provider) { + super( + "format", + "String.format($EXPR$, $END$)", + "String.format(expr)", + Collections.singleton( + new JavaPostfixTemplateExpressionCondition.JavaPostfixTemplateExpressionFqnCondition(JavaClassNames.JAVA_LANG_STRING) + ), + LanguageLevel.JDK_1_3, false, provider + ); + } - @Override - public boolean isBuiltin() { - return true; - } + @Override + public boolean isBuiltin() { + return true; + } } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/SwitchStatementPostfixTemplate.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/SwitchStatementPostfixTemplate.java index 0b26c63687..5edab920e5 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/SwitchStatementPostfixTemplate.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/SwitchStatementPostfixTemplate.java @@ -30,144 +30,157 @@ import java.util.function.Predicate; public class SwitchStatementPostfixTemplate extends SurroundPostfixTemplateBase implements DumbAware { - - private static final Predicate SWITCH_TYPE = e -> { - if (!(e instanceof PsiExpression expression)) return false; - - return DumbService.getInstance(expression.getProject()).computeWithAlternativeResolveEnabled(() -> { - final PsiType type = expression.getType(); - - if (type == null) return false; - if (PsiTypes.intType().isAssignableFrom(type)) return true; - if (type instanceof PsiClassType classType) { - if (PsiUtil.isAvailable(JavaFeature.PATTERNS_IN_SWITCH, expression)) return true; - - final PsiClass psiClass = classType.resolve(); - if (psiClass != null && psiClass.isEnum()) return true; - } - - if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING) && expression.getContainingFile() instanceof PsiJavaFile javaFile) { - if (PsiUtil.isAvailable(JavaFeature.STRING_SWITCH, javaFile)) return true; - } - - return false; - }); - }; - - public SwitchStatementPostfixTemplate() { - super("switch", "switch(expr)", JavaPostfixTemplatesUtils.JAVA_PSI_INFO, selectorTopmost(SWITCH_TYPE)); - } - - @Nonnull - @Override - protected Surrounder getSurrounder() { - return new JavaExpressionSurrounder() { - @Override - public boolean isApplicable(PsiExpression expr) { - return expr.isPhysical() && SWITCH_TYPE.test(expr); - } - - @Override - public TextRange surroundExpression(Project project, Editor editor, PsiExpression expr) throws IncorrectOperationException { - PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); - CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); - - PsiElement parent = expr.getParent(); - if (parent instanceof PsiExpressionStatement) { - PsiSwitchStatement switchStatement = (PsiSwitchStatement)factory.createStatementFromText("switch(1){case 1:}", null); - return postprocessSwitch(editor, expr, codeStyleManager, parent, switchStatement); - } - else if (PsiUtil.isAvailable(JavaFeature.ENHANCED_SWITCH, expr)) { - PsiSwitchExpression switchExpression = (PsiSwitchExpression)factory.createExpressionFromText("switch(1){case 1->1;}", null); - return postprocessSwitch(editor, expr, codeStyleManager, expr, switchExpression); - } - - return TextRange.from(editor.getCaretModel().getOffset(), 0); - } - - @Nonnull - private static TextRange postprocessSwitch(Editor editor, - PsiExpression expr, - CodeStyleManager codeStyleManager, - PsiElement toReplace, - PsiSwitchBlock switchBlock) { - - switchBlock = (PsiSwitchBlock)codeStyleManager.reformat(switchBlock); - PsiExpression selectorExpression = switchBlock.getExpression(); - if (selectorExpression != null) { - selectorExpression.replace(expr); + private static final Predicate SWITCH_TYPE = e -> { + if (!(e instanceof PsiExpression expression)) { + return false; } - switchBlock = (PsiSwitchBlock)toReplace.replace(switchBlock); - - PsiCodeBlock body = switchBlock.getBody(); - if (body != null) { - body = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(body); - if (body != null) { - TextRange range = body.getStatements()[0].getTextRange(); - editor.getDocument().deleteString(range.getStartOffset(), range.getEndOffset()); - return TextRange.from(range.getStartOffset(), 0); - } - } - return TextRange.from(editor.getCaretModel().getOffset(), 0); - } - - @Override - public String getTemplateDescription() { - return JavaBundle.message("switch.stmt.template.description"); - } + return DumbService.getInstance(expression.getProject()).computeWithAlternativeResolveEnabled(() -> { + final PsiType type = expression.getType(); + + if (type == null) { + return false; + } + if (PsiTypes.intType().isAssignableFrom(type)) { + return true; + } + if (type instanceof PsiClassType classType) { + if (PsiUtil.isAvailable(JavaFeature.PATTERNS_IN_SWITCH, expression)) { + return true; + } + + final PsiClass psiClass = classType.resolve(); + if (psiClass != null && psiClass.isEnum()) { + return true; + } + } + + if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING) && expression.getContainingFile() instanceof PsiJavaFile javaFile) { + if (PsiUtil.isAvailable(JavaFeature.STRING_SWITCH, javaFile)) { + return true; + } + } + + return false; + }); }; - } - - public static PostfixTemplateExpressionSelector selectorTopmost(Predicate additionalFilter) { - return new PostfixTemplateExpressionSelectorBase(additionalFilter) { - @Override - protected List getNonFilteredExpressions(@Nonnull PsiElement context, @Nonnull Document document, int offset) { - boolean isEnhancedSwitchAvailable = PsiUtil.isAvailable(JavaFeature.ENHANCED_SWITCH, context); - List result = new ArrayList<>(); - - for (PsiElement element = PsiTreeUtil.getNonStrictParentOfType(context, PsiExpression.class, PsiStatement.class); - element instanceof PsiExpression; element = element.getParent()) { - PsiElement parent = element.getParent(); - if (parent instanceof PsiExpressionStatement) { - result.add(element); - } - else if (isEnhancedSwitchAvailable && (isVariableInitializer(element, parent) || - isRightSideOfAssignment(element, parent) || - isReturnValue(element, parent) || - isArgumentList(parent))) { - result.add(element); - } - } - return result; - } - - @Override - protected Predicate getFilters(int offset) { - return super.getFilters(offset).and(getPsiErrorFilter()); - } - - @Nonnull - @Override - public Function getRenderer() { - return JavaPostfixTemplatesUtils.getRenderer(); - } - - private static boolean isVariableInitializer(PsiElement element, PsiElement parent) { - return parent instanceof PsiVariable && ((PsiVariable)parent).getInitializer() == element; - } - - private static boolean isRightSideOfAssignment(PsiElement element, PsiElement parent) { - return parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)parent).getRExpression() == element; - } - - private static boolean isReturnValue(PsiElement element, PsiElement parent) { - return parent instanceof PsiReturnStatement && ((PsiReturnStatement)parent).getReturnValue() == element; - } - - private static boolean isArgumentList(PsiElement parent) { - return parent instanceof PsiExpressionList && parent.getParent() instanceof PsiCall; - } - }; - } + + public SwitchStatementPostfixTemplate() { + super("switch", "switch(expr)", JavaPostfixTemplatesUtils.JAVA_PSI_INFO, selectorTopmost(SWITCH_TYPE)); + } + + @Nonnull + @Override + protected Surrounder getSurrounder() { + return new JavaExpressionSurrounder() { + @Override + public boolean isApplicable(PsiExpression expr) { + return expr.isPhysical() && SWITCH_TYPE.test(expr); + } + + @Override + public TextRange surroundExpression(Project project, Editor editor, PsiExpression expr) throws IncorrectOperationException { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + + PsiElement parent = expr.getParent(); + if (parent instanceof PsiExpressionStatement) { + PsiSwitchStatement switchStatement = (PsiSwitchStatement)factory.createStatementFromText("switch(1){case 1:}", null); + return postprocessSwitch(editor, expr, codeStyleManager, parent, switchStatement); + } + else if (PsiUtil.isAvailable(JavaFeature.ENHANCED_SWITCH, expr)) { + PsiSwitchExpression switchExpression = + (PsiSwitchExpression)factory.createExpressionFromText("switch(1){case 1->1;}", null); + return postprocessSwitch(editor, expr, codeStyleManager, expr, switchExpression); + } + + return TextRange.from(editor.getCaretModel().getOffset(), 0); + } + + @Nonnull + private static TextRange postprocessSwitch( + Editor editor, + PsiExpression expr, + CodeStyleManager codeStyleManager, + PsiElement toReplace, + PsiSwitchBlock switchBlock + ) { + switchBlock = (PsiSwitchBlock)codeStyleManager.reformat(switchBlock); + PsiExpression selectorExpression = switchBlock.getExpression(); + if (selectorExpression != null) { + selectorExpression.replace(expr); + } + + switchBlock = (PsiSwitchBlock)toReplace.replace(switchBlock); + + PsiCodeBlock body = switchBlock.getBody(); + if (body != null) { + body = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(body); + if (body != null) { + TextRange range = body.getStatements()[0].getTextRange(); + editor.getDocument().deleteString(range.getStartOffset(), range.getEndOffset()); + return TextRange.from(range.getStartOffset(), 0); + } + } + return TextRange.from(editor.getCaretModel().getOffset(), 0); + } + + @Override + public String getTemplateDescription() { + return JavaBundle.message("switch.stmt.template.description"); + } + }; + } + + public static PostfixTemplateExpressionSelector selectorTopmost(Predicate additionalFilter) { + return new PostfixTemplateExpressionSelectorBase(additionalFilter) { + @Override + protected List getNonFilteredExpressions(@Nonnull PsiElement context, @Nonnull Document document, int offset) { + boolean isEnhancedSwitchAvailable = PsiUtil.isAvailable(JavaFeature.ENHANCED_SWITCH, context); + List result = new ArrayList<>(); + + for (PsiElement element = PsiTreeUtil.getNonStrictParentOfType(context, PsiExpression.class, PsiStatement.class); + element instanceof PsiExpression; element = element.getParent()) { + PsiElement parent = element.getParent(); + if (parent instanceof PsiExpressionStatement) { + result.add(element); + } + else if (isEnhancedSwitchAvailable && (isVariableInitializer(element, parent) || + isRightSideOfAssignment(element, parent) || + isReturnValue(element, parent) || + isArgumentList(parent))) { + result.add(element); + } + } + return result; + } + + @Override + protected Predicate getFilters(int offset) { + return super.getFilters(offset).and(getPsiErrorFilter()); + } + + @Nonnull + @Override + public Function getRenderer() { + return JavaPostfixTemplatesUtils.getRenderer(); + } + + private static boolean isVariableInitializer(PsiElement element, PsiElement parent) { + return parent instanceof PsiVariable && ((PsiVariable)parent).getInitializer() == element; + } + + private static boolean isRightSideOfAssignment(PsiElement element, PsiElement parent) { + return parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)parent).getRExpression() == element; + } + + private static boolean isReturnValue(PsiElement element, PsiElement parent) { + return parent instanceof PsiReturnStatement && ((PsiReturnStatement)parent).getReturnValue() == element; + } + + private static boolean isArgumentList(PsiElement parent) { + return parent instanceof PsiExpressionList && parent.getParent() instanceof PsiCall; + } + }; + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/ThrowExceptionPostfixTemplate.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/ThrowExceptionPostfixTemplate.java index 092b815042..750f123588 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/ThrowExceptionPostfixTemplate.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/postfix/templates/ThrowExceptionPostfixTemplate.java @@ -9,17 +9,19 @@ import java.util.Collections; public class ThrowExceptionPostfixTemplate extends JavaEditablePostfixTemplate implements DumbAware { - public ThrowExceptionPostfixTemplate(@Nonnull JavaPostfixTemplateProvider provider) { - super("throw", - "throw $EXPR$;$END$", - "throw expr", - Collections.singleton( - new JavaPostfixTemplateExpressionCondition.JavaPostfixTemplateExpressionFqnCondition(CommonClassNames.JAVA_LANG_THROWABLE)), - LanguageLevel.JDK_1_3, true, provider); - } + public ThrowExceptionPostfixTemplate(@Nonnull JavaPostfixTemplateProvider provider) { + super("throw", + "throw $EXPR$;$END$", + "throw expr", + Collections.singleton( + new JavaPostfixTemplateExpressionCondition.JavaPostfixTemplateExpressionFqnCondition(CommonClassNames.JAVA_LANG_THROWABLE) + ), + LanguageLevel.JDK_1_3, true, provider + ); + } - @Override - public boolean isBuiltin() { - return true; - } + @Override + public boolean isBuiltin() { + return true; + } } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/ReplaceComputeWithComputeIfPresentFix.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/ReplaceComputeWithComputeIfPresentFix.java index 8600244e46..32a155e7e8 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/ReplaceComputeWithComputeIfPresentFix.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/ReplaceComputeWithComputeIfPresentFix.java @@ -18,53 +18,53 @@ import static consulo.util.lang.ObjectUtil.tryCast; public class ReplaceComputeWithComputeIfPresentFix implements LocalQuickFix, HighPriorityAction { - private static final CallMatcher MAP_COMPUTE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "compute"). - parameterTypes("K", CommonClassNames.JAVA_UTIL_FUNCTION_BI_FUNCTION); + private static final CallMatcher MAP_COMPUTE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "compute"). + parameterTypes("K", CommonClassNames.JAVA_UTIL_FUNCTION_BI_FUNCTION); - @Override - @Nonnull - public String getFamilyName() { - return JavaInspectionsBundle.message("inspection.data.flow.use.computeifpresent.quickfix"); - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiLambdaExpression.class); - if (lambda == null) { - return; - } - PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(lambda, PsiMethodCallExpression.class); - if (call == null || !"compute".equals(call.getMethodExpression().getReferenceName())) { - return; + @Override + @Nonnull + public String getFamilyName() { + return JavaInspectionsBundle.message("inspection.data.flow.use.computeifpresent.quickfix"); } - ExpressionUtils.bindCallTo(call, "computeIfPresent"); - } - @Contract("null -> null") - public static ReplaceComputeWithComputeIfPresentFix makeFix(PsiElement reference) { - if (!(reference instanceof PsiReferenceExpression)) { - return null; - } - PsiParameter parameter = tryCast(((PsiReferenceExpression) reference).resolve(), PsiParameter.class); - if (parameter == null) { - return null; - } - PsiParameterList parameterList = tryCast(parameter.getParent(), PsiParameterList.class); - if (parameterList == null || parameterList.getParametersCount() != 2 || parameterList.getParameterIndex(parameter) != 1) { - return null; + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiLambdaExpression.class); + if (lambda == null) { + return; + } + PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(lambda, PsiMethodCallExpression.class); + if (call == null || !"compute".equals(call.getMethodExpression().getReferenceName())) { + return; + } + ExpressionUtils.bindCallTo(call, "computeIfPresent"); } - PsiLambdaExpression lambda = tryCast(parameterList.getParent(), PsiLambdaExpression.class); - if (lambda == null) { - return null; - } - PsiExpressionList arguments = tryCast(lambda.getParent(), PsiExpressionList.class); - if (arguments == null || arguments.getExpressionCount() != 2 || arguments.getExpressions()[1] != lambda) { - return null; - } - PsiMethodCallExpression call = tryCast(arguments.getParent(), PsiMethodCallExpression.class); - if (!MAP_COMPUTE.test(call)) { - return null; + + @Contract("null -> null") + public static ReplaceComputeWithComputeIfPresentFix makeFix(PsiElement reference) { + if (!(reference instanceof PsiReferenceExpression)) { + return null; + } + PsiParameter parameter = tryCast(((PsiReferenceExpression)reference).resolve(), PsiParameter.class); + if (parameter == null) { + return null; + } + PsiParameterList parameterList = tryCast(parameter.getParent(), PsiParameterList.class); + if (parameterList == null || parameterList.getParametersCount() != 2 || parameterList.getParameterIndex(parameter) != 1) { + return null; + } + PsiLambdaExpression lambda = tryCast(parameterList.getParent(), PsiLambdaExpression.class); + if (lambda == null) { + return null; + } + PsiExpressionList arguments = tryCast(lambda.getParent(), PsiExpressionList.class); + if (arguments == null || arguments.getExpressionCount() != 2 || arguments.getExpressions()[1] != lambda) { + return null; + } + PsiMethodCallExpression call = tryCast(arguments.getParent(), PsiMethodCallExpression.class); + if (!MAP_COMPUTE.test(call)) { + return null; + } + return new ReplaceComputeWithComputeIfPresentFix(); } - return new ReplaceComputeWithComputeIfPresentFix(); - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/WrapWithMutableCollectionFix.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/WrapWithMutableCollectionFix.java index 684d2bc1ae..9d3c0bc5a6 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/WrapWithMutableCollectionFix.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/WrapWithMutableCollectionFix.java @@ -21,125 +21,126 @@ import jakarta.annotation.Nullable; public class WrapWithMutableCollectionFix implements LocalQuickFix { - private final String myVariableName; - private final String myCollectionName; - private final boolean myOnTheFly; + private final String myVariableName; + private final String myCollectionName; + private final boolean myOnTheFly; - public WrapWithMutableCollectionFix(String variableName, String collectionName, boolean onTheFly) { - myVariableName = variableName; - myCollectionName = collectionName; - myOnTheFly = onTheFly; - } - - @Nls(capitalization = Nls.Capitalization.Sentence) - @Nonnull - @Override - public String getName() { - return "Wrap '" + myVariableName + "' with '" + StringUtil.getShortName(myCollectionName) + "'"; - } - - @Nls(capitalization = Nls.Capitalization.Sentence) - @Nonnull - @Override - public String getFamilyName() { - return "Wrap with mutable collection"; - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiLocalVariable variable = getVariable(descriptor.getStartElement()); - if (variable == null) { - return; - } - PsiExpression initializer = variable.getInitializer(); - if (initializer == null) { - return; + public WrapWithMutableCollectionFix(String variableName, String collectionName, boolean onTheFly) { + myVariableName = variableName; + myCollectionName = collectionName; + myOnTheFly = onTheFly; } - PsiClassType type = ObjectUtil.tryCast(variable.getType(), PsiClassType.class); - if (type == null) { - return; - } - String typeParameters = ""; - if (myCollectionName.equals(CommonClassNames.JAVA_UTIL_HASH_MAP)) { - PsiType keyParameter = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_UTIL_MAP, 0, false); - PsiType valueParameter = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_UTIL_MAP, 1, false); - if (keyParameter != null && valueParameter != null) { - typeParameters = "<" + keyParameter.getCanonicalText() + "," + valueParameter.getCanonicalText() + ">"; - } - } else { - PsiType elementParameter = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_LANG_ITERABLE, 0, false); - if (elementParameter != null) { - typeParameters = "<" + elementParameter.getCanonicalText() + ">"; - } - } - CommentTracker ct = new CommentTracker(); - PsiElement replacement = - ct.replaceAndRestoreComments(initializer, "new " + myCollectionName + typeParameters + "(" + ct.text(initializer) + ")"); - RemoveRedundantTypeArgumentsUtil.removeRedundantTypeArguments(replacement); - if (myOnTheFly) { - HighlightUtils.highlightElement(replacement); - } - } - @Nullable - public static WrapWithMutableCollectionFix createFix(@Nonnull PsiElement anchor, boolean onTheFly) { - PsiLocalVariable variable = getVariable(anchor); - if (variable == null) { - return null; - } - PsiExpression initializer = variable.getInitializer(); - if (initializer == null) { - return null; + @Nls(capitalization = Nls.Capitalization.Sentence) + @Nonnull + @Override + public String getName() { + return "Wrap '" + myVariableName + "' with '" + StringUtil.getShortName(myCollectionName) + "'"; } - String wrapper = getWrapperByType(variable.getType()); - if (wrapper == null) { - return null; - } - PsiElement block = PsiUtil.getVariableCodeBlock(variable, null); - if (block == null) { - return null; - } - if (!HighlightControlFlowUtil.isEffectivelyFinal(variable, block, null)) { - return null; - } - return new WrapWithMutableCollectionFix(variable.getName(), wrapper, onTheFly); - } - @Nullable - private static PsiLocalVariable getVariable(@Nonnull PsiElement anchor) { - if (anchor.getParent() instanceof PsiReferenceExpression && anchor.getParent().getParent() instanceof PsiCallExpression) { - anchor = ((PsiReferenceExpression) anchor.getParent()).getQualifierExpression(); - } - if (!(anchor instanceof PsiExpression)) { - return null; + @Nls(capitalization = Nls.Capitalization.Sentence) + @Nonnull + @Override + public String getFamilyName() { + return "Wrap with mutable collection"; } - return ExpressionUtils.resolveLocalVariable((PsiExpression) anchor); - } - @Contract("null -> null") - @Nullable - private static String getWrapperByType(PsiType type) { - if (!(type instanceof PsiClassType)) { - return null; + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiLocalVariable variable = getVariable(descriptor.getStartElement()); + if (variable == null) { + return; + } + PsiExpression initializer = variable.getInitializer(); + if (initializer == null) { + return; + } + PsiClassType type = ObjectUtil.tryCast(variable.getType(), PsiClassType.class); + if (type == null) { + return; + } + String typeParameters = ""; + if (myCollectionName.equals(CommonClassNames.JAVA_UTIL_HASH_MAP)) { + PsiType keyParameter = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_UTIL_MAP, 0, false); + PsiType valueParameter = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_UTIL_MAP, 1, false); + if (keyParameter != null && valueParameter != null) { + typeParameters = "<" + keyParameter.getCanonicalText() + "," + valueParameter.getCanonicalText() + ">"; + } + } + else { + PsiType elementParameter = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_LANG_ITERABLE, 0, false); + if (elementParameter != null) { + typeParameters = "<" + elementParameter.getCanonicalText() + ">"; + } + } + CommentTracker ct = new CommentTracker(); + PsiElement replacement = + ct.replaceAndRestoreComments(initializer, "new " + myCollectionName + typeParameters + "(" + ct.text(initializer) + ")"); + RemoveRedundantTypeArgumentsUtil.removeRedundantTypeArguments(replacement); + if (myOnTheFly) { + HighlightUtils.highlightElement(replacement); + } } - PsiClass aClass = ((PsiClassType) type).resolve(); - if (aClass == null) { - return null; + + @Nullable + public static WrapWithMutableCollectionFix createFix(@Nonnull PsiElement anchor, boolean onTheFly) { + PsiLocalVariable variable = getVariable(anchor); + if (variable == null) { + return null; + } + PsiExpression initializer = variable.getInitializer(); + if (initializer == null) { + return null; + } + String wrapper = getWrapperByType(variable.getType()); + if (wrapper == null) { + return null; + } + PsiElement block = PsiUtil.getVariableCodeBlock(variable, null); + if (block == null) { + return null; + } + if (!HighlightControlFlowUtil.isEffectivelyFinal(variable, block, null)) { + return null; + } + return new WrapWithMutableCollectionFix(variable.getName(), wrapper, onTheFly); } - String name = aClass.getQualifiedName(); - if (name == null) { - return null; + + @Nullable + private static PsiLocalVariable getVariable(@Nonnull PsiElement anchor) { + if (anchor.getParent() instanceof PsiReferenceExpression && anchor.getParent().getParent() instanceof PsiCallExpression) { + anchor = ((PsiReferenceExpression)anchor.getParent()).getQualifierExpression(); + } + if (!(anchor instanceof PsiExpression)) { + return null; + } + return ExpressionUtils.resolveLocalVariable((PsiExpression)anchor); } - switch (name) { - case CommonClassNames.JAVA_LANG_ITERABLE: - case CommonClassNames.JAVA_UTIL_COLLECTION: - case CommonClassNames.JAVA_UTIL_LIST: - return CommonClassNames.JAVA_UTIL_ARRAY_LIST; - case CommonClassNames.JAVA_UTIL_SET: - return CommonClassNames.JAVA_UTIL_HASH_SET; - case CommonClassNames.JAVA_UTIL_MAP: - return CommonClassNames.JAVA_UTIL_HASH_MAP; + + @Contract("null -> null") + @Nullable + private static String getWrapperByType(PsiType type) { + if (!(type instanceof PsiClassType)) { + return null; + } + PsiClass aClass = ((PsiClassType)type).resolve(); + if (aClass == null) { + return null; + } + String name = aClass.getQualifiedName(); + if (name == null) { + return null; + } + switch (name) { + case CommonClassNames.JAVA_LANG_ITERABLE: + case CommonClassNames.JAVA_UTIL_COLLECTION: + case CommonClassNames.JAVA_UTIL_LIST: + return CommonClassNames.JAVA_UTIL_ARRAY_LIST; + case CommonClassNames.JAVA_UTIL_SET: + return CommonClassNames.JAVA_UTIL_HASH_SET; + case CommonClassNames.JAVA_UTIL_MAP: + return CommonClassNames.JAVA_UTIL_HASH_MAP; + } + return null; } - return null; - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/ig/bugs/MismatchedCollectionQueryUpdateInspection.java b/plugin/src/main/java/com/intellij/java/impl/ig/bugs/MismatchedCollectionQueryUpdateInspection.java index e3343ca5f9..455e855334 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ig/bugs/MismatchedCollectionQueryUpdateInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/ig/bugs/MismatchedCollectionQueryUpdateInspection.java @@ -30,6 +30,7 @@ import com.siyeh.ig.psiutils.SideEffectChecker; import com.siyeh.ig.ui.ExternalizableStringSet; import com.siyeh.localize.InspectionGadgetsLocalize; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.ide.impl.idea.codeInspection.ui.ListTable; import consulo.ide.impl.idea.codeInspection.ui.ListWrappingTableModel; @@ -58,491 +59,539 @@ public class MismatchedCollectionQueryUpdateInspection extends BaseInspection { - private static final CallMatcher TRANSFORMED = CallMatcher.staticCall( - JavaClassNames.JAVA_UTIL_COLLECTIONS, "asLifoQueue", "checkedCollection", "checkedList", "checkedMap", "checkedNavigableMap", - "checkedNavigableSet", "checkedQueue", "checkedSet", "checkedSortedMap", "checkedSortedSet", "newSetFromMap", "synchronizedCollection", - "synchronizedList", "synchronizedMap", "synchronizedNavigableMap", "synchronizedNavigableSet", "synchronizedSet", - "synchronizedSortedMap", "synchronizedSortedSet"); - private static final CallMatcher DERIVED = CallMatcher.anyOf( - CollectionUtils.DERIVED_COLLECTION, - CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_LIST, "subList"), - CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_SORTED_MAP, "headMap", "tailMap", "subMap"), - CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_SORTED_SET, "headSet", "tailSet", "subSet")); - private static final CallMatcher COLLECTION_SAFE_ARGUMENT_METHODS = - CallMatcher.anyOf( - CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_COLLECTION, "addAll", "removeAll", "containsAll", "remove"), - CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_MAP, "putAll", "remove") - ); - private static final Set COLLECTIONS_QUERIES = - Set.of("binarySearch", "disjoint", "indexOfSubList", "lastIndexOfSubList", "max", "min"); - private static final Set COLLECTIONS_UPDATES = Set.of("addAll", "fill", "copy", "replaceAll", "sort"); - private static final Set COLLECTIONS_ALL = - StreamEx.of(COLLECTIONS_QUERIES).append(COLLECTIONS_UPDATES).toImmutableSet(); - @SuppressWarnings("PublicField") - public final ExternalizableStringSet queryNames = - new ExternalizableStringSet( - "contains", "copyInto", "equals", "forEach", "get", "hashCode", "iterator", "parallelStream", "propertyNames", - "replaceAll", "save", "size", "store", "stream", "toArray", "toString", "write"); - @SuppressWarnings("PublicField") - public final ExternalizableStringSet updateNames = - new ExternalizableStringSet("add", "clear", "insert", "load", "merge", "offer", "poll", "pop", "push", "put", "remove", "replace", - "retain", "set", "take"); - @SuppressWarnings("PublicField") - public final ExternalizableStringSet ignoredClasses = new ExternalizableStringSet(); - - @Override - public JComponent createOptionsPanel() { - final ListTable queryNamesTable = - new ListTable(new ListWrappingTableModel(queryNames, InspectionGadgetsLocalize.queryColumnName().get())); - final JPanel queryNamesPanel = UiUtils.createAddRemovePanel(queryNamesTable); - - final ListTable updateNamesTable = - new ListTable(new ListWrappingTableModel(updateNames, InspectionGadgetsLocalize.updateColumnName().get())); - final JPanel updateNamesPanel = UiUtils.createAddRemovePanel(updateNamesTable); - - LocalizeValue ignoreClassesMessage = InspectionGadgetsLocalize.ignoredClassNames(); - final ListTable ignoredClassesTable = new ListTable(new ListWrappingTableModel(ignoredClasses, ignoreClassesMessage.get())); - final JPanel ignoredClassesPanel = UiUtils.createAddRemoveTreeClassChooserPanel( - ignoredClassesTable, - ignoreClassesMessage.get(), - CommonClassNames.JAVA_UTIL_COLLECTION, - CommonClassNames.JAVA_UTIL_MAP + private static final CallMatcher TRANSFORMED = CallMatcher.staticCall( + JavaClassNames.JAVA_UTIL_COLLECTIONS, + "asLifoQueue", + "checkedCollection", + "checkedList", + "checkedMap", + "checkedNavigableMap", + "checkedNavigableSet", + "checkedQueue", + "checkedSet", + "checkedSortedMap", + "checkedSortedSet", + "newSetFromMap", + "synchronizedCollection", + "synchronizedList", + "synchronizedMap", + "synchronizedNavigableMap", + "synchronizedNavigableSet", + "synchronizedSet", + "synchronizedSortedMap", + "synchronizedSortedSet" ); + private static final CallMatcher DERIVED = CallMatcher.anyOf( + CollectionUtils.DERIVED_COLLECTION, + CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_LIST, "subList"), + CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_SORTED_MAP, "headMap", "tailMap", "subMap"), + CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_SORTED_SET, "headSet", "tailSet", "subSet") + ); + private static final CallMatcher COLLECTION_SAFE_ARGUMENT_METHODS = + CallMatcher.anyOf( + CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_COLLECTION, "addAll", "removeAll", "containsAll", "remove"), + CallMatcher.instanceCall(JavaClassNames.JAVA_UTIL_MAP, "putAll", "remove") + ); + private static final Set COLLECTIONS_QUERIES = + Set.of("binarySearch", "disjoint", "indexOfSubList", "lastIndexOfSubList", "max", "min"); + private static final Set COLLECTIONS_UPDATES = Set.of("addAll", "fill", "copy", "replaceAll", "sort"); + private static final Set COLLECTIONS_ALL = + StreamEx.of(COLLECTIONS_QUERIES).append(COLLECTIONS_UPDATES).toImmutableSet(); + @SuppressWarnings("PublicField") + public final ExternalizableStringSet queryNames = new ExternalizableStringSet( + "contains", + "copyInto", + "equals", + "forEach", + "get", + "hashCode", + "iterator", + "parallelStream", + "propertyNames", + "replaceAll", + "save", + "size", + "store", + "stream", + "toArray", + "toString", + "write" + ); + @SuppressWarnings("PublicField") + public final ExternalizableStringSet updateNames = new ExternalizableStringSet( + "add", + "clear", + "insert", + "load", + "merge", + "offer", + "poll", + "pop", + "push", + "put", + "remove", + "replace", + "retain", + "set", + "take" + ); + @SuppressWarnings("PublicField") + public final ExternalizableStringSet ignoredClasses = new ExternalizableStringSet(); - final JPanel namesPanel = new JPanel(new GridLayout(1, 2, UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP)); - namesPanel.add(queryNamesPanel); - namesPanel.add(updateNamesPanel); - - final JPanel panel = new JPanel(new GridLayout(2, 1, UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP)); - panel.add(namesPanel); - panel.add(ignoredClassesPanel); - return panel; - } - - @Pattern(VALID_ID_PATTERN) - @Override - @Nonnull - public String getID() { - return "MismatchedQueryAndUpdateOfCollection"; - } - - @Override - @Nonnull - public String getDisplayName() { - return InspectionGadgetsLocalize.mismatchedUpdateCollectionDisplayName().get(); - } - - @Override - @Nonnull - public String buildErrorString(Object... infos) { - final boolean updated = (Boolean)infos[0]; - return updated - ? InspectionGadgetsLocalize.mismatchedUpdateCollectionProblemDescriptorUpdatedNotQueried().get() - : InspectionGadgetsLocalize.mismatchedUpdateCollectionProblemDescriptionQueriedNotUpdated().get(); - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Override - public boolean runForWholeFile() { - return true; - } - - @Override - public BaseInspectionVisitor buildVisitor() { - return new MismatchedCollectionQueryUpdateVisitor(); - } - - private QueryUpdateInfo getCollectionQueryUpdateInfo(@Nullable PsiVariable variable, PsiElement context) { - QueryUpdateInfo info = new QueryUpdateInfo(); - class Visitor extends JavaRecursiveElementWalkingVisitor { - @Override - public void visitReferenceExpression(PsiReferenceExpression ref) { - super.visitReferenceExpression(ref); - if (variable == null) { - if (ref.getQualifierExpression() == null) { - makeUpdated(); - makeQueried(); - } - } else if (ref.isReferenceTo(variable)) { - process(findEffectiveReference(ref)); - } - } + @Override + public JComponent createOptionsPanel() { + ListTable queryNamesTable = + new ListTable(new ListWrappingTableModel(queryNames, InspectionGadgetsLocalize.queryColumnName().get())); + JPanel queryNamesPanel = UiUtils.createAddRemovePanel(queryNamesTable); + + ListTable updateNamesTable = + new ListTable(new ListWrappingTableModel(updateNames, InspectionGadgetsLocalize.updateColumnName().get())); + JPanel updateNamesPanel = UiUtils.createAddRemovePanel(updateNamesTable); + + LocalizeValue ignoreClassesMessage = InspectionGadgetsLocalize.ignoredClassNames(); + ListTable ignoredClassesTable = new ListTable(new ListWrappingTableModel(ignoredClasses, ignoreClassesMessage.get())); + JPanel ignoredClassesPanel = UiUtils.createAddRemoveTreeClassChooserPanel( + ignoredClassesTable, + ignoreClassesMessage.get(), + JavaClassNames.JAVA_UTIL_COLLECTION, + JavaClassNames.JAVA_UTIL_MAP + ); + + JPanel namesPanel = new JPanel(new GridLayout(1, 2, UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP)); + namesPanel.add(queryNamesPanel); + namesPanel.add(updateNamesPanel); + + JPanel panel = new JPanel(new GridLayout(2, 1, UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP)); + panel.add(namesPanel); + panel.add(ignoredClassesPanel); + return panel; + } - @Override - public void visitThisExpression(PsiThisExpression expression) { - super.visitThisExpression(expression); - if (variable == null) { - process(findEffectiveReference(expression)); - } - } + @Pattern(VALID_ID_PATTERN) + @Override + @Nonnull + public String getID() { + return "MismatchedQueryAndUpdateOfCollection"; + } - private void makeUpdated() { - info.updated = true; - if (info.queried) { - stopWalking(); - } - } + @Override + @Nonnull + public String getDisplayName() { + return InspectionGadgetsLocalize.mismatchedUpdateCollectionDisplayName().get(); + } - private void makeQueried() { - info.queried = true; - if (info.updated) { - stopWalking(); - } - } + @Override + @Nonnull + public String buildErrorString(Object... infos) { + boolean updated = (Boolean)infos[0]; + return updated + ? InspectionGadgetsLocalize.mismatchedUpdateCollectionProblemDescriptorUpdatedNotQueried().get() + : InspectionGadgetsLocalize.mismatchedUpdateCollectionProblemDescriptionQueriedNotUpdated().get(); + } - private void process(PsiExpression reference) { - PsiMethodCallExpression qualifiedCall = ExpressionUtils.getCallForQualifier(reference); - if (qualifiedCall != null) { - processQualifiedCall(qualifiedCall); - return; - } - PsiElement parent = reference.getParent(); - if (parent instanceof PsiExpressionList) { - PsiExpressionList args = (PsiExpressionList) parent; - PsiCallExpression surroundingCall = ObjectUtil.tryCast(args.getParent(), PsiCallExpression.class); - if (surroundingCall != null) { - if (surroundingCall instanceof PsiMethodCallExpression && - processCollectionMethods((PsiMethodCallExpression) surroundingCall, reference)) { - return; + @Override + public boolean isEnabledByDefault() { + return true; + } + + @Override + public boolean runForWholeFile() { + return true; + } + + @Override + public BaseInspectionVisitor buildVisitor() { + return new MismatchedCollectionQueryUpdateVisitor(); + } + + private QueryUpdateInfo getCollectionQueryUpdateInfo(@Nullable PsiVariable variable, PsiElement context) { + QueryUpdateInfo info = new QueryUpdateInfo(); + class Visitor extends JavaRecursiveElementWalkingVisitor { + @Override + @RequiredReadAction + public void visitReferenceExpression(PsiReferenceExpression ref) { + super.visitReferenceExpression(ref); + if (variable == null) { + if (ref.getQualifierExpression() == null) { + makeUpdated(); + makeQueried(); + } + } + else if (ref.isReferenceTo(variable)) { + process(findEffectiveReference(ref)); + } } - makeQueried(); - if (!isQueryMethod(surroundingCall) && !COLLECTION_SAFE_ARGUMENT_METHODS.matches(surroundingCall)) { - makeUpdated(); + + @Override + @RequiredReadAction + public void visitThisExpression(@Nonnull PsiThisExpression expression) { + super.visitThisExpression(expression); + if (variable == null) { + process(findEffectiveReference(expression)); + } + } + + private void makeUpdated() { + info.updated = true; + if (info.queried) { + stopWalking(); + } + } + + private void makeQueried() { + info.queried = true; + if (info.updated) { + stopWalking(); + } + } + + @RequiredReadAction + private void process(PsiExpression reference) { + PsiMethodCallExpression qualifiedCall = ExpressionUtils.getCallForQualifier(reference); + if (qualifiedCall != null) { + processQualifiedCall(qualifiedCall); + return; + } + PsiElement parent = reference.getParent(); + if (parent instanceof PsiExpressionList args) { + PsiCallExpression surroundingCall = ObjectUtil.tryCast(args.getParent(), PsiCallExpression.class); + if (surroundingCall != null) { + if (surroundingCall instanceof PsiMethodCallExpression methodCall + && processCollectionMethods(methodCall, reference)) { + return; + } + makeQueried(); + if (!isQueryMethod(surroundingCall) && !COLLECTION_SAFE_ARGUMENT_METHODS.matches(surroundingCall)) { + makeUpdated(); + } + return; + } + } + if (parent instanceof PsiMethodReferenceExpression methodRefExpr) { + processQualifiedMethodReference(methodRefExpr); + return; + } + if (parent instanceof PsiForeachStatement forEach && forEach.getIteratedValue() == reference) { + makeQueried(); + return; + } + if (parent instanceof PsiAssignmentExpression assignment && assignment.getLExpression() == reference) { + PsiExpression rValue = assignment.getRExpression(); + if (rValue == null) { + return; + } + if (ExpressionUtils.nonStructuralChildren(rValue) + .allMatch(MismatchedCollectionQueryUpdateInspection::isEmptyCollectionInitializer)) { + return; + } + if (ExpressionUtils.nonStructuralChildren(rValue) + .allMatch(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer)) { + makeUpdated(); + return; + } + } + if (parent instanceof PsiPolyadicExpression polyadic) { + IElementType tokenType = polyadic.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.PLUS)) { + // String concatenation + makeQueried(); + return; + } + if (JavaTokenType.EQEQ.equals(tokenType) || JavaTokenType.NE.equals(tokenType)) { + return; + } + } + if (parent instanceof PsiAssertStatement assertStmt && assertStmt.getAssertDescription() == reference) { + makeQueried(); + return; + } + if (parent instanceof PsiInstanceOfExpression || parent instanceof PsiSynchronizedStatement) { + return; + } + // Any other reference + makeUpdated(); + makeQueried(); + } + + @RequiredReadAction + private void processQualifiedMethodReference(PsiMethodReferenceExpression expression) { + String methodName = expression.getReferenceName(); + if (isQueryMethodName(methodName)) { + makeQueried(); + } + if (isUpdateMethodName(methodName)) { + makeUpdated(); + } + PsiMethod method = ObjectUtil.tryCast(expression.resolve(), PsiMethod.class); + if (method == null || + PsiType.VOID.equals(method.getReturnType()) || + PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType(expression))) { + return; + } + makeQueried(); + } + + private boolean processCollectionMethods(PsiMethodCallExpression call, PsiExpression arg) { + PsiExpressionList expressionList = call.getArgumentList(); + String name = call.getMethodExpression().getReferenceName(); + if (!COLLECTIONS_ALL.contains(name) || !isCollectionsClassMethod(call)) { + return false; + } + if (COLLECTIONS_QUERIES.contains(name) && !(call.getParent() instanceof PsiExpressionStatement)) { + makeQueried(); + return true; + } + if (COLLECTIONS_UPDATES.contains(name)) { + int index = ArrayUtil.indexOf(expressionList.getExpressions(), arg); + if (index == 0) { + makeUpdated(); + } + else { + makeQueried(); + } + return true; + } + return false; + } + + private void processQualifiedCall(PsiMethodCallExpression call) { + boolean voidContext = ExpressionUtils.isVoidContext(call); + String name = call.getMethodExpression().getReferenceName(); + boolean queryQualifier = isQueryMethodName(name); + boolean updateQualifier = isUpdateMethodName(name); + if (queryQualifier && + (!voidContext || PsiType.VOID.equals(call.getType()) || "toArray".equals(name) && !call.getArgumentList().isEmpty())) { + makeQueried(); + } + if (updateQualifier) { + makeUpdated(); + if (!voidContext) { + makeQueried(); + } + } + if (!queryQualifier && !updateQualifier) { + if (!isQueryMethod(call)) { + makeUpdated(); + } + makeQueried(); + } + } + + private PsiExpression findEffectiveReference(PsiExpression expression) { + while (true) { + PsiElement parent = expression.getParent(); + if (parent instanceof PsiParenthesizedExpression + || parent instanceof PsiTypeCastExpression + || parent instanceof PsiConditionalExpression) { + expression = (PsiExpression)parent; + continue; + } + if (parent instanceof PsiReferenceExpression) { + PsiMethodCallExpression grandParent = ObjectUtil.tryCast(parent.getParent(), PsiMethodCallExpression.class); + if (DERIVED.test(grandParent)) { + expression = grandParent; + continue; + } + } + if (parent instanceof PsiExpressionList) { + PsiMethodCallExpression grandParent = ObjectUtil.tryCast(parent.getParent(), PsiMethodCallExpression.class); + if (TRANSFORMED.test(grandParent)) { + expression = grandParent; + continue; + } + } + break; + } + return expression; } - return; - } - } - if (parent instanceof PsiMethodReferenceExpression) { - processQualifiedMethodReference(((PsiMethodReferenceExpression) parent)); - return; - } - if (parent instanceof PsiForeachStatement && ((PsiForeachStatement) parent).getIteratedValue() == reference) { - makeQueried(); - return; - } - if (parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression) parent).getLExpression() == reference) { - PsiExpression rValue = ((PsiAssignmentExpression) parent).getRExpression(); - if (rValue == null) { - return; - } - if (ExpressionUtils.nonStructuralChildren(rValue) - .allMatch(MismatchedCollectionQueryUpdateInspection::isEmptyCollectionInitializer)) { - return; - } - if (ExpressionUtils.nonStructuralChildren(rValue) - .allMatch(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer)) { - makeUpdated(); - return; - } - } - if (parent instanceof PsiPolyadicExpression) { - IElementType tokenType = ((PsiPolyadicExpression) parent).getOperationTokenType(); - if (tokenType.equals(JavaTokenType.PLUS)) { - // String concatenation - makeQueried(); - return; - } - if (tokenType.equals(JavaTokenType.EQEQ) || tokenType.equals(JavaTokenType.NE)) { - return; - } - } - if (parent instanceof PsiAssertStatement && ((PsiAssertStatement) parent).getAssertDescription() == reference) { - makeQueried(); - return; - } - if (parent instanceof PsiInstanceOfExpression || parent instanceof PsiSynchronizedStatement) { - return; } - // Any other reference - makeUpdated(); - makeQueried(); - } - - private void processQualifiedMethodReference(PsiMethodReferenceExpression expression) { - final String methodName = expression.getReferenceName(); - if (isQueryMethodName(methodName)) { - makeQueried(); + Visitor visitor = new Visitor(); + context.accept(visitor); + return info; + } + + private boolean isQueryMethodName(String methodName) { + return isQueryUpdateMethodName(methodName, queryNames); + } + + private boolean isUpdateMethodName(String methodName) { + return isQueryUpdateMethodName(methodName, updateNames); + } + + static boolean isEmptyCollectionInitializer(PsiExpression initializer) { + if (!(initializer instanceof PsiNewExpression newExpression)) { + return ConstructionUtils.isEmptyCollectionInitializer(initializer); } - if (isUpdateMethodName(methodName)) { - makeUpdated(); + PsiExpressionList argumentList = newExpression.getArgumentList(); + if (argumentList == null) { + return false; } - final PsiMethod method = ObjectUtil.tryCast(expression.resolve(), PsiMethod.class); - if (method == null || - PsiType.VOID.equals(method.getReturnType()) || - PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType(expression))) { - return; + PsiExpression[] arguments = argumentList.getExpressions(); + for (PsiExpression argument : arguments) { + PsiType argumentType = argument.getType(); + if (argumentType == null) { + return false; + } + if (CollectionUtils.isCollectionClassOrInterface(argumentType)) { + return false; + } + if (argumentType instanceof PsiArrayType) { + return false; + } } - makeQueried(); - } - - private boolean processCollectionMethods(PsiMethodCallExpression call, PsiExpression arg) { - PsiExpressionList expressionList = call.getArgumentList(); - String name = call.getMethodExpression().getReferenceName(); - if (!COLLECTIONS_ALL.contains(name) || !isCollectionsClassMethod(call)) { - return false; + return true; + } + + static boolean isCollectionInitializer(PsiExpression initializer) { + return isEmptyCollectionInitializer(initializer) || ConstructionUtils.isPrepopulatedCollectionInitializer(initializer); + } + + private static boolean isQueryUpdateMethodName(String methodName, Set myNames) { + if (methodName == null) { + return false; } - if (COLLECTIONS_QUERIES.contains(name) && !(call.getParent() instanceof PsiExpressionStatement)) { - makeQueried(); - return true; + if (myNames.contains(methodName)) { + return true; } - if (COLLECTIONS_UPDATES.contains(name)) { - int index = ArrayUtil.indexOf(expressionList.getExpressions(), arg); - if (index == 0) { - makeUpdated(); - } else { - makeQueried(); - } - return true; + for (String updateName : myNames) { + if (methodName.startsWith(updateName)) { + return true; + } } return false; - } - - private void processQualifiedCall(PsiMethodCallExpression call) { - boolean voidContext = ExpressionUtils.isVoidContext(call); - String name = call.getMethodExpression().getReferenceName(); - boolean queryQualifier = isQueryMethodName(name); - boolean updateQualifier = isUpdateMethodName(name); - if (queryQualifier && - (!voidContext || PsiType.VOID.equals(call.getType()) || "toArray".equals(name) && !call.getArgumentList().isEmpty())) { - makeQueried(); + } + + private static boolean isCollectionsClassMethod(PsiMethodCallExpression call) { + PsiMethod method = call.resolveMethod(); + if (method == null) { + return false; } - if (updateQualifier) { - makeUpdated(); - if (!voidContext) { - makeQueried(); - } + PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return false; } - if (!queryQualifier && !updateQualifier) { - if (!isQueryMethod(call)) { - makeUpdated(); - } - makeQueried(); + String qualifiedName = aClass.getQualifiedName(); + return JavaClassNames.JAVA_UTIL_COLLECTIONS.equals(qualifiedName); + } + + private static boolean isQueryMethod(@Nonnull PsiCallExpression call) { + PsiType type = call.getType(); + boolean immutable = isImmutable(type); + // If pure method returns mutable object, then it's possible that further mutation of that object will modify the original collection + if (!immutable) { + immutable = call instanceof PsiNewExpression && CollectionUtils.isConcreteCollectionClass(type); } - } - - private PsiExpression findEffectiveReference(PsiExpression expression) { - while (true) { - PsiElement parent = expression.getParent(); - if (parent instanceof PsiParenthesizedExpression || parent instanceof PsiTypeCastExpression || - parent instanceof PsiConditionalExpression) { - expression = (PsiExpression) parent; - continue; - } - if (parent instanceof PsiReferenceExpression) { - PsiMethodCallExpression grandParent = ObjectUtil.tryCast(parent.getParent(), PsiMethodCallExpression.class); - if (DERIVED.test(grandParent)) { - expression = grandParent; - continue; + PsiMethod method = call.resolveMethod(); + if (!immutable && method != null) { + if (PsiUtil.resolveClassInClassTypeOnly(method.getReturnType()) instanceof PsiTypeParameter returnTypeParam) { + // method returning unbounded type parameter is unlikely to allow modify original collection via the returned value + immutable = returnTypeParam.getExtendsList().getReferencedTypes().length == 0; } - } - if (parent instanceof PsiExpressionList) { - PsiMethodCallExpression grandParent = ObjectUtil.tryCast(parent.getParent(), PsiMethodCallExpression.class); - if (TRANSFORMED.test(grandParent)) { - expression = grandParent; - continue; + if (!immutable) { + immutable = Mutability.getMutability(method).isUnmodifiable(); } - } - break; } - return expression; - } - } - Visitor visitor = new Visitor(); - context.accept(visitor); - return info; - } - - private boolean isQueryMethodName(String methodName) { - return isQueryUpdateMethodName(methodName, queryNames); - } - - private boolean isUpdateMethodName(String methodName) { - return isQueryUpdateMethodName(methodName, updateNames); - } - - static boolean isEmptyCollectionInitializer(PsiExpression initializer) { - if (!(initializer instanceof PsiNewExpression)) { - return ConstructionUtils.isEmptyCollectionInitializer(initializer); - } - final PsiNewExpression newExpression = (PsiNewExpression) initializer; - final PsiExpressionList argumentList = newExpression.getArgumentList(); - if (argumentList == null) { - return false; - } - final PsiExpression[] arguments = argumentList.getExpressions(); - for (final PsiExpression argument : arguments) { - final PsiType argumentType = argument.getType(); - if (argumentType == null) { - return false; - } - if (CollectionUtils.isCollectionClassOrInterface(argumentType)) { - return false; - } - if (argumentType instanceof PsiArrayType) { - return false; - } + return immutable && !SideEffectChecker.mayHaveSideEffects(call); } - return true; - } - static boolean isCollectionInitializer(PsiExpression initializer) { - return isEmptyCollectionInitializer(initializer) || ConstructionUtils.isPrepopulatedCollectionInitializer(initializer); - } - - private static boolean isQueryUpdateMethodName(String methodName, Set myNames) { - if (methodName == null) { - return false; - } - if (myNames.contains(methodName)) { - return true; - } - for (String updateName : myNames) { - if (methodName.startsWith(updateName)) { - return true; - } + static class QueryUpdateInfo { + boolean updated; + boolean queried; } - return false; - } - private static boolean isCollectionsClassMethod(PsiMethodCallExpression call) { - final PsiMethod method = call.resolveMethod(); - if (method == null) { - return false; - } - final PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return false; - } - final String qualifiedName = aClass.getQualifiedName(); - return CommonClassNames.JAVA_UTIL_COLLECTIONS.equals(qualifiedName); - } - - private static boolean isQueryMethod(@Nonnull PsiCallExpression call) { - PsiType type = call.getType(); - boolean immutable = isImmutable(type); - // If pure method returns mutable object, then it's possible that further mutation of that object will modify the original collection - if (!immutable) { - immutable = call instanceof PsiNewExpression && CollectionUtils.isConcreteCollectionClass(type); - } - PsiMethod method = call.resolveMethod(); - if (!immutable && method != null) { - PsiClass returnType = PsiUtil.resolveClassInClassTypeOnly(method.getReturnType()); - if (returnType instanceof PsiTypeParameter) { - // method returning unbounded type parameter is unlikely to allow modify original collection via the returned value - immutable = ((PsiTypeParameter) returnType).getExtendsList().getReferencedTypes().length == 0; - } - if (!immutable) { - immutable = Mutability.getMutability(method).isUnmodifiable(); - } - } - return immutable && !SideEffectChecker.mayHaveSideEffects(call); - } - - static class QueryUpdateInfo { - boolean updated; - boolean queried; - } - - private class MismatchedCollectionQueryUpdateVisitor extends BaseInspectionVisitor { - private void register(PsiVariable variable, boolean written) { - if (written) { - PsiExpression initializer = variable.getInitializer(); - if (initializer != null) { - List expressions = ExpressionUtils.nonStructuralChildren(initializer).collect(Collectors.toList()); - if (!expressions.stream().allMatch(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer)) { - expressions.stream().filter(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer) - .forEach(emptyCollection -> registerError(emptyCollection, Boolean.TRUE)); - return; - } + private class MismatchedCollectionQueryUpdateVisitor extends BaseInspectionVisitor { + private void register(PsiVariable variable, boolean written) { + if (written) { + PsiExpression initializer = variable.getInitializer(); + if (initializer != null) { + List expressions = ExpressionUtils.nonStructuralChildren(initializer).collect(Collectors.toList()); + if (!expressions.stream().allMatch(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer)) { + expressions.stream().filter(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer) + .forEach(emptyCollection -> registerError(emptyCollection, Boolean.TRUE)); + return; + } + } + } + registerVariableError(variable, written); } - } - registerVariableError(variable, written); - } - @Override - public void visitField(@Nonnull PsiField field) { - super.visitField(field); - if (!field.hasModifierProperty(PsiModifier.PRIVATE)) { - PsiClass aClass = field.getContainingClass(); - if (aClass == null || !aClass.hasModifierProperty(PsiModifier.PRIVATE) || field.hasModifierProperty(PsiModifier.PUBLIC)) { - // Public field within private class can be written/read via reflection even without setAccessible hacks - // so we don't analyze such fields to reduce false-positives - return; + @Override + public void visitField(@Nonnull PsiField field) { + super.visitField(field); + if (!field.isPrivate()) { + PsiClass aClass = field.getContainingClass(); + if (aClass == null || !aClass.isPrivate() || field.isPublic()) { + // Public field within private class can be written/read via reflection even without setAccessible hacks + // so we don't analyze such fields to reduce false-positives + return; + } + } + PsiClass containingClass = PsiUtil.getTopLevelClass(field); + if (!checkVariable(field, containingClass)) { + return; + } + QueryUpdateInfo info = getCollectionQueryUpdateInfo(field, containingClass); + boolean written = info.updated || updatedViaInitializer(field); + boolean read = info.queried || queriedViaInitializer(field); + if (read == written || UnusedSymbolUtil.isImplicitWrite(field) || UnusedSymbolUtil.isImplicitRead(field)) { + // Even implicit read of the mutable collection field may cause collection change + return; + } + register(field, written); } - } - final PsiClass containingClass = PsiUtil.getTopLevelClass(field); - if (!checkVariable(field, containingClass)) { - return; - } - QueryUpdateInfo info = getCollectionQueryUpdateInfo(field, containingClass); - final boolean written = info.updated || updatedViaInitializer(field); - final boolean read = info.queried || queriedViaInitializer(field); - if (read == written || UnusedSymbolUtil.isImplicitWrite(field) || UnusedSymbolUtil.isImplicitRead(field)) { - // Even implicit read of the mutable collection field may cause collection change - return; - } - register(field, written); - } - @Override - public void visitLocalVariable(@Nonnull PsiLocalVariable variable) { - super.visitLocalVariable(variable); - final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); - if (!checkVariable(variable, codeBlock)) { - return; - } - QueryUpdateInfo info = getCollectionQueryUpdateInfo(variable, codeBlock); - final boolean written = info.updated || updatedViaInitializer(variable); - final boolean read = info.queried || queriedViaInitializer(variable); - if (read != written) { - register(variable, written); - } - } + @Override + public void visitLocalVariable(@Nonnull PsiLocalVariable variable) { + super.visitLocalVariable(variable); + PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); + if (!checkVariable(variable, codeBlock)) { + return; + } + QueryUpdateInfo info = getCollectionQueryUpdateInfo(variable, codeBlock); + boolean written = info.updated || updatedViaInitializer(variable); + boolean read = info.queried || queriedViaInitializer(variable); + if (read != written) { + register(variable, written); + } + } - private boolean checkVariable(PsiVariable variable, PsiElement context) { - if (context == null) { - return false; - } - final PsiType type = variable.getType(); - if (!CollectionUtils.isCollectionClassOrInterface(type)) { - return false; - } - return ignoredClasses.stream().noneMatch(className -> InheritanceUtil.isInheritor(type, className)); - } + private boolean checkVariable(PsiVariable variable, PsiElement context) { + if (context == null) { + return false; + } + PsiType type = variable.getType(); + return CollectionUtils.isCollectionClassOrInterface(type) + && ignoredClasses.stream().noneMatch(className -> InheritanceUtil.isInheritor(type, className)); + } - private boolean updatedViaInitializer(PsiVariable variable) { - final PsiExpression initializer = variable.getInitializer(); - if (initializer != null && - !ExpressionUtils.nonStructuralChildren(initializer) - .allMatch(MismatchedCollectionQueryUpdateInspection::isEmptyCollectionInitializer)) { - return true; - } - if (initializer instanceof PsiNewExpression) { - final PsiNewExpression newExpression = (PsiNewExpression) initializer; - final PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); - if (anonymousClass != null) { - if (getCollectionQueryUpdateInfo(null, anonymousClass).updated) { - return true; - } - final ThisPassedAsArgumentVisitor visitor = new ThisPassedAsArgumentVisitor(); - anonymousClass.accept(visitor); - if (visitor.isPassed()) { - return true; - } + private boolean updatedViaInitializer(PsiVariable variable) { + PsiExpression initializer = variable.getInitializer(); + if (initializer != null && + !ExpressionUtils.nonStructuralChildren(initializer) + .allMatch(MismatchedCollectionQueryUpdateInspection::isEmptyCollectionInitializer)) { + return true; + } + if (initializer instanceof PsiNewExpression newExpression) { + PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); + if (anonymousClass != null) { + if (getCollectionQueryUpdateInfo(null, anonymousClass).updated) { + return true; + } + ThisPassedAsArgumentVisitor visitor = new ThisPassedAsArgumentVisitor(); + anonymousClass.accept(visitor); + if (visitor.isPassed()) { + return true; + } + } + } + return false; } - } - return false; - } - private boolean queriedViaInitializer(PsiVariable variable) { - final PsiExpression initializer = variable.getInitializer(); - return initializer != null && - ExpressionUtils.nonStructuralChildren(initializer) - .noneMatch(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer); + private boolean queriedViaInitializer(PsiVariable variable) { + PsiExpression initializer = variable.getInitializer(); + return initializer != null && + ExpressionUtils.nonStructuralChildren(initializer) + .noneMatch(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer); + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/ig/performance/SetReplaceableByEnumSetInspection.java b/plugin/src/main/java/com/intellij/java/impl/ig/performance/SetReplaceableByEnumSetInspection.java index cd2ec84b26..681820b460 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ig/performance/SetReplaceableByEnumSetInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/ig/performance/SetReplaceableByEnumSetInspection.java @@ -29,64 +29,51 @@ @ExtensionImpl public class SetReplaceableByEnumSetInspection extends BaseInspection { + @Override + @Nonnull + public String getDisplayName() { + return InspectionGadgetsLocalize.setReplaceableByEnumSetDisplayName().get(); + } - @Override - @Nonnull - public String getDisplayName() { - return InspectionGadgetsLocalize.setReplaceableByEnumSetDisplayName().get(); - } - - @Override - @Nonnull - protected String buildErrorString(Object... infos) { - return InspectionGadgetsLocalize.setReplaceableByEnumSetProblemDescriptor().get(); - } - - @Override - public BaseInspectionVisitor buildVisitor() { - return new SetReplaceableByEnumSetVisitor(); - } - - private static class SetReplaceableByEnumSetVisitor - extends BaseInspectionVisitor { + @Override + @Nonnull + protected String buildErrorString(Object... infos) { + return InspectionGadgetsLocalize.setReplaceableByEnumSetProblemDescriptor().get(); + } @Override - public void visitNewExpression( - @Nonnull PsiNewExpression expression) { - super.visitNewExpression(expression); - final PsiType type = expression.getType(); - if (!(type instanceof PsiClassType)) { - return; - } - final PsiClassType classType = (PsiClassType)type; - if (!classType.hasParameters()) { - return; - } - final PsiType[] typeArguments = classType.getParameters(); - if (typeArguments.length != 1) { - return; - } - final PsiType argumentType = typeArguments[0]; - if (!(argumentType instanceof PsiClassType)) { - return; - } - if (!TypeUtils.expressionHasTypeOrSubtype(expression, - JavaClassNames.JAVA_UTIL_SET)) { - return; - } - if (TypeUtils.expressionHasTypeOrSubtype(expression, - "java.util.EnumSet")) { - return; - } - final PsiClassType argumentClassType = (PsiClassType)argumentType; - final PsiClass argumentClass = argumentClassType.resolve(); - if (argumentClass == null) { - return; - } - if (!argumentClass.isEnum()) { - return; - } - registerNewExpressionError(expression); + public BaseInspectionVisitor buildVisitor() { + return new SetReplaceableByEnumSetVisitor(); + } + + private static class SetReplaceableByEnumSetVisitor extends BaseInspectionVisitor { + @Override + public void visitNewExpression(@Nonnull PsiNewExpression expression) { + super.visitNewExpression(expression); + PsiType type = expression.getType(); + if (!(type instanceof PsiClassType classType) || !classType.hasParameters()) { + return; + } + PsiType[] typeArguments = classType.getParameters(); + if (typeArguments.length != 1) { + return; + } + PsiType argumentType = typeArguments[0]; + if (!(argumentType instanceof PsiClassType argumentClassType)) { + return; + } + if (!TypeUtils.expressionHasTypeOrSubtype(expression, JavaClassNames.JAVA_UTIL_SET) + || TypeUtils.expressionHasTypeOrSubtype(expression, JavaClassNames.JAVA_UTIL_ENUM_SET)) { + return; + } + PsiClass argumentClass = argumentClassType.resolve(); + if (argumentClass == null) { + return; + } + if (!argumentClass.isEnum()) { + return; + } + registerNewExpressionError(expression); + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java b/plugin/src/main/java/com/intellij/java/impl/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java index d63a5af189..cd7aeed52f 100644 --- a/plugin/src/main/java/com/intellij/java/impl/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java +++ b/plugin/src/main/java/com/intellij/java/impl/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java @@ -28,8 +28,8 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ServiceImpl; -import consulo.application.util.function.Processor; import consulo.application.util.matcher.NameUtil; import consulo.application.util.matcher.NameUtilCore; import consulo.java.language.module.util.JavaClassNames; @@ -52,12 +52,11 @@ import consulo.util.collection.ContainerUtil; import consulo.util.lang.Comparing; import consulo.util.lang.StringUtil; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.jetbrains.annotations.NonNls; -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; import java.beans.Introspector; import java.util.*; @@ -67,1297 +66,1353 @@ @Singleton @ServiceImpl public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager { - private static final Logger LOG = Logger.getInstance(JavaCodeStyleManagerImpl.class); - private static final String IMPL_SUFFIX = "Impl"; - private static final String GET_PREFIX = "get"; - private static final String IS_PREFIX = "is"; - private static final String FIND_PREFIX = "find"; - private static final String CREATE_PREFIX = "create"; - private static final String SET_PREFIX = "set"; - private static final String AS_PREFIX = "as"; - private static final String TO_PREFIX = "to"; - - private static final String[] ourPrepositions = { - "as", "at", "by", "down", "for", "from", "in", "into", "of", "on", "onto", "out", "over", - "per", "to", "up", "upon", "via", "with"}; - private static final String[] ourCommonTypeSuffixes = {"Entity"}; - - private final Project myProject; - - @Inject - public JavaCodeStyleManagerImpl(final Project project) { - myProject = project; - } - - @Nonnull - @Override - public PsiElement shortenClassReferences(@Nonnull PsiElement element) throws IncorrectOperationException { - return shortenClassReferences(element, 0); - } - - @Nonnull - @Override - public PsiElement shortenClassReferences(@Nonnull PsiElement element, int flags) throws IncorrectOperationException { - CheckUtil.checkWritable(element); - if (!SourceTreeToPsiMap.hasTreeElement(element)) { - return element; - } - - final boolean addImports = (flags & DO_NOT_ADD_IMPORTS) == 0; - final boolean incompleteCode = (flags & INCOMPLETE_CODE) != 0; - - final ReferenceAdjuster adjuster = ReferenceAdjuster.forLanguage(element.getLanguage()); - if (adjuster != null) { - final ASTNode reference = adjuster.process(element.getNode(), addImports, incompleteCode, myProject); - return SourceTreeToPsiMap.treeToPsiNotNull(reference); - } - else { - return element; - } - } - - @Override - public void shortenClassReferences(@Nonnull PsiElement element, int startOffset, int endOffset) throws IncorrectOperationException { - CheckUtil.checkWritable(element); - if (SourceTreeToPsiMap.hasTreeElement(element)) { - final ReferenceAdjuster adjuster = ReferenceAdjuster.forLanguage(element.getLanguage()); - if (adjuster != null) { - adjuster.processRange(element.getNode(), startOffset, endOffset, myProject); - } - } - } - - @Nonnull - @Override - public PsiElement qualifyClassReferences(@Nonnull PsiElement element) { - final ReferenceAdjuster adjuster = ReferenceAdjuster.forLanguage(element.getLanguage()); - if (adjuster != null) { - final ASTNode reference = adjuster.process(element.getNode(), false, false, true, true); - return SourceTreeToPsiMap.treeToPsiNotNull(reference); - } - return element; - } - - @Override - public void optimizeImports(@Nonnull PsiFile file) throws IncorrectOperationException { - CheckUtil.checkWritable(file); - if (file instanceof PsiJavaFile) { - PsiImportList newList = prepareOptimizeImportsResult((PsiJavaFile)file); - if (newList != null) { - final PsiImportList importList = ((PsiJavaFile)file).getImportList(); - if (importList != null) { - importList.replace(newList); - } - } - } - } - - @Override - public boolean hasConflictingOnDemandImport(@Nonnull PsiJavaFile file, @Nonnull PsiClass psiClass, @Nonnull String referenceName) { - return ImportHelper.hasConflictingOnDemandImport(file, psiClass, referenceName); - } - - @Override - public PsiImportList prepareOptimizeImportsResult(@Nonnull PsiJavaFile file) { - return new ImportHelper(JavaCodeStyleSettings.getInstance(file)).prepareOptimizeImportsResult(file); - } - - @Override - public boolean addImport(@Nonnull PsiJavaFile file, @Nonnull PsiClass refClass) { - return new ImportHelper(JavaCodeStyleSettings.getInstance(file)).addImport(file, refClass); - } - - @Override - public void removeRedundantImports(@Nonnull final PsiJavaFile file) throws IncorrectOperationException { - final Collection redundant = findRedundantImports(file); - if (redundant == null) { - return; - } - - for (final PsiImportStatementBase importStatement : redundant) { - final PsiJavaCodeReferenceElement ref = importStatement.getImportReference(); - //Do not remove non-resolving refs - if (ref == null || ref.resolve() == null) { - continue; - } - - importStatement.delete(); - } - } - - @Override - @Nullable - public Collection findRedundantImports(@Nonnull final PsiJavaFile file) { - final PsiImportList importList = file.getImportList(); - if (importList == null) { - return null; - } - final PsiImportStatementBase[] imports = importList.getAllImportStatements(); - if (imports.length == 0) { - return null; - } - - Set allImports = new HashSet(Arrays.asList(imports)); - final Collection redundant; - /*if(FileTypeUtils.isInServerPageFile(file)) - { - // remove only duplicate imports - redundant = ContainerUtil.newIdentityTroveSet(); - ContainerUtil.addAll(redundant, imports); - redundant.removeAll(allImports); - for(PsiImportStatementBase importStatement : imports) - { - if(importStatement instanceof JspxImportStatement && importStatement.isForeignFileImport()) - { - redundant.remove(importStatement); - } - } - } - else */ - { - redundant = allImports; - final List roots = file.getViewProvider().getAllFiles(); - for (PsiElement root : roots) { - root.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - if (!reference.isQualified()) { - final JavaResolveResult resolveResult = reference.advancedResolve(false); - if (!inTheSamePackage(file, resolveResult.getElement())) { - final PsiElement resolveScope = resolveResult.getCurrentFileResolveScope(); - if (resolveScope instanceof PsiImportStatementBase) { - final PsiImportStatementBase importStatementBase = (PsiImportStatementBase)resolveScope; - redundant.remove(importStatementBase); + private static final Logger LOG = Logger.getInstance(JavaCodeStyleManagerImpl.class); + private static final String IMPL_SUFFIX = "Impl"; + private static final String GET_PREFIX = "get"; + private static final String IS_PREFIX = "is"; + private static final String FIND_PREFIX = "find"; + private static final String CREATE_PREFIX = "create"; + private static final String SET_PREFIX = "set"; + private static final String AS_PREFIX = "as"; + private static final String TO_PREFIX = "to"; + + private static final String[] ourPrepositions = { + "as", "at", "by", "down", "for", "from", "in", "into", "of", "on", "onto", "out", "over", + "per", "to", "up", "upon", "via", "with"}; + private static final String[] ourCommonTypeSuffixes = {"Entity"}; + + private final Project myProject; + + @Inject + public JavaCodeStyleManagerImpl(Project project) { + myProject = project; + } + + @Nonnull + @Override + @RequiredReadAction + public PsiElement shortenClassReferences(@Nonnull PsiElement element) throws IncorrectOperationException { + return shortenClassReferences(element, 0); + } + + @Nonnull + @Override + @RequiredReadAction + public PsiElement shortenClassReferences(@Nonnull PsiElement element, int flags) throws IncorrectOperationException { + CheckUtil.checkWritable(element); + if (!SourceTreeToPsiMap.hasTreeElement(element)) { + return element; + } + + boolean addImports = (flags & DO_NOT_ADD_IMPORTS) == 0; + boolean incompleteCode = (flags & INCOMPLETE_CODE) != 0; + + ReferenceAdjuster adjuster = ReferenceAdjuster.forLanguage(element.getLanguage()); + if (adjuster != null) { + ASTNode reference = adjuster.process(element.getNode(), addImports, incompleteCode, myProject); + return SourceTreeToPsiMap.treeToPsiNotNull(reference); + } + else { + return element; + } + } + + @Override + @RequiredReadAction + public void shortenClassReferences(@Nonnull PsiElement element, int startOffset, int endOffset) throws IncorrectOperationException { + CheckUtil.checkWritable(element); + if (SourceTreeToPsiMap.hasTreeElement(element)) { + ReferenceAdjuster adjuster = ReferenceAdjuster.forLanguage(element.getLanguage()); + if (adjuster != null) { + adjuster.processRange(element.getNode(), startOffset, endOffset, myProject); + } + } + } + + @Nonnull + @Override + @RequiredReadAction + public PsiElement qualifyClassReferences(@Nonnull PsiElement element) { + ReferenceAdjuster adjuster = ReferenceAdjuster.forLanguage(element.getLanguage()); + if (adjuster != null) { + ASTNode reference = adjuster.process(element.getNode(), false, false, true, true); + return SourceTreeToPsiMap.treeToPsiNotNull(reference); + } + return element; + } + + @Override + @RequiredReadAction + public void optimizeImports(@Nonnull PsiFile file) throws IncorrectOperationException { + CheckUtil.checkWritable(file); + if (file instanceof PsiJavaFile javaFile) { + PsiImportList newList = prepareOptimizeImportsResult(javaFile); + if (newList != null) { + PsiImportList importList = javaFile.getImportList(); + if (importList != null) { + importList.replace(newList); } - } } - super.visitReferenceElement(reference); - } - - private boolean inTheSamePackage(PsiJavaFile file, PsiElement element) { - if (element instanceof PsiClass && ((PsiClass)element).getContainingClass() == null) { - final PsiFile containingFile = element.getContainingFile(); - if (containingFile instanceof PsiJavaFile) { - return Comparing.strEqual(file.getPackageName(), ((PsiJavaFile)containingFile).getPackageName()); - } + } + } + + @Override + public boolean hasConflictingOnDemandImport(@Nonnull PsiJavaFile file, @Nonnull PsiClass psiClass, @Nonnull String referenceName) { + return ImportHelper.hasConflictingOnDemandImport(file, psiClass, referenceName); + } + + @Override + public PsiImportList prepareOptimizeImportsResult(@Nonnull PsiJavaFile file) { + return new ImportHelper(JavaCodeStyleSettings.getInstance(file)).prepareOptimizeImportsResult(file); + } + + @Override + public boolean addImport(@Nonnull PsiJavaFile file, @Nonnull PsiClass refClass) { + return new ImportHelper(JavaCodeStyleSettings.getInstance(file)).addImport(file, refClass); + } + + @Override + @RequiredReadAction + public void removeRedundantImports(@Nonnull PsiJavaFile file) throws IncorrectOperationException { + Collection redundant = findRedundantImports(file); + if (redundant == null) { + return; + } + + for (PsiImportStatementBase importStatement : redundant) { + PsiJavaCodeReferenceElement ref = importStatement.getImportReference(); + //Do not remove non-resolving refs + if (ref == null || ref.resolve() == null) { + continue; } - return false; - } - }); - } - } - return redundant; - } - - @Override - public int findEntryIndex(@Nonnull PsiImportStatementBase statement) { - return new ImportHelper(JavaCodeStyleSettings.getInstance(statement.getContainingFile())).findEntryIndex(statement); - } - - @Override - @Nullable - public String suggestCompiledParameterName(@Nonnull PsiType type) { - // avoid hang due to nice name evaluation that uses indices for resolve (IDEA-116803) - Collection result = doSuggestParameterNamesByTypeWithoutIndex(type); - return ContainerUtil.getFirstItem(getSuggestionsByNames(result, VariableKind.PARAMETER, true)); - } - - @Nonnull - private Collection doSuggestParameterNamesByTypeWithoutIndex(@Nonnull PsiType type) { - String fromTypeMap = suggestNameFromTypeMap(type, VariableKind.PARAMETER, type.getCanonicalText()); - if (fromTypeMap != null) { - return Collections.singletonList(fromTypeMap); - } - - return suggestNamesFromTypeName(type, VariableKind.PARAMETER, getTypeNameWithoutIndex(type.getDeepComponentType())); - } - - @Nullable - private static String getTypeNameWithoutIndex(@Nonnull PsiType type) { - type = type.getDeepComponentType(); - return type instanceof PsiClassType ? ((PsiClassType)type).getClassName() : - type instanceof PsiPrimitiveType ? type.getPresentableText() : - null; - } - - @Nonnull - private static List suggestNamesFromTypeName(@Nonnull PsiType type, - @Nonnull VariableKind variableKind, - @Nullable String typeName) { - if (typeName == null) return Collections.emptyList(); - - typeName = normalizeTypeName(typeName); - String result = type instanceof PsiArrayType ? StringUtil.pluralize(typeName) : typeName; - if (variableKind == VariableKind.PARAMETER && type instanceof PsiClassType && typeName.endsWith("Exception")) { - return Arrays.asList("e", result); - } - for (String suffix : ourCommonTypeSuffixes) { - if (result.length() > suffix.length() && result.endsWith(suffix)) { - return Arrays.asList(result, result.substring(0, result.length() - suffix.length())); - } - } - return Collections.singletonList(result); - } - - @Nonnull - private Collection getSuggestionsByNames(@Nonnull Iterable names, @Nonnull VariableKind kind, boolean correctKeywords) { - final Collection suggestions = new LinkedHashSet<>(); - for (String name : names) { - suggestions.addAll(getSuggestionsByName(name, kind, correctKeywords)); - } - return suggestions; - } - - @Nonnull - private Collection getSuggestionsByName(@Nonnull String name, @Nonnull VariableKind variableKind, boolean correctKeywords) { - if (!StringUtil.isJavaIdentifier(name)) return List.of(); - boolean upperCaseStyle = variableKind == VariableKind.STATIC_FINAL_FIELD; - boolean preferLongerNames = getJavaSettings().PREFER_LONGER_NAMES; - String prefix = getPrefixByVariableKind(variableKind); - String suffix = getSuffixByVariableKind(variableKind); - - List answer = new ArrayList<>(); - for (String suggestion : NameUtil.getSuggestionsByName(name, prefix, suffix, upperCaseStyle, preferLongerNames, false)) { - answer.add(correctKeywords ? changeIfNotIdentifier(suggestion) : suggestion); - } - - String wordByPreposition = getWordByPreposition(name, prefix, suffix, upperCaseStyle); - if (wordByPreposition != null && (!correctKeywords || isIdentifier(wordByPreposition))) { - answer.add(wordByPreposition); - } - if (name.equals("hashCode")) { - answer.add("hash"); - } - return answer; - } - - private static String getWordByPreposition(@Nonnull String name, String prefix, String suffix, boolean upperCaseStyle) { - String[] words = NameUtil.splitNameIntoWords(name); - for (int i = 1; i < words.length; i++) { - for (String preposition : ourPrepositions) { - if (preposition.equalsIgnoreCase(words[i])) { - String mainWord = words[i - 1]; - if (upperCaseStyle) { - mainWord = StringUtil.toUpperCase(mainWord); - } - else { - if (prefix.isEmpty() || StringUtil.endsWithChar(prefix, '_')) { - mainWord = StringUtil.toLowerCase(mainWord); + + importStatement.delete(); + } + } + + @Nullable + @Override + @RequiredReadAction + public Collection findRedundantImports(@Nonnull final PsiJavaFile file) { + PsiImportList importList = file.getImportList(); + if (importList == null) { + return null; + } + PsiImportStatementBase[] imports = importList.getAllImportStatements(); + if (imports.length == 0) { + return null; + } + + Set allImports = new HashSet<>(Arrays.asList(imports)); + final Collection redundant; + /* if(FileTypeUtils.isInServerPageFile(file)) + { + // remove only duplicate imports + redundant = ContainerUtil.newIdentityTroveSet(); + ContainerUtil.addAll(redundant, imports); + redundant.removeAll(allImports); + for(PsiImportStatementBase importStatement : imports) + { + if(importStatement instanceof JspxImportStatement && importStatement.isForeignFileImport()) + { + redundant.remove(importStatement); + } + } } - else { - mainWord = StringUtil.capitalize(mainWord); + else */ + { + redundant = allImports; + List roots = file.getViewProvider().getAllFiles(); + for (PsiElement root : roots) { + root.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitReferenceElement(@Nonnull PsiJavaCodeReferenceElement reference) { + if (!reference.isQualified()) { + JavaResolveResult resolveResult = reference.advancedResolve(false); + if (!inTheSamePackage(file, resolveResult.getElement()) + && resolveResult.getCurrentFileResolveScope() instanceof PsiImportStatementBase importStatementBase) { + redundant.remove(importStatementBase); + } + } + super.visitReferenceElement(reference); + } + + private boolean inTheSamePackage(PsiJavaFile file, PsiElement element) { + if (element instanceof PsiClass psiClass && psiClass.getContainingClass() == null) { + PsiFile containingFile = element.getContainingFile(); + if (containingFile instanceof PsiJavaFile javaFile) { + return Comparing.strEqual(file.getPackageName(), javaFile.getPackageName()); + } + } + return false; + } + }); } - } - return prefix + mainWord + suffix; - } - } - } - return null; - } - - @Nullable - private String suggestNameFromTypeMap(@Nonnull PsiType type, @Nonnull VariableKind variableKind, @Nullable String longTypeName) { - if (longTypeName != null) { - if (type.equals(PsiTypes.nullType())) { - longTypeName = CommonClassNames.JAVA_LANG_OBJECT; - } - String name = nameByType(longTypeName, variableKind); - if (name != null && isIdentifier(name)) { - return type instanceof PsiArrayType ? StringUtil.pluralize(name) : name; - } - } - return null; - } - - @Nullable - private static String nameByType(@Nonnull String longTypeName, @Nonnull VariableKind kind) { - if (kind == VariableKind.PARAMETER) { - return switch(longTypeName){ - case "int", "boolean", "byte", "char", "long" ->longTypeName.substring(0, 1); - case "double", "float" ->"v"; - case "short" ->"i"; - case CommonClassNames.JAVA_LANG_OBJECT ->"o"; - case CommonClassNames.JAVA_LANG_STRING ->"s"; - case CommonClassNames.JAVA_LANG_VOID ->"unused"; - default ->null; - }; - } - if (kind == VariableKind.LOCAL_VARIABLE) { - return switch(longTypeName){ - case "int", "boolean", "byte", "char", "long" ->longTypeName.substring(0, 1); - case "double", "float", CommonClassNames.JAVA_LANG_DOUBLE, CommonClassNames.JAVA_LANG_FLOAT ->"v"; - case "short", CommonClassNames.JAVA_LANG_SHORT, CommonClassNames.JAVA_LANG_INTEGER ->"i"; - case CommonClassNames.JAVA_LANG_LONG ->"l"; - case CommonClassNames.JAVA_LANG_BOOLEAN, CommonClassNames.JAVA_LANG_BYTE ->"b"; - case CommonClassNames.JAVA_LANG_CHARACTER ->"c"; - case CommonClassNames.JAVA_LANG_OBJECT ->"o"; - case CommonClassNames.JAVA_LANG_STRING ->"s"; - case CommonClassNames.JAVA_LANG_VOID ->"unused"; - default ->null; - }; - } - return null; - } - - - @Nonnull - @Override - public SuggestedNameInfo suggestVariableName(@Nonnull final VariableKind kind, - @Nullable final String propertyName, - @Nullable final PsiExpression expr, - @Nullable PsiType type, - final boolean correctKeywords) { - if (expr != null && type == null) { - type = expr.getType(); - } - - Set names = new LinkedHashSet<>(); - if (propertyName != null) { - String[] namesByName = ArrayUtil.toStringArray(getSuggestionsByName(propertyName, kind, correctKeywords)); - sortVariableNameSuggestions(namesByName, kind, propertyName, null); - ContainerUtil.addAll(names, namesByName); - } - - final NamesByExprInfo namesByExpr; - if (expr != null) { - namesByExpr = suggestVariableNameByExpression(expr, kind); - String[] suggestions = ArrayUtil.toStringArray(getSuggestionsByNames(namesByExpr.names, kind, correctKeywords)); - if (namesByExpr.propertyName != null) { - sortVariableNameSuggestions(suggestions, kind, namesByExpr.propertyName, null); - } - ContainerUtil.addAll(names, suggestions); - } - else { - namesByExpr = null; - } - - if (type != null) { - String[] namesByType = suggestVariableNameByType(type, kind, correctKeywords); - sortVariableNameSuggestions(namesByType, kind, null, type); - ContainerUtil.addAll(names, namesByType); - } - - final String _propertyName; - if (propertyName != null) { - _propertyName = propertyName; - } - else { - _propertyName = namesByExpr != null ? namesByExpr.propertyName : null; - } - - filterOutBadNames(names); - addNamesFromStatistics(names, kind, _propertyName, type); - - String[] namesArray = ArrayUtil.toStringArray(names); - sortVariableNameSuggestions(namesArray, kind, _propertyName, type); - - final String _type = type == null ? null : type.getCanonicalText(); - return new SuggestedNameInfo(namesArray) { - @Override - public void nameChosen(String name) { - if (_propertyName != null || _type != null) { - JavaStatisticsManager.incVariableNameUseCount(name, kind, _propertyName, _type); - } - } - }; - } - - private static void filterOutBadNames(Set names) { - names.remove("of"); - names.remove("to"); - } - - private static void addNamesFromStatistics(@Nonnull Set names, - @Nonnull VariableKind variableKind, - @Nullable String propertyName, - @Nullable PsiType type) { - String[] allNames = JavaStatisticsManager.getAllVariableNamesUsed(variableKind, propertyName, type); - - int maxFrequency = 0; - for (String name : allNames) { - int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); - maxFrequency = Math.max(maxFrequency, count); - } - - int frequencyLimit = Math.max(5, maxFrequency / 2); - - for (String name : allNames) { - if (names.contains(name)) { - continue; - } - int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); - if (LOG.isDebugEnabled()) { - LOG.debug("new name:" + name + " count:" + count); - LOG.debug("frequencyLimit:" + frequencyLimit); - } - if (count >= frequencyLimit) { - names.add(name); - } + } + return redundant; } - if (propertyName != null && type != null) { - addNamesFromStatistics(names, variableKind, propertyName, null); - addNamesFromStatistics(names, variableKind, null, type); + @Override + public int findEntryIndex(@Nonnull PsiImportStatementBase statement) { + return new ImportHelper(JavaCodeStyleSettings.getInstance(statement.getContainingFile())).findEntryIndex(statement); } - } - - @Nonnull - private String[] suggestVariableNameByType(@Nonnull PsiType type, @Nonnull VariableKind variableKind, boolean correctKeywords) { - Collection byTypeNames = doSuggestNamesByType(type, variableKind); - return ArrayUtil.toStringArray(getSuggestionsByNames(byTypeNames, variableKind, correctKeywords)); - } - private static void suggestNamesFromGenericParameters(@Nonnull PsiClassType type, @Nonnull Collection suggestions) { - PsiType[] parameters = type.getParameters(); - if (parameters.length == 0) return; + @Override + @Nullable + public String suggestCompiledParameterName(@Nonnull PsiType type) { + // avoid hang due to nice name evaluation that uses indices for resolve (IDEA-116803) + Collection result = doSuggestParameterNamesByTypeWithoutIndex(type); + return ContainerUtil.getFirstItem(getSuggestionsByNames(result, VariableKind.PARAMETER, true)); + } - StringBuilder fullNameBuilder = new StringBuilder(); - for (PsiType parameter : parameters) { - if (parameter instanceof PsiClassType) { - final String typeName = normalizeTypeName(getTypeName(parameter)); - if (typeName != null) { - fullNameBuilder.append(typeName); + @Nonnull + private Collection doSuggestParameterNamesByTypeWithoutIndex(@Nonnull PsiType type) { + String fromTypeMap = suggestNameFromTypeMap(type, VariableKind.PARAMETER, type.getCanonicalText()); + if (fromTypeMap != null) { + return Collections.singletonList(fromTypeMap); } - } + + return suggestNamesFromTypeName(type, VariableKind.PARAMETER, getTypeNameWithoutIndex(type.getDeepComponentType())); + } + + @Nullable + private static String getTypeNameWithoutIndex(@Nonnull PsiType type) { + type = type.getDeepComponentType(); + return type instanceof PsiClassType ? ((PsiClassType)type).getClassName() : + type instanceof PsiPrimitiveType ? type.getPresentableText() : + null; } - String baseName = normalizeTypeName(getTypeName(type)); - if (baseName != null) { - fullNameBuilder.append(baseName); - suggestions.add(fullNameBuilder.toString()); + + @Nonnull + private static List suggestNamesFromTypeName( + @Nonnull PsiType type, + @Nonnull VariableKind variableKind, + @Nullable String typeName + ) { + if (typeName == null) { + return Collections.emptyList(); + } + + typeName = normalizeTypeName(typeName); + String result = type instanceof PsiArrayType ? StringUtil.pluralize(typeName) : typeName; + if (variableKind == VariableKind.PARAMETER && type instanceof PsiClassType && typeName.endsWith("Exception")) { + return Arrays.asList("e", result); + } + for (String suffix : ourCommonTypeSuffixes) { + if (result.length() > suffix.length() && result.endsWith(suffix)) { + return Arrays.asList(result, result.substring(0, result.length() - suffix.length())); + } + } + return Collections.singletonList(result); } - } - private static void suggestNamesForCollectionInheritors(@Nonnull PsiClassType type, @Nonnull Collection suggestions) { - PsiType componentType = PsiUtil.extractIterableTypeParameter(type, false); - if (componentType == null || componentType.equals(type)) { - return; + + @Nonnull + private Collection getSuggestionsByNames(@Nonnull Iterable names, @Nonnull VariableKind kind, boolean correctKeywords) { + Collection suggestions = new LinkedHashSet<>(); + for (String name : names) { + suggestions.addAll(getSuggestionsByName(name, kind, correctKeywords)); + } + return suggestions; } - String typeName = normalizeTypeName(getTypeName(componentType)); - if (typeName != null) { - suggestions.add(StringUtil.pluralize(typeName)); + + @Nonnull + private Collection getSuggestionsByName(@Nonnull String name, @Nonnull VariableKind variableKind, boolean correctKeywords) { + if (!StringUtil.isJavaIdentifier(name)) { + return List.of(); + } + boolean upperCaseStyle = variableKind == VariableKind.STATIC_FINAL_FIELD; + boolean preferLongerNames = getJavaSettings().PREFER_LONGER_NAMES; + String prefix = getPrefixByVariableKind(variableKind); + String suffix = getSuffixByVariableKind(variableKind); + + List answer = new ArrayList<>(); + for (String suggestion : NameUtil.getSuggestionsByName(name, prefix, suffix, upperCaseStyle, preferLongerNames, false)) { + answer.add(correctKeywords ? changeIfNotIdentifier(suggestion) : suggestion); + } + + String wordByPreposition = getWordByPreposition(name, prefix, suffix, upperCaseStyle); + if (wordByPreposition != null && (!correctKeywords || isIdentifier(wordByPreposition))) { + answer.add(wordByPreposition); + } + if (name.equals("hashCode")) { + answer.add("hash"); + } + return answer; + } + + private static String getWordByPreposition(@Nonnull String name, String prefix, String suffix, boolean upperCaseStyle) { + String[] words = NameUtil.splitNameIntoWords(name); + for (int i = 1; i < words.length; i++) { + for (String preposition : ourPrepositions) { + if (preposition.equalsIgnoreCase(words[i])) { + String mainWord = words[i - 1]; + if (upperCaseStyle) { + mainWord = StringUtil.toUpperCase(mainWord); + } + else { + if (prefix.isEmpty() || StringUtil.endsWithChar(prefix, '_')) { + mainWord = StringUtil.toLowerCase(mainWord); + } + else { + mainWord = StringUtil.capitalize(mainWord); + } + } + return prefix + mainWord + suffix; + } + } + } + return null; } - } - private static String normalizeTypeName(@Nullable String typeName) { - if (typeName == null) { - return null; + @Nullable + private String suggestNameFromTypeMap(@Nonnull PsiType type, @Nonnull VariableKind variableKind, @Nullable String longTypeName) { + if (longTypeName != null) { + if (type.equals(PsiTypes.nullType())) { + longTypeName = JavaClassNames.JAVA_LANG_OBJECT; + } + String name = nameByType(longTypeName, variableKind); + if (name != null && isIdentifier(name)) { + return type instanceof PsiArrayType ? StringUtil.pluralize(name) : name; + } + } + return null; } - if (typeName.endsWith(IMPL_SUFFIX) && typeName.length() > IMPL_SUFFIX.length()) { - return typeName.substring(0, typeName.length() - IMPL_SUFFIX.length()); + + @Nullable + private static String nameByType(@Nonnull String longTypeName, @Nonnull VariableKind kind) { + if (kind == VariableKind.PARAMETER) { + return switch (longTypeName) { + case "int", "boolean", "byte", "char", "long" -> longTypeName.substring(0, 1); + case "double", "float" -> "v"; + case "short" -> "i"; + case JavaClassNames.JAVA_LANG_OBJECT -> "o"; + case JavaClassNames.JAVA_LANG_STRING -> "s"; + case JavaClassNames.JAVA_LANG_VOID -> "unused"; + default -> null; + }; + } + if (kind == VariableKind.LOCAL_VARIABLE) { + return switch (longTypeName) { + case "int", "boolean", "byte", "char", "long" -> longTypeName.substring(0, 1); + case "double", "float", JavaClassNames.JAVA_LANG_DOUBLE, JavaClassNames.JAVA_LANG_FLOAT -> "v"; + case "short", JavaClassNames.JAVA_LANG_SHORT, JavaClassNames.JAVA_LANG_INTEGER -> "i"; + case JavaClassNames.JAVA_LANG_LONG -> "l"; + case JavaClassNames.JAVA_LANG_BOOLEAN, JavaClassNames.JAVA_LANG_BYTE -> "b"; + case JavaClassNames.JAVA_LANG_CHARACTER -> "c"; + case JavaClassNames.JAVA_LANG_OBJECT -> "o"; + case JavaClassNames.JAVA_LANG_STRING -> "s"; + case JavaClassNames.JAVA_LANG_VOID -> "unused"; + default -> null; + }; + } + return null; } - return typeName; - } - @Nullable - public static String getTypeName(@Nonnull PsiType type) { - return getTypeName(type, true); - } - @Nullable - private static String getTypeName(@Nonnull PsiType type, boolean withIndices) { - type = type.getDeepComponentType(); - if (type instanceof PsiClassType) { - final PsiClassType classType = (PsiClassType)type; - final String className = classType.getClassName(); - if (className != null || !withIndices) { - return className; - } - final PsiClass aClass = classType.resolve(); - return aClass instanceof PsiAnonymousClass ? ((PsiAnonymousClass)aClass).getBaseClassType().getClassName() : null; + @Nonnull + @Override + @RequiredReadAction + public SuggestedNameInfo suggestVariableName( + @Nonnull final VariableKind kind, + @Nullable String propertyName, + @Nullable PsiExpression expr, + @Nullable PsiType type, + boolean correctKeywords + ) { + if (expr != null && type == null) { + type = expr.getType(); + } + + Set names = new LinkedHashSet<>(); + if (propertyName != null) { + String[] namesByName = ArrayUtil.toStringArray(getSuggestionsByName(propertyName, kind, correctKeywords)); + sortVariableNameSuggestions(namesByName, kind, propertyName, null); + ContainerUtil.addAll(names, namesByName); + } + + NamesByExprInfo namesByExpr; + if (expr != null) { + namesByExpr = suggestVariableNameByExpression(expr, kind); + String[] suggestions = ArrayUtil.toStringArray(getSuggestionsByNames(namesByExpr.names, kind, correctKeywords)); + if (namesByExpr.propertyName != null) { + sortVariableNameSuggestions(suggestions, kind, namesByExpr.propertyName, null); + } + ContainerUtil.addAll(names, suggestions); + } + else { + namesByExpr = null; + } + + if (type != null) { + String[] namesByType = suggestVariableNameByType(type, kind, correctKeywords); + sortVariableNameSuggestions(namesByType, kind, null, type); + ContainerUtil.addAll(names, namesByType); + } + + final String _propertyName; + if (propertyName != null) { + _propertyName = propertyName; + } + else { + _propertyName = namesByExpr != null ? namesByExpr.propertyName : null; + } + + filterOutBadNames(names); + addNamesFromStatistics(names, kind, _propertyName, type); + + String[] namesArray = ArrayUtil.toStringArray(names); + sortVariableNameSuggestions(namesArray, kind, _propertyName, type); + + final String _type = type == null ? null : type.getCanonicalText(); + return new SuggestedNameInfo(namesArray) { + @Override + public void nameChosen(String name) { + if (_propertyName != null || _type != null) { + JavaStatisticsManager.incVariableNameUseCount(name, kind, _propertyName, _type); + } + } + }; + } + + private static void filterOutBadNames(Set names) { + names.remove("of"); + names.remove("to"); } - else if (type instanceof PsiPrimitiveType) { - return type.getPresentableText(); + + private static void addNamesFromStatistics( + @Nonnull Set names, + @Nonnull VariableKind variableKind, + @Nullable String propertyName, + @Nullable PsiType type + ) { + String[] allNames = JavaStatisticsManager.getAllVariableNamesUsed(variableKind, propertyName, type); + + int maxFrequency = 0; + for (String name : allNames) { + int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); + maxFrequency = Math.max(maxFrequency, count); + } + + int frequencyLimit = Math.max(5, maxFrequency / 2); + + for (String name : allNames) { + if (names.contains(name)) { + continue; + } + int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); + if (LOG.isDebugEnabled()) { + LOG.debug("new name:" + name + " count:" + count); + LOG.debug("frequencyLimit:" + frequencyLimit); + } + if (count >= frequencyLimit) { + names.add(name); + } + } + + if (propertyName != null && type != null) { + addNamesFromStatistics(names, variableKind, propertyName, null); + addNamesFromStatistics(names, variableKind, null, type); + } } - else if (type instanceof PsiWildcardType) { - return getTypeName(((PsiWildcardType)type).getExtendsBound(), withIndices); + + @Nonnull + private String[] suggestVariableNameByType(@Nonnull PsiType type, @Nonnull VariableKind variableKind, boolean correctKeywords) { + Collection byTypeNames = doSuggestNamesByType(type, variableKind); + return ArrayUtil.toStringArray(getSuggestionsByNames(byTypeNames, variableKind, correctKeywords)); } - else if (type instanceof PsiIntersectionType) { - return getTypeName(((PsiIntersectionType)type).getRepresentative(), withIndices); + + private static void suggestNamesFromGenericParameters(@Nonnull PsiClassType type, @Nonnull Collection suggestions) { + PsiType[] parameters = type.getParameters(); + if (parameters.length == 0) { + return; + } + + StringBuilder fullNameBuilder = new StringBuilder(); + for (PsiType parameter : parameters) { + if (parameter instanceof PsiClassType) { + String typeName = normalizeTypeName(getTypeName(parameter)); + if (typeName != null) { + fullNameBuilder.append(typeName); + } + } + } + String baseName = normalizeTypeName(getTypeName(type)); + if (baseName != null) { + fullNameBuilder.append(baseName); + suggestions.add(fullNameBuilder.toString()); + } } - else if (type instanceof PsiCapturedWildcardType) { - return getTypeName(((PsiCapturedWildcardType)type).getWildcard(), withIndices); + + private static void suggestNamesForCollectionInheritors(@Nonnull PsiClassType type, @Nonnull Collection suggestions) { + PsiType componentType = PsiUtil.extractIterableTypeParameter(type, false); + if (componentType == null || componentType.equals(type)) { + return; + } + String typeName = normalizeTypeName(getTypeName(componentType)); + if (typeName != null) { + suggestions.add(StringUtil.pluralize(typeName)); + } } - else if (type instanceof PsiDisjunctionType) { - return getTypeName(((PsiDisjunctionType)type).getLeastUpperBound(), withIndices); + + private static String normalizeTypeName(@Nullable String typeName) { + if (typeName == null) { + return null; + } + if (typeName.endsWith(IMPL_SUFFIX) && typeName.length() > IMPL_SUFFIX.length()) { + return typeName.substring(0, typeName.length() - IMPL_SUFFIX.length()); + } + return typeName; } - else { - return null; + + @Nullable + public static String getTypeName(@Nonnull PsiType type) { + return getTypeName(type, true); } - } - @Nullable - private static String getLongTypeName(@Nonnull PsiType type) { - if (type instanceof PsiClassType) { - PsiClass aClass = ((PsiClassType)type).resolve(); - if (aClass == null) { + @Nullable + private static String getTypeName(@Nonnull PsiType type, boolean withIndices) { + type = type.getDeepComponentType(); + if (type instanceof PsiClassType classType) { + String className = classType.getClassName(); + if (className != null || !withIndices) { + return className; + } + PsiClass aClass = classType.resolve(); + return aClass instanceof PsiAnonymousClass anonymousClass ? anonymousClass.getBaseClassType().getClassName() : null; + } + else if (type instanceof PsiPrimitiveType) { + return type.getPresentableText(); + } + else if (type instanceof PsiWildcardType wildcardType) { + return getTypeName(wildcardType.getExtendsBound(), withIndices); + } + else if (type instanceof PsiIntersectionType intersectionType) { + return getTypeName(intersectionType.getRepresentative(), withIndices); + } + else if (type instanceof PsiCapturedWildcardType capturedWildcardType) { + return getTypeName(capturedWildcardType.getWildcard(), withIndices); + } + else if (type instanceof PsiDisjunctionType disjunctionType) { + return getTypeName(disjunctionType.getLeastUpperBound(), withIndices); + } + else { + return null; + } + } + + @Nullable + private static String getLongTypeName(@Nonnull PsiType type) { + if (type instanceof PsiClassType) { + PsiClass aClass = ((PsiClassType)type).resolve(); + if (aClass == null) { + return null; + } + else if (aClass instanceof PsiAnonymousClass) { + PsiClass baseClass = ((PsiAnonymousClass)aClass).getBaseClassType().resolve(); + return baseClass != null ? baseClass.getQualifiedName() : null; + } + else { + return aClass.getQualifiedName(); + } + } + else if (type instanceof PsiArrayType) { + return getLongTypeName(((PsiArrayType)type).getComponentType()) + "[]"; + } + else if (type instanceof PsiPrimitiveType) { + return type.getPresentableText(); + } + else if (type instanceof PsiWildcardType wildcardType) { + PsiType bound = wildcardType.getBound(); + return bound != null ? getLongTypeName(bound) : JavaClassNames.JAVA_LANG_OBJECT; + } + else if (type instanceof PsiCapturedWildcardType capturedWildcardType) { + PsiType bound = capturedWildcardType.getWildcard().getBound(); + return bound != null ? getLongTypeName(bound) : JavaClassNames.JAVA_LANG_OBJECT; + } + else if (type instanceof PsiIntersectionType intersectionType) { + return getLongTypeName(intersectionType.getRepresentative()); + } + else if (type instanceof PsiDisjunctionType disjunctionType) { + return getLongTypeName(disjunctionType.getLeastUpperBound()); + } + else { + return null; + } + } + + private static final class NamesByExprInfo { + static final NamesByExprInfo EMPTY = new NamesByExprInfo(null, Collections.emptyList()); + + private final String propertyName; + private final Collection names; + + private NamesByExprInfo(@Nullable String propertyName, @Nonnull Collection names) { + this.propertyName = propertyName; + this.names = names; + } + + private NamesByExprInfo(@Nonnull String propertyName) { + this(propertyName, Collections.singletonList(propertyName)); + } + + private NamesByExprInfo(@Nullable String propertyName, @Nonnull String... names) { + this( + propertyName, + propertyName == null ? Arrays.asList(names) : ContainerUtil.prepend(Arrays.asList(names), propertyName) + ); + } + } + + @Nonnull + @RequiredReadAction + private NamesByExprInfo suggestVariableNameByExpression(@Nonnull PsiExpression expr, @Nullable VariableKind variableKind) { + List fromLiteral = ExpressionUtils.nonStructuralChildren(expr) + .map(e -> e instanceof PsiLiteralExpression literal && literal.getValue() instanceof String str ? str : null) + .filter(Objects::nonNull) + .flatMap(literal -> LiteralNameSuggester.literalNames(literal).stream()) + .distinct() + .toList(); + LinkedHashSet names = new LinkedHashSet<>(fromLiteral); + ContainerUtil.addIfNotNull(names, suggestVariableNameFromConstant(expr, variableKind)); + ContainerUtil.addIfNotNull(names, suggestVariableNameFromLiterals(expr)); + + NamesByExprInfo byExpr = suggestVariableNameByExpressionOnly(expr, variableKind, false); + NamesByExprInfo byExprPlace = suggestVariableNameByExpressionPlace(expr, variableKind); + NamesByExprInfo byExprAllMethods = suggestVariableNameByExpressionOnly(expr, variableKind, true); + + names.addAll(byExpr.names); + names.addAll(byExprPlace.names); + + PsiType type = expr.getType(); + if (type != null && variableKind != null) { + names.addAll(doSuggestNamesByType(type, variableKind)); + } + names.addAll(byExprAllMethods.names); + + String propertyName = byExpr.propertyName != null ? byExpr.propertyName : byExprPlace.propertyName; + return new NamesByExprInfo(propertyName, names); + } + + @Nullable + @RequiredReadAction + private static String suggestVariableNameFromConstant(@Nonnull PsiExpression expr, @Nullable VariableKind kind) { + if (kind == null || kind == VariableKind.LOCAL_VARIABLE) { + return null; + } + PsiExpression expression = PsiUtil.skipParenthesizedExprDown(expr); + if (expression instanceof PsiReferenceExpression referenceExpression && + referenceExpression.resolve() instanceof PsiEnumConstant enumConstant) { + return normalizeTypeName(getTypeName(enumConstant.getType())); + } return null; - } - else if (aClass instanceof PsiAnonymousClass) { - PsiClass baseClass = ((PsiAnonymousClass)aClass).getBaseClassType().resolve(); - return baseClass != null ? baseClass.getQualifiedName() : null; - } - else { - return aClass.getQualifiedName(); - } } - else if (type instanceof PsiArrayType) { - return getLongTypeName(((PsiArrayType)type).getComponentType()) + "[]"; + + @Nonnull + private Collection doSuggestNamesByType(@Nonnull PsiType type, @Nonnull VariableKind variableKind) { + String fromTypeMap = suggestNameFromTypeMap(type, variableKind, getLongTypeName(type)); + if (fromTypeMap != null && type instanceof PsiPrimitiveType) { + return Collections.singletonList(fromTypeMap); + } + Collection suggestions = new LinkedHashSet<>(); + if (fromTypeMap != null) { + suggestions.add(fromTypeMap); + } + + List fromTypeName = suggestNamesFromTypeName(type, variableKind, getTypeName(type)); + if (!(type instanceof PsiClassType classType)) { + suggestions.addAll(fromTypeName); + return suggestions; + } + + suggestNamesForCollectionInheritors(classType, suggestions); + suggestFromOptionalContent(variableKind, classType, suggestions); + suggestNamesFromGenericParameters(classType, suggestions); + suggestions.addAll(fromTypeName); + suggestNamesFromHierarchy(classType, suggestions); + return suggestions; } - else if (type instanceof PsiPrimitiveType) { - return type.getPresentableText(); + + private static void suggestNamesFromHierarchy(@Nonnull PsiClassType type, @Nonnull Collection suggestions) { + PsiClass resolved = type.resolve(); + if (resolved == null || resolved.getContainingClass() == null) { + return; + } + + InheritanceUtil.processSupers( + resolved, + false, + superClass -> { + if (PsiTreeUtil.isAncestor(superClass, resolved, true)) { + suggestions.add(superClass.getName()); + } + return false; + } + ); } - else if (type instanceof PsiWildcardType) { - final PsiType bound = ((PsiWildcardType)type).getBound(); - return bound != null ? getLongTypeName(bound) : JavaClassNames.JAVA_LANG_OBJECT; + + private void suggestFromOptionalContent( + @Nonnull VariableKind variableKind, + @Nonnull PsiClassType classType, + @Nonnull Collection suggestions + ) { + PsiType optionalContent = extractOptionalContent(classType); + if (optionalContent == null) { + return; + } + + Collection contentSuggestions = doSuggestNamesByType(optionalContent, variableKind); + suggestions.addAll(contentSuggestions); + for (String s : contentSuggestions) { + suggestions.add("optional" + StringUtil.capitalize(s)); + } } - else if (type instanceof PsiCapturedWildcardType) { - final PsiType bound = ((PsiCapturedWildcardType)type).getWildcard().getBound(); - return bound != null ? getLongTypeName(bound) : JavaClassNames.JAVA_LANG_OBJECT; + + @Nullable + private static PsiType extractOptionalContent(@Nonnull PsiClassType classType) { + PsiClass resolved = classType.resolve(); + if (resolved != null && JavaClassNames.JAVA_UTIL_OPTIONAL.equals(resolved.getQualifiedName())) { + if (classType.getParameterCount() == 1) { + return classType.getParameters()[0]; + } + } + return null; } - else if (type instanceof PsiIntersectionType) { - return getLongTypeName(((PsiIntersectionType)type).getRepresentative()); + + @Nullable + @RequiredReadAction + private static String suggestVariableNameFromLiterals(@Nonnull PsiExpression expr) { + String text = findLiteralText(expr); + if (text == null) { + return null; + } + return expr.getType() instanceof PsiArrayType ? StringUtil.pluralize(text) : text; } - else if (type instanceof PsiDisjunctionType) { - return getLongTypeName(((PsiDisjunctionType)type).getLeastUpperBound()); + + private static boolean isNameSupplier(String text) { + if (!StringUtil.isQuotedString(text)) { + return false; + } + String stringPresentation = StringUtil.unquoteString(text); + String[] words = stringPresentation.split(" "); + //noinspection SimplifiableIfStatement + if (words.length > 5) { + return false; + } + return ContainerUtil.and(words, StringUtil::isJavaIdentifier); + } + + @Nullable + @RequiredReadAction + private static String findLiteralText(@Nonnull PsiExpression expr) { + List literals = SyntaxTraverser.psiTraverser(expr) + .filter(PsiLiteralExpression.class) + .filter(lit -> isNameSupplier(lit.getText())) + .filter(lit -> { + PsiElement exprList = lit.getParent(); + if (!(exprList instanceof PsiExpressionList)) { + return false; + } + PsiElement call = exprList.getParent(); + //TODO: exclude or not getA().getB("name").getC(); or getA(getB("name").getC()); It works fine for now in the most cases + return call instanceof PsiNewExpression || call instanceof PsiMethodCallExpression; + }) + .toList(); + + if (literals.size() == 1) { + return StringUtil.unquoteString(literals.get(0).getText()).replaceAll(" ", "_"); + } + return null; } - else { - return null; + + @Nonnull + @RequiredReadAction + private NamesByExprInfo suggestVariableNameByExpressionOnly( + @Nonnull PsiExpression expr, + @Nullable VariableKind variableKind, + boolean useAllMethodNames + ) { + if (expr instanceof PsiMethodCallExpression) { + PsiReferenceExpression methodExpr = ((PsiMethodCallExpression)expr).getMethodExpression(); + String methodName = methodExpr.getReferenceName(); + if (methodName != null) { + if ("of".equals(methodName) || "ofNullable".equals(methodName)) { + if (isJavaUtilMethodCall((PsiMethodCallExpression)expr)) { + PsiExpression[] expressions = ((PsiMethodCallExpression)expr).getArgumentList().getExpressions(); + if (expressions.length > 0) { + return suggestVariableNameByExpressionOnly(expressions[0], variableKind, useAllMethodNames); + } + } + } + if ("map".equals(methodName) || "flatMap".equals(methodName) || "filter".equals(methodName)) { + if (isJavaUtilMethodCall((PsiMethodCallExpression)expr)) { + return NamesByExprInfo.EMPTY; + } + } + + String[] words = NameUtilCore.nameToWords(methodName); + if (words.length > 0) { + String firstWord = words[0]; + if (GET_PREFIX.equals(firstWord) + || IS_PREFIX.equals(firstWord) + || FIND_PREFIX.equals(firstWord) + || CREATE_PREFIX.equals(firstWord) + || AS_PREFIX.equals(firstWord) + || TO_PREFIX.equals(firstWord)) { + if (words.length > 1) { + String propertyName = methodName.substring(firstWord.length()); + if (methodExpr.getQualifierExpression() instanceof PsiReferenceExpression qRefExpr + && qRefExpr.resolve() instanceof PsiVariable) { + String name = qRefExpr.getReferenceName() + StringUtil.capitalize(propertyName); + return new NamesByExprInfo(propertyName, name); + } + return new NamesByExprInfo(propertyName); + } + } + else if (words.length == 1 || useAllMethodNames) { + if (!"equals".equals(firstWord) && !"valueOf".equals(methodName)) { + words[0] = PastParticiple.pastParticiple(firstWord); + return new NamesByExprInfo(methodName, words[0], String.join("", words)); + } + else { + return new NamesByExprInfo(methodName); + } + } + } + } + } + else if (expr instanceof PsiReferenceExpression refExpr) { + String propertyName = getPropertyName(refExpr, true); + if (propertyName != null) { + return new NamesByExprInfo(propertyName); + } + } + else if (expr instanceof PsiArrayAccessExpression) { + NamesByExprInfo info = + suggestVariableNameByExpressionOnly(((PsiArrayAccessExpression)expr).getArrayExpression(), variableKind, useAllMethodNames); + + String singular = info.propertyName == null ? null : StringUtil.unpluralize(info.propertyName); + if (singular != null) { + return new NamesByExprInfo(singular, ContainerUtil.mapNotNull(info.names, StringUtil::unpluralize)); + } + } + else if (expr instanceof PsiLiteralExpression literalExpression && variableKind == VariableKind.STATIC_FINAL_FIELD) { + Object value = literalExpression.getValue(); + if (value instanceof String stringValue) { + String[] names = getSuggestionsByValue(stringValue); + if (names.length > 0) { + return new NamesByExprInfo(null, constantValueToConstantName(names)); + } + } + } + else if (expr instanceof PsiParenthesizedExpression parenthesized) { + PsiExpression expression = parenthesized.getExpression(); + if (expression != null) { + return suggestVariableNameByExpressionOnly(expression, variableKind, useAllMethodNames); + } + } + else if (expr instanceof PsiTypeCastExpression typeCast) { + PsiExpression operand = typeCast.getOperand(); + if (operand != null) { + return suggestVariableNameByExpressionOnly(operand, variableKind, useAllMethodNames); + } + } + else if (expr instanceof PsiLiteralExpression) { + String text = StringUtil.unquoteString(expr.getText()); + if (isIdentifier(text)) { + return new NamesByExprInfo(text); + } + } + else if (expr instanceof PsiFunctionalExpression functionalExpr && variableKind != null) { + PsiType functionalInterfaceType = functionalExpr.getFunctionalInterfaceType(); + if (functionalInterfaceType != null) { + return new NamesByExprInfo(null, doSuggestNamesByType(functionalInterfaceType, variableKind)); + } + } + + return NamesByExprInfo.EMPTY; + } + + @Nullable + @RequiredReadAction + private String getPropertyName(@Nonnull PsiReferenceExpression expression) { + return getPropertyName(expression, false); } - } - private static final class NamesByExprInfo { - static final NamesByExprInfo EMPTY = new NamesByExprInfo(null, Collections.emptyList()); + @Nullable + @RequiredReadAction + private String getPropertyName(@Nonnull PsiReferenceExpression expression, boolean skipUnresolved) { + String propertyName = expression.getReferenceName(); + if (propertyName == null) { + return null; + } + + PsiElement refElement = expression.resolve(); + if (refElement instanceof PsiVariable variable) { + VariableKind refVariableKind = getVariableKind(variable); + return variableNameToPropertyName(propertyName, refVariableKind); + } + else if (refElement == null && skipUnresolved) { + return null; + } + else { + return propertyName; + } + } - private final String propertyName; - private final Collection names; - - private NamesByExprInfo(@Nullable String propertyName, @Nonnull Collection names) { - this.propertyName = propertyName; - this.names = names; - } - - private NamesByExprInfo(@Nonnull String propertyName) { - this(propertyName, Collections.singletonList(propertyName)); - } + private static boolean isJavaUtilMethodCall(@Nonnull PsiMethodCallExpression expr) { + PsiMethod method = expr.resolveMethod(); + //noinspection SimplifiableIfStatement + if (method == null) { + return false; + } + + return isJavaUtilMethod(method) + || !MethodDeepestSuperSearcher.processDeepestSuperMethods(method, method1 -> !isJavaUtilMethod(method1)); + } - private NamesByExprInfo(@Nullable String propertyName, @Nonnull String ... names) { - this( - propertyName, - propertyName == null ? Arrays.asList(names) : ContainerUtil.prepend(Arrays.asList(names), propertyName) - ); - } - } - - @Nonnull - private NamesByExprInfo suggestVariableNameByExpression(@Nonnull PsiExpression expr, @Nullable VariableKind variableKind) { - List fromLiteral = ExpressionUtils.nonStructuralChildren(expr) - .map(e -> e instanceof PsiLiteralExpression literal && literal.getValue() instanceof String str ? str : null) - .filter(Objects::nonNull) - .flatMap(literal -> LiteralNameSuggester.literalNames(literal).stream()) - .distinct() - .toList(); - final LinkedHashSet names = new LinkedHashSet<>(fromLiteral); - ContainerUtil.addIfNotNull(names, suggestVariableNameFromConstant(expr, variableKind)); - ContainerUtil.addIfNotNull(names, suggestVariableNameFromLiterals(expr)); - - NamesByExprInfo byExpr = suggestVariableNameByExpressionOnly(expr, variableKind, false); - NamesByExprInfo byExprPlace = suggestVariableNameByExpressionPlace(expr, variableKind); - NamesByExprInfo byExprAllMethods = suggestVariableNameByExpressionOnly(expr, variableKind, true); - - names.addAll(byExpr.names); - names.addAll(byExprPlace.names); - - PsiType type = expr.getType(); - if (type != null && variableKind != null) { - names.addAll(doSuggestNamesByType(type, variableKind)); - } - names.addAll(byExprAllMethods.names); - - String propertyName = byExpr.propertyName != null ? byExpr.propertyName : byExprPlace.propertyName; - return new NamesByExprInfo(propertyName, names); - } - - @Nullable - private static String suggestVariableNameFromConstant(@Nonnull PsiExpression expr, @Nullable VariableKind kind) { - if (kind == null || kind == VariableKind.LOCAL_VARIABLE) { - return null; - } - PsiExpression expression = PsiUtil.skipParenthesizedExprDown(expr); - if (expression instanceof PsiReferenceExpression referenceExpression && - referenceExpression.resolve() instanceof PsiEnumConstant enumConstant) { - return normalizeTypeName(getTypeName(enumConstant.getType())); - } - return null; - } - - @Nonnull - private Collection doSuggestNamesByType(@Nonnull PsiType type, @Nonnull final VariableKind variableKind) { - String fromTypeMap = suggestNameFromTypeMap(type, variableKind, getLongTypeName(type)); - if (fromTypeMap != null && type instanceof PsiPrimitiveType) { - return Collections.singletonList(fromTypeMap); - } - final Collection suggestions = new LinkedHashSet<>(); - if (fromTypeMap != null) { - suggestions.add(fromTypeMap); - } - - List fromTypeName = suggestNamesFromTypeName(type, variableKind, getTypeName(type)); - if (!(type instanceof PsiClassType classType)) { - suggestions.addAll(fromTypeName); - return suggestions; - } - - suggestNamesForCollectionInheritors(classType, suggestions); - suggestFromOptionalContent(variableKind, classType, suggestions); - suggestNamesFromGenericParameters(classType, suggestions); - suggestions.addAll(fromTypeName); - suggestNamesFromHierarchy(classType, suggestions); - return suggestions; - } - - private static void suggestNamesFromHierarchy(@Nonnull PsiClassType type, @Nonnull Collection suggestions) { - final PsiClass resolved = type.resolve(); - if (resolved == null || resolved.getContainingClass() == null) return; - - InheritanceUtil.processSupers(resolved, false, superClass -> { - if (PsiTreeUtil.isAncestor(superClass, resolved, true)) { - suggestions.add(superClass.getName()); - } - return false; - }); - } - - private void suggestFromOptionalContent(@Nonnull VariableKind variableKind, - @Nonnull PsiClassType classType, - @Nonnull Collection suggestions) { - final PsiType optionalContent = extractOptionalContent(classType); - if (optionalContent == null) return; - - final Collection contentSuggestions = doSuggestNamesByType(optionalContent, variableKind); - suggestions.addAll(contentSuggestions); - for (String s : contentSuggestions) { - suggestions.add("optional" + StringUtil.capitalize(s)); - } - } - - @Nullable - private static PsiType extractOptionalContent(@Nonnull PsiClassType classType) { - final PsiClass resolved = classType.resolve(); - if (resolved != null && CommonClassNames.JAVA_UTIL_OPTIONAL.equals(resolved.getQualifiedName())) { - if (classType.getParameterCount() == 1) { - return classType.getParameters()[0]; - } - } - return null; - } - - @Nullable - private static String suggestVariableNameFromLiterals(@Nonnull PsiExpression expr) { - String text = findLiteralText(expr); - if (text == null) return null; - return expr.getType() instanceof PsiArrayType ? StringUtil.pluralize(text) : text; - } - - private static boolean isNameSupplier(String text) { - if (!StringUtil.isQuotedString(text)) return false; - String stringPresentation = StringUtil.unquoteString(text); - String[] words = stringPresentation.split(" "); - if (words.length > 5) return false; - return ContainerUtil.and(words, StringUtil::isJavaIdentifier); - } - - @Nullable - private static String findLiteralText(@Nonnull PsiExpression expr) { - final List literals = SyntaxTraverser.psiTraverser(expr) - .filter(PsiLiteralExpression.class) - .filter(lit -> isNameSupplier(lit.getText())) - .filter(lit -> { - final PsiElement exprList = lit.getParent(); - if (!(exprList instanceof PsiExpressionList)) return false; - final PsiElement call = exprList.getParent(); - //TODO: exclude or not getA().getB("name").getC(); or getA(getB("name").getC()); It works fine for now in the most cases - return call instanceof PsiNewExpression || call instanceof PsiMethodCallExpression; - }) - .toList(); - - if (literals.size() == 1) { - return StringUtil.unquoteString(literals.get(0).getText()).replaceAll(" ", "_"); - } - return null; - } - - @Nonnull - private NamesByExprInfo suggestVariableNameByExpressionOnly(@Nonnull PsiExpression expr, - @Nullable VariableKind variableKind, - boolean useAllMethodNames) { - if (expr instanceof PsiMethodCallExpression) { - PsiReferenceExpression methodExpr = ((PsiMethodCallExpression)expr).getMethodExpression(); - String methodName = methodExpr.getReferenceName(); - if (methodName != null) { - if ("of".equals(methodName) || "ofNullable".equals(methodName)) { - if (isJavaUtilMethodCall((PsiMethodCallExpression)expr)) { - PsiExpression[] expressions = ((PsiMethodCallExpression)expr).getArgumentList().getExpressions(); - if (expressions.length > 0) { - return suggestVariableNameByExpressionOnly(expressions[0], variableKind, useAllMethodNames); + private static boolean isJavaUtilMethod(@Nonnull PsiMethod method) { + String name = PsiUtil.getMemberQualifiedName(method); + return name != null && name.startsWith("java.util."); + } + + @Nonnull + private static String constantValueToConstantName(@Nonnull String[] names) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < names.length; i++) { + if (i > 0) { + result.append("_"); + } + result.append(names[i]); + } + return result.toString(); + } + + @Nonnull + private static String[] getSuggestionsByValue(@Nonnull String stringValue) { + List result = new ArrayList<>(); + StringBuffer currentWord = new StringBuffer(); + + boolean prevIsUpperCase = false; + + for (int i = 0; i < stringValue.length(); i++) { + char c = stringValue.charAt(i); + if (Character.isUpperCase(c)) { + if (currentWord.length() > 0 && !prevIsUpperCase) { + result.add(currentWord.toString()); + currentWord = new StringBuffer(); + } + currentWord.append(c); } - } - } - if ("map".equals(methodName) || "flatMap".equals(methodName) || "filter".equals(methodName)) { - if (isJavaUtilMethodCall((PsiMethodCallExpression)expr)) { - return NamesByExprInfo.EMPTY; - } - } - - String[] words = NameUtilCore.nameToWords(methodName); - if (words.length > 0) { - final String firstWord = words[0]; - if (GET_PREFIX.equals(firstWord) - || IS_PREFIX.equals(firstWord) - || FIND_PREFIX.equals(firstWord) - || CREATE_PREFIX.equals(firstWord) - || AS_PREFIX.equals(firstWord) - || TO_PREFIX.equals(firstWord)) { - if (words.length > 1) { - final String propertyName = methodName.substring(firstWord.length()); - final PsiExpression qualifierExpression = methodExpr.getQualifierExpression(); - if (qualifierExpression instanceof PsiReferenceExpression && - ((PsiReferenceExpression)qualifierExpression).resolve() instanceof PsiVariable) { - String name = ((PsiReferenceExpression)qualifierExpression).getReferenceName() + StringUtil.capitalize(propertyName); - return new NamesByExprInfo(propertyName, name); - } - return new NamesByExprInfo(propertyName); + else if (Character.isLowerCase(c)) { + currentWord.append(Character.toUpperCase(c)); } - } - else if (words.length == 1 || useAllMethodNames) { - if (!"equals".equals(firstWord) && !"valueOf".equals(methodName)) { - words[0] = PastParticiple.pastParticiple(firstWord); - return new NamesByExprInfo(methodName, words[0], String.join("", words)); + else if (Character.isJavaIdentifierPart(c) && c != '_') { + if (Character.isJavaIdentifierStart(c) || currentWord.length() > 0 || !result.isEmpty()) { + currentWord.append(c); + } } else { - return new NamesByExprInfo(methodName); + if (currentWord.length() > 0) { + result.add(currentWord.toString()); + currentWord = new StringBuffer(); + } } - } - } - } - } - else if (expr instanceof PsiReferenceExpression) { - String propertyName = getPropertyName((PsiReferenceExpression)expr, true); - if (propertyName != null) { - return new NamesByExprInfo(propertyName); - } - } - else if (expr instanceof PsiArrayAccessExpression) { - NamesByExprInfo info = - suggestVariableNameByExpressionOnly(((PsiArrayAccessExpression)expr).getArrayExpression(), variableKind, useAllMethodNames); - - String singular = info.propertyName == null ? null : StringUtil.unpluralize(info.propertyName); - if (singular != null) { - return new NamesByExprInfo(singular, ContainerUtil.mapNotNull(info.names, StringUtil::unpluralize)); - } - } - else if (expr instanceof PsiLiteralExpression literalExpression && variableKind == VariableKind.STATIC_FINAL_FIELD) { - final Object value = literalExpression.getValue(); - if (value instanceof String stringValue) { - String[] names = getSuggestionsByValue(stringValue); - if (names.length > 0) { - return new NamesByExprInfo(null, constantValueToConstantName(names)); - } - } - } - else if (expr instanceof PsiParenthesizedExpression) { - final PsiExpression expression = ((PsiParenthesizedExpression)expr).getExpression(); - if (expression != null) { - return suggestVariableNameByExpressionOnly(expression, variableKind, useAllMethodNames); - } - } - else if (expr instanceof PsiTypeCastExpression) { - final PsiExpression operand = ((PsiTypeCastExpression)expr).getOperand(); - if (operand != null) { - return suggestVariableNameByExpressionOnly(operand, variableKind, useAllMethodNames); - } - } - else if (expr instanceof PsiLiteralExpression) { - final String text = StringUtil.unquoteString(expr.getText()); - if (isIdentifier(text)) { - return new NamesByExprInfo(text); - } - } - else if (expr instanceof PsiFunctionalExpression && variableKind != null) { - final PsiType functionalInterfaceType = ((PsiFunctionalExpression)expr).getFunctionalInterfaceType(); - if (functionalInterfaceType != null) { - return new NamesByExprInfo(null, doSuggestNamesByType(functionalInterfaceType, variableKind)); - } - } - - return NamesByExprInfo.EMPTY; - } - - @Nullable - private String getPropertyName(@Nonnull PsiReferenceExpression expression) { - return getPropertyName(expression, false); - } - - @Nullable - private String getPropertyName(@Nonnull PsiReferenceExpression expression, boolean skipUnresolved) { - String propertyName = expression.getReferenceName(); - if (propertyName == null) return null; - - PsiElement refElement = expression.resolve(); - if (refElement instanceof PsiVariable) { - VariableKind refVariableKind = getVariableKind((PsiVariable)refElement); - return variableNameToPropertyName(propertyName, refVariableKind); - } - else if (refElement == null && skipUnresolved) { - return null; - } - else { - return propertyName; - } - } - - private static boolean isJavaUtilMethodCall(@Nonnull PsiMethodCallExpression expr) { - PsiMethod method = expr.resolveMethod(); - if (method == null) { - return false; - } - - return isJavaUtilMethod(method) || !MethodDeepestSuperSearcher.processDeepestSuperMethods(method, new Processor() { - @Override - public boolean process(PsiMethod method) { - return !isJavaUtilMethod(method); - } - }); - } - - private static boolean isJavaUtilMethod(@Nonnull PsiMethod method) { - String name = PsiUtil.getMemberQualifiedName(method); - return name != null && name.startsWith("java.util."); - } - - @Nonnull - private static String constantValueToConstantName(@Nonnull String[] names) { - final StringBuilder result = new StringBuilder(); - for (int i = 0; i < names.length; i++) { - if (i > 0) { - result.append("_"); - } - result.append(names[i]); - } - return result.toString(); - } - - @Nonnull - private static String[] getSuggestionsByValue(@Nonnull String stringValue) { - List result = new ArrayList(); - StringBuffer currentWord = new StringBuffer(); - - boolean prevIsUpperCase = false; - - for (int i = 0; i < stringValue.length(); i++) { - final char c = stringValue.charAt(i); - if (Character.isUpperCase(c)) { - if (currentWord.length() > 0 && !prevIsUpperCase) { - result.add(currentWord.toString()); - currentWord = new StringBuffer(); - } - currentWord.append(c); - } - else if (Character.isLowerCase(c)) { - currentWord.append(Character.toUpperCase(c)); - } - else if (Character.isJavaIdentifierPart(c) && c != '_') { - if (Character.isJavaIdentifierStart(c) || currentWord.length() > 0 || !result.isEmpty()) { - currentWord.append(c); - } - } - else { + + prevIsUpperCase = Character.isUpperCase(c); + } + if (currentWord.length() > 0) { - result.add(currentWord.toString()); - currentWord = new StringBuffer(); - } - } - - prevIsUpperCase = Character.isUpperCase(c); - } - - if (currentWord.length() > 0) { - result.add(currentWord.toString()); - } - return ArrayUtil.toStringArray(result); - } - - @Nonnull - private NamesByExprInfo suggestVariableNameByExpressionPlace(@Nonnull PsiExpression expr, @Nullable VariableKind variableKind) { - if (expr.getParent() instanceof PsiExpressionList list) { - PsiElement listParent = list.getParent(); - PsiSubstitutor subst = PsiSubstitutor.EMPTY; - PsiMethod method = null; - if (listParent instanceof PsiMethodCallExpression call) { - final JavaResolveResult resolveResult = call.getMethodExpression().advancedResolve(false); - method = (PsiMethod)resolveResult.getElement(); - subst = resolveResult.getSubstitutor(); - } - else { - if (listParent instanceof PsiAnonymousClass) { - listParent = listParent.getParent(); - } - if (listParent instanceof PsiNewExpression newExpression) { - method = newExpression.resolveConstructor(); - } - } - - if (method != null) { - PsiParameter parameter = MethodCallUtils.getParameterForArgument(expr); - if (parameter != null) { - String name = parameter.getName(); - if (TypeConversionUtil.areTypesAssignmentCompatible(subst.substitute(parameter.getType()), expr)) { - name = variableNameToPropertyName(name, VariableKind.PARAMETER); - if (list.getExpressionCount() == 1) { - final String methodName = method.getName(); - String[] words = NameUtilCore.nameToWords(methodName); - if (words.length > 0) { - final String firstWord = words[0]; - if (SET_PREFIX.equals(firstWord)) { - final String propertyName = methodName.substring(firstWord.length()); - return new NamesByExprInfo(name, propertyName); + result.add(currentWord.toString()); + } + return ArrayUtil.toStringArray(result); + } + + @Nonnull + @RequiredReadAction + private NamesByExprInfo suggestVariableNameByExpressionPlace(@Nonnull PsiExpression expr, @Nullable VariableKind variableKind) { + if (expr.getParent() instanceof PsiExpressionList list) { + PsiElement listParent = list.getParent(); + PsiSubstitutor subst = PsiSubstitutor.EMPTY; + PsiMethod method = null; + if (listParent instanceof PsiMethodCallExpression call) { + JavaResolveResult resolveResult = call.getMethodExpression().advancedResolve(false); + method = (PsiMethod)resolveResult.getElement(); + subst = resolveResult.getSubstitutor(); + } + else { + if (listParent instanceof PsiAnonymousClass) { + listParent = listParent.getParent(); + } + if (listParent instanceof PsiNewExpression newExpression) { + method = newExpression.resolveConstructor(); + } + } + + if (method != null) { + PsiParameter parameter = MethodCallUtils.getParameterForArgument(expr); + if (parameter != null) { + String name = parameter.getName(); + if (TypeConversionUtil.areTypesAssignmentCompatible(subst.substitute(parameter.getType()), expr)) { + name = variableNameToPropertyName(name, VariableKind.PARAMETER); + if (list.getExpressionCount() == 1) { + String methodName = method.getName(); + String[] words = NameUtilCore.nameToWords(methodName); + if (words.length > 0) { + String firstWord = words[0]; + if (SET_PREFIX.equals(firstWord)) { + String propertyName = methodName.substring(firstWord.length()); + return new NamesByExprInfo(name, propertyName); + } + } + } + return new NamesByExprInfo(name); + } + } + } + } + else if (expr.getParent() instanceof PsiAssignmentExpression assignmentExpression) { + if (expr == assignmentExpression.getRExpression()) { + PsiExpression leftExpression = assignmentExpression.getLExpression(); + if (leftExpression instanceof PsiReferenceExpression ref) { + String name = getPropertyName(ref); + if (name != null) { + return new NamesByExprInfo(name); + } + } + } + } + //skip places where name for this local variable is calculated, otherwise grab the name + else if (expr.getParent() instanceof PsiLocalVariable variable && variableKind != VariableKind.LOCAL_VARIABLE) { + String variableName = variable.getName(); + String propertyName = variableNameToPropertyName(variableName, getVariableKind(variable)); + return new NamesByExprInfo(propertyName); + } + + return NamesByExprInfo.EMPTY; + } + + @Nonnull + @Override + public String variableNameToPropertyName(@Nonnull String name, @Nonnull VariableKind variableKind) { + if (variableKind == VariableKind.STATIC_FINAL_FIELD || variableKind == VariableKind.STATIC_FIELD && name.contains("_")) { + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (c != '_') { + if (Character.isLowerCase(c)) { + return variableNameToPropertyNameInner(name, variableKind); + } + + buffer.append(Character.toLowerCase(c)); + continue; + } + //noinspection AssignmentToForLoopParameter + i++; + if (i < name.length()) { + c = name.charAt(i); + buffer.append(c); + } + } + return buffer.toString(); + } + + return variableNameToPropertyNameInner(name, variableKind); + } + + @Nonnull + private String variableNameToPropertyNameInner(@Nonnull String name, @Nonnull VariableKind variableKind) { + String prefix = getPrefixByVariableKind(variableKind); + String suffix = getSuffixByVariableKind(variableKind); + boolean doDecapitalize = false; + + int pLength = prefix.length(); + if (pLength > 0 && name.startsWith(prefix) && name.length() > pLength && + // check it's not just a long camel word that happens to begin with the specified prefix + (!Character.isLetter(prefix.charAt(pLength - 1)) || Character.isUpperCase(name.charAt(pLength)))) { + name = name.substring(pLength); + doDecapitalize = true; + } + + if (name.endsWith(suffix) && name.length() > suffix.length()) { + name = name.substring(0, name.length() - suffix.length()); + doDecapitalize = true; + } + + if (doDecapitalize) { + name = Introspector.decapitalize(name); + } + + return name; + } + + @Nonnull + @Override + public String propertyNameToVariableName(@Nonnull String propertyName, @Nonnull VariableKind variableKind) { + if (variableKind == VariableKind.STATIC_FINAL_FIELD) { + String[] words = NameUtil.nameToWords(propertyName); + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < words.length; i++) { + String word = words[i]; + if (i > 0) { + buffer.append("_"); } - } + buffer.append(StringUtil.toUpperCase(word)); + } + return buffer.toString(); + } + + String prefix = getPrefixByVariableKind(variableKind); + String name = propertyName; + if (!name.isEmpty() && !prefix.isEmpty() && !StringUtil.endsWithChar(prefix, '_')) { + name = Character.toUpperCase(name.charAt(0)) + name.substring(1); + } + name = prefix + name + getSuffixByVariableKind(variableKind); + name = changeIfNotIdentifier(name); + return name; + } + + @Nonnull + private String[] getSuggestionsByName( + @Nonnull String name, + @Nonnull VariableKind variableKind, + boolean isArray, + boolean correctKeywords + ) { + boolean upperCaseStyle = variableKind == VariableKind.STATIC_FINAL_FIELD; + boolean preferLongerNames = getSettings().PREFER_LONGER_NAMES; + String prefix = getPrefixByVariableKind(variableKind); + String suffix = getSuffixByVariableKind(variableKind); + + List answer = new ArrayList<>(); + for (String suggestion : NameUtil.getSuggestionsByName(name, prefix, suffix, upperCaseStyle, preferLongerNames, isArray)) { + answer.add(correctKeywords ? changeIfNotIdentifier(suggestion) : suggestion); + } + + return ArrayUtil.toStringArray(answer); + } + + @Nonnull + @Override + @RequiredReadAction + public String suggestUniqueVariableName(@Nonnull String baseName, PsiElement place, boolean lookForward) { + return suggestUniqueVariableName(baseName, place, lookForward, false); + } + + @Nonnull + @RequiredReadAction + private static String suggestUniqueVariableName( + @Nonnull String baseName, + PsiElement place, + boolean lookForward, + boolean allowShadowing + ) { + PsiElement scope = PsiTreeUtil.getNonStrictParentOfType(place, PsiStatement.class, PsiCodeBlock.class, PsiMethod.class); + for (int index = 0; ; index++) { + String name = index > 0 ? baseName + index : baseName; + if (hasConflictingVariable(place, name, allowShadowing) || lookForward && hasConflictingVariableAfterwards(scope, name)) { + continue; } - return new NamesByExprInfo(name); - } + return name; } - } - } - else if (expr.getParent() instanceof PsiAssignmentExpression assignmentExpression) { - if (expr == assignmentExpression.getRExpression()) { - final PsiExpression leftExpression = assignmentExpression.getLExpression(); - if (leftExpression instanceof PsiReferenceExpression ref) { - String name = getPropertyName(ref); - if (name != null) { - return new NamesByExprInfo(name); - } - } - } - } - //skip places where name for this local variable is calculated, otherwise grab the name - else if (expr.getParent() instanceof PsiLocalVariable variable && variableKind != VariableKind.LOCAL_VARIABLE) { - String variableName = variable.getName(); - String propertyName = variableNameToPropertyName(variableName, getVariableKind(variable)); - return new NamesByExprInfo(propertyName); - } - - return NamesByExprInfo.EMPTY; - } - - @Nonnull - @Override - public String variableNameToPropertyName(@Nonnull String name, @Nonnull VariableKind variableKind) { - if (variableKind == VariableKind.STATIC_FINAL_FIELD || variableKind == VariableKind.STATIC_FIELD && name.contains("_")) { - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); - if (c != '_') { - if (Character.isLowerCase(c)) { - return variableNameToPropertyNameInner(name, variableKind); - } - - buffer.append(Character.toLowerCase(c)); - continue; - } - //noinspection AssignmentToForLoopParameter - i++; - if (i < name.length()) { - c = name.charAt(i); - buffer.append(c); - } - } - return buffer.toString(); - } - - return variableNameToPropertyNameInner(name, variableKind); - } - - @Nonnull - private String variableNameToPropertyNameInner(@Nonnull String name, @Nonnull VariableKind variableKind) { - String prefix = getPrefixByVariableKind(variableKind); - String suffix = getSuffixByVariableKind(variableKind); - boolean doDecapitalize = false; - - int pLength = prefix.length(); - if (pLength > 0 && name.startsWith(prefix) && name.length() > pLength && - // check it's not just a long camel word that happens to begin with the specified prefix - (!Character.isLetter(prefix.charAt(pLength - 1)) || Character.isUpperCase(name.charAt(pLength)))) { - name = name.substring(pLength); - doDecapitalize = true; - } - - if (name.endsWith(suffix) && name.length() > suffix.length()) { - name = name.substring(0, name.length() - suffix.length()); - doDecapitalize = true; - } - - if (doDecapitalize) { - name = Introspector.decapitalize(name); - } - - return name; - } - - @Nonnull - @Override - public String propertyNameToVariableName(@Nonnull String propertyName, @Nonnull VariableKind variableKind) { - if (variableKind == VariableKind.STATIC_FINAL_FIELD) { - String[] words = NameUtil.nameToWords(propertyName); - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < words.length; i++) { - String word = words[i]; - if (i > 0) { - buffer.append("_"); - } - buffer.append(StringUtil.toUpperCase(word)); - } - return buffer.toString(); - } - - String prefix = getPrefixByVariableKind(variableKind); - String name = propertyName; - if (!name.isEmpty() && !prefix.isEmpty() && !StringUtil.endsWithChar(prefix, '_')) { - name = Character.toUpperCase(name.charAt(0)) + name.substring(1); - } - name = prefix + name + getSuffixByVariableKind(variableKind); - name = changeIfNotIdentifier(name); - return name; - } - - @Nonnull - private String[] getSuggestionsByName(@Nonnull String name, - @Nonnull VariableKind variableKind, - boolean isArray, - boolean correctKeywords) { - boolean upperCaseStyle = variableKind == VariableKind.STATIC_FINAL_FIELD; - boolean preferLongerNames = getSettings().PREFER_LONGER_NAMES; - String prefix = getPrefixByVariableKind(variableKind); - String suffix = getSuffixByVariableKind(variableKind); - - List answer = new ArrayList(); - for (String suggestion : NameUtil.getSuggestionsByName(name, prefix, suffix, upperCaseStyle, preferLongerNames, isArray)) { - answer.add(correctKeywords ? changeIfNotIdentifier(suggestion) : suggestion); - } - - return ArrayUtil.toStringArray(answer); - } - - @Nonnull - @Override - public String suggestUniqueVariableName(@Nonnull String baseName, PsiElement place, boolean lookForward) { - return suggestUniqueVariableName(baseName, place, lookForward, false); - } - - @Nonnull - private static String suggestUniqueVariableName(@Nonnull String baseName, PsiElement place, boolean lookForward, boolean allowShadowing) { - PsiElement scope = PsiTreeUtil.getNonStrictParentOfType(place, PsiStatement.class, PsiCodeBlock.class, PsiMethod.class); - for (int index = 0; ; index++) { - String name = index > 0 ? baseName + index : baseName; - if (hasConflictingVariable(place, name, allowShadowing) || lookForward && hasConflictingVariableAfterwards(scope, name)) { - continue; - } - return name; - } - } - - private static boolean hasConflictingVariableAfterwards(@Nullable PsiElement scope, @Nonnull final String name) { - PsiElement run = scope; - while (run != null) { - class CancelException extends RuntimeException { - } - try { - run.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitClass(final PsiClass aClass) { - } - - @Override - public void visitVariable(PsiVariable variable) { - if (name.equals(variable.getName())) { - throw new CancelException(); + } + + @RequiredReadAction + private static boolean hasConflictingVariableAfterwards(@Nullable PsiElement scope, @Nonnull final String name) { + PsiElement run = scope; + while (run != null) { + class CancelException extends RuntimeException { + } + try { + run.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitClass(@Nonnull PsiClass aClass) { + } + + @Override + @RequiredReadAction + public void visitVariable(@Nonnull PsiVariable variable) { + if (name.equals(variable.getName())) { + throw new CancelException(); + } + } + }); + } + catch (CancelException e) { + return true; + } + run = run.getNextSibling(); + if (scope instanceof PsiMethod || scope instanceof PsiForeachStatement) {//do not check next member for param name conflict + break; } - } - }); - } - catch (CancelException e) { - return true; - } - run = run.getNextSibling(); - if (scope instanceof PsiMethod || scope instanceof PsiForeachStatement) {//do not check next member for param name conflict - break; - } - } - return false; - } - - private static boolean hasConflictingVariable(@Nullable PsiElement place, @Nonnull String name, boolean allowShadowing) { - if (place == null) { - return false; - } - PsiResolveHelper helper = JavaPsiFacade.getInstance(place.getProject()).getResolveHelper(); - PsiVariable existingVariable = helper.resolveAccessibleReferencedVariable(name, place); - if (existingVariable == null) { - return false; - } - - if (allowShadowing && existingVariable instanceof PsiField && PsiTreeUtil.getNonStrictParentOfType(place, PsiMethod.class) != null) { - return false; - } - - return true; - } - - @Override - @Nonnull - public SuggestedNameInfo suggestUniqueVariableName(@Nonnull final SuggestedNameInfo baseNameInfo, - PsiElement place, - boolean ignorePlaceName, - boolean lookForward) { - final String[] names = baseNameInfo.names; - final LinkedHashSet uniqueNames = new LinkedHashSet(names.length); - for (String name : names) { - if (ignorePlaceName && place instanceof PsiNamedElement) { - final String placeName = ((PsiNamedElement)place).getName(); - if (Comparing.strEqual(placeName, name)) { - uniqueNames.add(name); - continue; - } - } - String unique = suggestUniqueVariableName(name, place, lookForward); - if (!unique.equals(name)) { - String withShadowing = suggestUniqueVariableName(name, place, lookForward, true); - if (withShadowing.equals(name)) { - uniqueNames.add(name); - } - } - uniqueNames.add(unique); - } - - return new SuggestedNameInfo(ArrayUtil.toStringArray(uniqueNames)) { - @Override - public void nameChosen(String name) { - baseNameInfo.nameChosen(name); - } - }; - } - - private static void sortVariableNameSuggestions(@Nonnull String[] names, - @Nonnull final VariableKind variableKind, - @Nullable final String propertyName, - @Nullable final PsiType type) { - if (names.length <= 1) { - return; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("sorting names:" + variableKind); - if (propertyName != null) { - LOG.debug("propertyName:" + propertyName); - } - if (type != null) { - LOG.debug("type:" + type); - } - for (String name : names) { - int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); - LOG.debug(name + " : " + count); - } - } - - Comparator comparator = new Comparator() { - @Override - public int compare(@Nonnull String s1, @Nonnull String s2) { - int count1 = JavaStatisticsManager.getVariableNameUseCount(s1, variableKind, propertyName, type); - int count2 = JavaStatisticsManager.getVariableNameUseCount(s2, variableKind, propertyName, type); - return count2 - count1; - } - }; - Arrays.sort(names, comparator); - } - - @Override - @Nonnull - public String getPrefixByVariableKind(@Nonnull VariableKind variableKind) { - String prefix = null; - switch (variableKind) { - case FIELD: - prefix = getJavaSettings().FIELD_NAME_PREFIX; - break; - case STATIC_FIELD: - prefix = getJavaSettings().STATIC_FIELD_NAME_PREFIX; - break; - case PARAMETER: - prefix = getJavaSettings().PARAMETER_NAME_PREFIX; - break; - case LOCAL_VARIABLE: - prefix = getJavaSettings().LOCAL_VARIABLE_NAME_PREFIX; - break; - case STATIC_FINAL_FIELD: - break; - default: - LOG.assertTrue(false); - break; - } - return prefix == null ? "" : prefix; - } - - @Override - @Nonnull - public String getSuffixByVariableKind(@Nonnull VariableKind variableKind) { - String suffix = null; - switch (variableKind) { - case FIELD: - suffix = getJavaSettings().FIELD_NAME_SUFFIX; - break; - case STATIC_FIELD: - suffix = getJavaSettings().STATIC_FIELD_NAME_SUFFIX; - break; - case PARAMETER: - suffix = getJavaSettings().PARAMETER_NAME_SUFFIX; - break; - case LOCAL_VARIABLE: - suffix = getJavaSettings().LOCAL_VARIABLE_NAME_SUFFIX; - break; - case STATIC_FINAL_FIELD: - break; - default: - LOG.assertTrue(false); - break; - } - return suffix == null ? "" : suffix; - } - - @NonNls - @Nonnull - private String changeIfNotIdentifier(@Nonnull String name) { - if (!isIdentifier(name)) { - return StringUtil.fixVariableNameDerivedFromPropertyName(name); - } - return name; - } - - private boolean isIdentifier(@Nonnull String name) { - return PsiNameHelper.getInstance(myProject).isIdentifier(name, LanguageLevel.HIGHEST); - } - - @Nonnull - private JavaCodeStyleSettings getJavaSettings() { - return CodeStyle.getSettings(myProject).getCustomSettings(JavaCodeStyleSettings.class); - } - - @Nonnull - private CodeStyleSettings getSettings() { - return CodeStyleSettingsManager.getSettings(myProject); - } - - private static boolean isStringPsiLiteral(@Nonnull PsiElement element) { - if (element instanceof PsiLiteralExpression) { - final String text = element.getText(); - return StringUtil.isQuotedString(text); - } - return false; - } + } + return false; + } + + private static boolean hasConflictingVariable(@Nullable PsiElement place, @Nonnull String name, boolean allowShadowing) { + if (place == null) { + return false; + } + PsiResolveHelper helper = JavaPsiFacade.getInstance(place.getProject()).getResolveHelper(); + PsiVariable existingVariable = helper.resolveAccessibleReferencedVariable(name, place); + //noinspection SimplifiableIfStatement + if (existingVariable == null) { + return false; + } + + return !allowShadowing + || !(existingVariable instanceof PsiField) + || PsiTreeUtil.getNonStrictParentOfType(place, PsiMethod.class) == null; + } + + @Nonnull + @Override + @RequiredReadAction + public SuggestedNameInfo suggestUniqueVariableName( + @Nonnull final SuggestedNameInfo baseNameInfo, + PsiElement place, + boolean ignorePlaceName, + boolean lookForward + ) { + String[] names = baseNameInfo.names; + final LinkedHashSet uniqueNames = new LinkedHashSet<>(names.length); + for (String name : names) { + if (ignorePlaceName && place instanceof PsiNamedElement namedElem) { + String placeName = namedElem.getName(); + if (Comparing.strEqual(placeName, name)) { + uniqueNames.add(name); + continue; + } + } + String unique = suggestUniqueVariableName(name, place, lookForward); + if (!unique.equals(name)) { + String withShadowing = suggestUniqueVariableName(name, place, lookForward, true); + if (withShadowing.equals(name)) { + uniqueNames.add(name); + } + } + uniqueNames.add(unique); + } + + return new SuggestedNameInfo(ArrayUtil.toStringArray(uniqueNames)) { + @Override + public void nameChosen(String name) { + baseNameInfo.nameChosen(name); + } + }; + } + + private static void sortVariableNameSuggestions( + @Nonnull String[] names, + @Nonnull VariableKind variableKind, + @Nullable String propertyName, + @Nullable PsiType type + ) { + if (names.length <= 1) { + return; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("sorting names:" + variableKind); + if (propertyName != null) { + LOG.debug("propertyName:" + propertyName); + } + if (type != null) { + LOG.debug("type:" + type); + } + for (String name : names) { + int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); + LOG.debug(name + " : " + count); + } + } + + Comparator comparator = (s1, s2) -> { + int count1 = JavaStatisticsManager.getVariableNameUseCount(s1, variableKind, propertyName, type); + int count2 = JavaStatisticsManager.getVariableNameUseCount(s2, variableKind, propertyName, type); + return count2 - count1; + }; + Arrays.sort(names, comparator); + } + + @Override + @Nonnull + public String getPrefixByVariableKind(@Nonnull VariableKind variableKind) { + String prefix = null; + switch (variableKind) { + case FIELD: + prefix = getJavaSettings().FIELD_NAME_PREFIX; + break; + case STATIC_FIELD: + prefix = getJavaSettings().STATIC_FIELD_NAME_PREFIX; + break; + case PARAMETER: + prefix = getJavaSettings().PARAMETER_NAME_PREFIX; + break; + case LOCAL_VARIABLE: + prefix = getJavaSettings().LOCAL_VARIABLE_NAME_PREFIX; + break; + case STATIC_FINAL_FIELD: + break; + default: + LOG.assertTrue(false); + break; + } + return prefix == null ? "" : prefix; + } + + @Override + @Nonnull + public String getSuffixByVariableKind(@Nonnull VariableKind variableKind) { + String suffix = null; + switch (variableKind) { + case FIELD: + suffix = getJavaSettings().FIELD_NAME_SUFFIX; + break; + case STATIC_FIELD: + suffix = getJavaSettings().STATIC_FIELD_NAME_SUFFIX; + break; + case PARAMETER: + suffix = getJavaSettings().PARAMETER_NAME_SUFFIX; + break; + case LOCAL_VARIABLE: + suffix = getJavaSettings().LOCAL_VARIABLE_NAME_SUFFIX; + break; + case STATIC_FINAL_FIELD: + break; + default: + LOG.assertTrue(false); + break; + } + return suffix == null ? "" : suffix; + } + + @Nonnull + private String changeIfNotIdentifier(@Nonnull String name) { + if (!isIdentifier(name)) { + return StringUtil.fixVariableNameDerivedFromPropertyName(name); + } + return name; + } + + private boolean isIdentifier(@Nonnull String name) { + return PsiNameHelper.getInstance(myProject).isIdentifier(name, LanguageLevel.HIGHEST); + } + + @Nonnull + private JavaCodeStyleSettings getJavaSettings() { + return CodeStyle.getSettings(myProject).getCustomSettings(JavaCodeStyleSettings.class); + } + + @Nonnull + private CodeStyleSettings getSettings() { + return CodeStyleSettingsManager.getSettings(myProject); + } + + @RequiredReadAction + private static boolean isStringPsiLiteral(@Nonnull PsiElement element) { + if (element instanceof PsiLiteralExpression) { + String text = element.getText(); + return StringUtil.isQuotedString(text); + } + return false; + } }