diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaFutureKeywordUseFixProvider.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaFutureKeywordUseFixProvider.java index 65cb1dff8d60..a57610498387 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaFutureKeywordUseFixProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaFutureKeywordUseFixProvider.java @@ -1,11 +1,17 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.codeInsight.daemon.impl.analysis; import com.intellij.codeInsight.daemon.QuickFixActionRegistrar; +import com.intellij.codeInsight.intention.QuickFixFactory; +import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper; import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; import com.intellij.psi.*; +import com.intellij.psi.PsiClassType.ClassResolveResult; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.psi.util.PsiUtil; import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class JavaFutureKeywordUseFixProvider extends UnresolvedReferenceQuickFixProvider { @Override @@ -14,6 +20,7 @@ public class JavaFutureKeywordUseFixProvider extends UnresolvedReferenceQuickFix if (typeElement == null || typeElement.getFirstChild() != typeElement.getLastChild()) return; PsiElement parent = typeElement.getParent(); if (PsiKeyword.VAR.equals(ref.getReferenceName())) { + registerSetVariableTypeFix(parent, registrar); registerVarLanguageLevelFix(ref, parent, registrar); } if (PsiKeyword.RECORD.equals(ref.getReferenceName())) { @@ -54,4 +61,42 @@ public class JavaFutureKeywordUseFixProvider extends UnresolvedReferenceQuickFix public Class getReferenceClass() { return PsiJavaCodeReferenceElement.class; } + + private static void registerSetVariableTypeFix(PsiElement parent, @NotNull QuickFixActionRegistrar registrar) { + PsiVariable variable = ObjectUtils.tryCast(parent, PsiVariable.class); + if (variable == null) return; + PsiType type = inferType(variable); + if (type == null) return; + registrar.register(PriorityIntentionActionWrapper.highPriority(QuickFixFactory.getInstance().createSetVariableTypeFix(variable, type))); + } + + private static @Nullable PsiType inferType(@NotNull PsiVariable variable) { + if (variable instanceof PsiParameter && variable.getParent() instanceof PsiForeachStatement) { + PsiForeachStatement foreach = (PsiForeachStatement)variable.getParent(); + PsiExpression iteratedValue = foreach.getIteratedValue(); + return iteratedValue != null ? JavaGenericsUtil.getCollectionItemType(iteratedValue) : null; + } + else if (variable instanceof PsiParameter) { + PsiParameterList parameterList = ObjectUtils.tryCast(variable.getParent(), PsiParameterList.class); + if (parameterList == null) return null; + PsiLambdaExpression lambda = ObjectUtils.tryCast(parameterList.getParent(), PsiLambdaExpression.class); + if (lambda == null) return null; + PsiType functionalInterfaceType = lambda.getFunctionalInterfaceType(); + if (functionalInterfaceType == null) return null; + ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); + if (interfaceMethod == null) return null; + PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); + int index = parameterList.getParameterIndex((PsiParameter)variable); + if (index >= parameters.length) return null; + return LambdaUtil.getSubstitutor(interfaceMethod, resolveResult).substitute(parameters[index].getType()); + } + else if (variable instanceof PsiLocalVariable) { + PsiExpression initializer = variable.getInitializer(); + if (initializer == null) return null; + PsiType type = initializer.getType(); + return PsiTypesUtil.isDenotableType(type, variable) && !PsiType.VOID.equals(type) ? type : null; + } + return null; + } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterForEach.java new file mode 100644 index 000000000000..2e9cd037bf42 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterForEach.java @@ -0,0 +1,9 @@ +// "Set variable type to 'String'" "true" +import java.util.List; + +class Demo { + void test(List list) { + for (String s : list) { + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterLocalVariable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterLocalVariable.java new file mode 100644 index 000000000000..19f3d88cd3ca --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterLocalVariable.java @@ -0,0 +1,9 @@ +// "Set variable type to 'HashMap>'" "true" +import java.util.HashMap; +import java.util.List; + +class Demo { + void test() { + HashMap> m = new HashMap>(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterTryWithResources.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterTryWithResources.java new file mode 100644 index 000000000000..44889b6fa32f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/afterTryWithResources.java @@ -0,0 +1,11 @@ +// "Set variable type to 'FileInputStream'" "true" +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +class Demo { + void test() { + try (FileInputStream input = new FileInputStream("validation.txt")) { + } catch (FileNotFoundException e) { + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeForEach.java new file mode 100644 index 000000000000..777f07f8f1a1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeForEach.java @@ -0,0 +1,9 @@ +// "Set variable type to 'String'" "true" +import java.util.List; + +class Demo { + void test(List list) { + for (var s : list) { + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLambdaExpression.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLambdaExpression.java new file mode 100644 index 000000000000..a19e8915e1f6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLambdaExpression.java @@ -0,0 +1,6 @@ +// "Set variable type to ''" "false" +class Demo { + void test() { + var s = () -> {}; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLambdaParameter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLambdaParameter.java new file mode 100644 index 000000000000..ab2fd74e0b89 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLambdaParameter.java @@ -0,0 +1,8 @@ +// "Set variable type to ''" "false" +class Demo { + void test() { + Object x = a -> { + var y = a; + }; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLocalVariable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLocalVariable.java new file mode 100644 index 000000000000..328117e8e4a0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeLocalVariable.java @@ -0,0 +1,9 @@ +// "Set variable type to 'HashMap>'" "true" +import java.util.HashMap; +import java.util.List; + +class Demo { + void test() { + var m = new HashMap>(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeMethodReference.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeMethodReference.java new file mode 100644 index 000000000000..8962ae795ba1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeMethodReference.java @@ -0,0 +1,6 @@ +// "Set variable type to ''" "false" +class Demo { + void test() { + var s = this::foo; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeNull.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeNull.java new file mode 100644 index 000000000000..c0d8099db7b9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeNull.java @@ -0,0 +1,6 @@ +// "Set variable type to 'null'" "false" +class Demo { + void test() { + var s = null; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeTryWithResources.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeTryWithResources.java new file mode 100644 index 000000000000..e596ec1c3f15 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeTryWithResources.java @@ -0,0 +1,11 @@ +// "Set variable type to 'FileInputStream'" "true" +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +class Demo { + void test() { + try (var input = new FileInputStream("validation.txt")) { + } catch (FileNotFoundException e) { + } + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeVoid.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeVoid.java new file mode 100644 index 000000000000..95899130d6b9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType/beforeVoid.java @@ -0,0 +1,8 @@ +// "Set variable type to 'void'" "false" +public class Demo { + void test() { + var s = foo(); + } + + void foo() {} +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SetExplicitTypeTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SetExplicitTypeTest.java new file mode 100644 index 000000000000..ce6b9a7eeed3 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SetExplicitTypeTest.java @@ -0,0 +1,21 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeInsight.daemon.quickFix; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +public class SetExplicitTypeTest extends LightQuickFixParameterizedTestCase { + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/setExplicitType"; + } + + @Override + protected @NotNull LightProjectDescriptor getProjectDescriptor() { + return LightJavaCodeInsightFixtureTestCase.JAVA_9; + } +} +