JaroslavTulach closed pull request #979: Adding an error fix to create methods
when a method ref refers to an …
URL: https://github.com/apache/incubator-netbeans/pull/979
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git
a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateElement.java
b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateElement.java
index 63924089e9..f8c26721be 100644
---
a/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateElement.java
+++
b/java/java.hints/src/org/netbeans/modules/java/hints/errors/CreateElement.java
@@ -22,6 +22,7 @@
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
@@ -46,6 +47,7 @@
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
@@ -53,6 +55,9 @@
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
@@ -102,7 +107,7 @@ public CreateElement() {
}
public Set<String> getCodes() {
- return new
HashSet<String>(Arrays.asList("compiler.err.cant.resolve.location",
"compiler.err.cant.resolve.location.args", "compiler.err.cant.apply.symbol",
"compiler.err.cant.apply.symbol.1", "compiler.err.cant.apply.symbols",
"compiler.err.cant.resolve", "compiler.err.cant.resolve.args", CAST_KEY,
"compiler.err.try.with.resources.expr.needs.var")); // NOI18N
+ return new
HashSet<String>(Arrays.asList("compiler.err.cant.resolve.location",
"compiler.err.cant.resolve.location.args", "compiler.err.cant.apply.symbol",
"compiler.err.cant.apply.symbol.1", "compiler.err.cant.apply.symbols",
"compiler.err.cant.resolve", "compiler.err.cant.resolve.args", CAST_KEY,
"compiler.err.try.with.resources.expr.needs.var",
"compiler.err.invalid.mref")); // NOI18N
}
public static final String CAST_KEY = "compiler.err.prob.found.req";
@@ -141,6 +146,10 @@ public CreateElement() {
}
private static List<Fix> analyzeImpl(CompilationInfo info, String
diagnosticKey, int offset) throws IOException {
+ if ("compiler.err.invalid.mref".equals(diagnosticKey)) {
+ return computeMissingMemberRefFixes(info, offset);
+ }
+
TreePath errorPath = ErrorHintsProvider.findUnresolvedElement(info,
offset);
if (errorPath == null) {
@@ -494,7 +503,62 @@ public CreateElement() {
return result;
}
-
+
+ private static List<Fix> computeMissingMemberRefFixes(CompilationInfo
info, int offset) {
+ TreePath errorPath = info.getTreeUtilities().pathFor(offset + 1);
+ while (errorPath != null && errorPath.getLeaf().getKind() !=
Kind.MEMBER_REFERENCE) {
+ errorPath = errorPath.getParentPath();
+ }
+ if (errorPath == null) {
+ return Collections.<Fix>emptyList();
+ }
+ MemberReferenceTree mref = (MemberReferenceTree) errorPath.getLeaf();
+ TypeMirror mrefType = info.getTrees().getTypeMirror(errorPath);
+ if (mrefType.getKind() == TypeKind.ERROR) {
+ TypeMirror expectedTargetType =
info.getTrees().getOriginalType((ErrorType) mrefType);
+ if (expectedTargetType == null || expectedTargetType.getKind() !=
TypeKind.DECLARED) {
+ return Collections.<Fix>emptyList();
+ }
+ ExecutableElement expectedMethod =
Utilities.getFunctionalMethodFromElement(info,
info.getTypes().asElement(expectedTargetType));
+ if (expectedMethod == null) {
+ return Collections.<Fix>emptyList();
+ }
+ ExecutableType methodType = (ExecutableType)
info.getTypes().asMemberOf((DeclaredType) expectedTargetType, expectedMethod);
+ if (Utilities.containsErrorsRecursively(methodType)) {
+ return Collections.<Fix>emptyList();
+ }
+ TypeMirror targetType = /*XXX: check the
target*/info.getTrees().getTypeMirror(new TreePath(errorPath,
mref.getQualifierExpression()));
+ TypeElement target = (TypeElement)
info.getTypes().asElement(targetType);
+ //TODO: use thrown types?
+ //IZ 111048 -- don't offer anything if target file isn't writable
+ if(!Utilities.isTargetWritable(target, info))
+ return Collections.<Fix>emptyList();
+
+ FileObject targetFile =
SourceUtils.getFile(ElementHandle.create(target), info.getClasspathInfo());
+ if (targetFile == null)
+ return Collections.<Fix>emptyList();
+ Set<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
+ TreePath tp = errorPath;
+ while (tp != null &&
!TreeUtilities.CLASS_TREE_KINDS.contains(tp.getLeaf().getKind())) {
+ tp = tp.getParentPath();
+ }
+ modifiers.addAll(Utilities.getAccessModifiers(info, (TypeElement)
info.getTrees().getElement(tp), (TypeElement) target).getRequiredModifiers());
+ Element targetExprEl = info.getTrees().getElement(new
TreePath(errorPath, mref.getQualifierExpression()));
+ List<Fix> fixes = new ArrayList<>();
+ if (targetExprEl.getKind().isClass() ||
targetExprEl.getKind().isInterface()) {
+ if (methodType.getParameterTypes().size() > 0 &&
target.equals(info.getTypes().asElement(methodType.getParameterTypes().get(0))))
{
+ //static ref to instance type:
+ fixes.add(new CreateMethodFix(info,
mref.getName().toString(), EnumSet.copyOf(modifiers), target,
methodType.getReturnType(), methodType.getParameterTypes().subList(1,
methodType.getParameterTypes().size()),
expectedMethod.getParameters().stream().skip(1).map(var ->
var.getSimpleName().toString()).collect(Collectors.toList()),
Collections.emptyList(), Collections.emptyList(), targetFile));
+ }
+ modifiers.add(Modifier.STATIC);
+ }
+ fixes.add(new CreateMethodFix(info, mref.getName().toString(),
modifiers, target, methodType.getReturnType(), methodType.getParameterTypes(),
expectedMethod.getParameters().stream().map(var ->
var.getSimpleName().toString()).collect(Collectors.toList()),
Collections.emptyList(), Collections.emptyList(), targetFile));
+ return fixes;
+ }
+
+ return Collections.<Fix>emptyList();
+ }
+
private static List<Fix> prepareCreateMethodFix(CompilationInfo info,
TreePath invocation, Set<Modifier> modifiers, TypeElement target, String
simpleName, List<? extends ExpressionTree> arguments, List<? extends
TypeMirror> returnTypes) {
//return type:
//XXX: should reasonably consider all the found type candidates, not
only the one:
diff --git
a/java/java.hints/src/org/netbeans/modules/java/hints/errors/Utilities.java
b/java/java.hints/src/org/netbeans/modules/java/hints/errors/Utilities.java
index 414fa5f8d5..fbb6a15b00 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/Utilities.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/Utilities.java
@@ -3007,4 +3007,30 @@ public static boolean isAnonymousType(TypeMirror type) {
}
return false;
}
+
+ public static ExecutableElement
getFunctionalMethodFromElement(CompilationInfo info, Element element) {
+ Element classType = info.getTypes().asElement(element.asType());
+
+ //return null if classType is invalid, such as a primitive type
+ if (classType == null || !classType.getKind().isInterface()) {
+ return null;
+ }
+
+ ExecutableElement elementToReturn = null;
+ int methodCounter = 0;
+ for (Element e : classType.getEnclosedElements()) {
+ if (e.getKind() == ElementKind.METHOD &&
e.getModifiers().contains(Modifier.ABSTRACT)) {
+ elementToReturn = (ExecutableElement) e;
+ methodCounter++;
+ }
+ }
+
+ //not a functional element, i.e. doesn't declare a single method
+ if (methodCounter != 1) {
+ return null;
+ }
+
+ return elementToReturn;
+ }
+
}
diff --git
a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaPreconditionChecker.java
b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaPreconditionChecker.java
index cb158c1a7f..b13179430c 100644
---
a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaPreconditionChecker.java
+++
b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToLambdaPreconditionChecker.java
@@ -561,39 +561,13 @@ private boolean
doAllParamsMatchExcludingGivenIndex(ExecutableElement invokingEl
return true;
}
- private ExecutableElement getFunctionalMethodFromElement(Element element) {
-
- Element classType = types.asElement(element.asType());
-
- //return null if classType is invalid, such as a primitive type
- if (classType == null) {
- return null;
- }
-
- ExecutableElement elementToReturn = null;
- int methodCounter = 0;
- for (Element e : classType.getEnclosedElements()) {
- if (e.getKind() == ElementKind.METHOD) {
- elementToReturn = (ExecutableElement) e;
- methodCounter++;
- }
- }
-
- //not a functional element, i.e. doesn't declare a single method
- if (methodCounter != 1) {
- return null;
- }
-
- return elementToReturn;
- }
-
private boolean doesLambdaElementMatchFound(ExecutableElement
possibleMatchingElement, int indexOfLambdaInArgs) {
TreePath pathToLambdaMethod = new TreePath(pathToNewClassTree,
lambdaMethodTree);
ExecutableElement lambdaMethodElement = (ExecutableElement)
getElementFromTreePath(pathToLambdaMethod);
Element paramAtIndexOfLambda =
possibleMatchingElement.getParameters().get(indexOfLambdaInArgs);
- ExecutableElement possibleLambdaMatch =
getFunctionalMethodFromElement(paramAtIndexOfLambda);
+ ExecutableElement possibleLambdaMatch =
Utilities.getFunctionalMethodFromElement(info, paramAtIndexOfLambda);
if (possibleLambdaMatch == null) {
return false;
diff --git
a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/CreateMethodTest.java
b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/CreateMethodTest.java
index a8e98beddb..080f93ff72 100644
---
a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/CreateMethodTest.java
+++
b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/CreateMethodTest.java
@@ -301,6 +301,156 @@ public void test233502() throws Exception {
"}\n").replaceAll("[ \n\t\r]+", " "));
}
+ public void testMethodRefInstanceRefToInstance() throws Exception {
+ sourceLevel = "1.8";
+ performFixTest("test/Test.java",
+ "package test;\n" +
+ "public class Test {\n" +
+ " public void test(Runnable r) {\n" +
+ " test(this::undef);\n" +
+ " }\n" +
+ "}\n",
+ -1,
+ "CreateMethodFix:undef()void:test.Test",
+ ("package test;\n" +
+ "public class Test {\n" +
+ " public void test(Runnable r) {\n" +
+ " test(this::undef);\n" +
+ " }\n" +
+ " private void undef() {\n" +
+ " throw new
UnsupportedOperationException(\"Not supported yet.\"); //To change body of
generated methods, choose Tools | Templates. \n" +
+ " }\n" +
+ "}\n").replaceAll("[ \n\t\r]+", " "));
+ }
+
+ public void testMethodRefStaticRefToStatic() throws Exception {
+ sourceLevel = "1.8";
+ performFixTest("test/Test.java",
+ "package test;\n" +
+ "public class Test {\n" +
+ " public void test(Runnable r) {\n" +
+ " test(Test::undef);\n" +
+ " }\n" +
+ "}\n",
+ -1,
+ "CreateMethodFix:undef()void:test.Test",
+ ("package test;\n" +
+ "public class Test {\n" +
+ " private static void undef() {\n" +
+ " throw new
UnsupportedOperationException(\"Not supported yet.\"); //To change body of
generated methods, choose Tools | Templates. \n" +
+ " }\n" +
+ " public void test(Runnable r) {\n" +
+ " test(Test::undef);\n" +
+ " }\n" +
+ "}\n").replaceAll("[ \n\t\r]+", " "));
+ }
+
+ public void testMethodRefStaticRefToInstance() throws Exception {
+ sourceLevel = "1.8";
+ performFixTest("test/Test.java",
+ "package test;\n" +
+ "public class Test {\n" +
+ " public void test(I<Test> r) {\n" +
+ " test(Test::undef);\n" +
+ " }\n" +
+ " public interface I<T> {\n" +
+ " public void run(T t);\n" +
+ " public default void run2() {}\n" +
+ " }\n" +
+ "}\n",
+ -1,
+ "CreateMethodFix:undef()void:test.Test",
+ ("package test;\n" +
+ "public class Test {\n" +
+ " public void test(I<Test> r) {\n" +
+ " test(Test::undef);\n" +
+ " }\n" +
+ " private void undef() {\n" +
+ " throw new
UnsupportedOperationException(\"Not supported yet.\"); //To change body of
generated methods, choose Tools | Templates. \n" +
+ " }\n" +
+ " public interface I<T> {\n" +
+ " public void run(T t);\n" +
+ " public default void run2() {}\n" +
+ " }\n" +
+ "}\n").replaceAll("[ \n\t\r]+", " "));
+ }
+
+ public void testMethodRefStaticRefToStatic2() throws Exception {
+ sourceLevel = "1.8";
+ performFixTest("test/Test.java",
+ "package test;\n" +
+ "public class Test {\n" +
+ " public void test(I<Test> r) {\n" +
+ " test(Test::undef);\n" +
+ " }\n" +
+ " public interface I<T> {\n" +
+ " public void run(T t);\n" +
+ " public default void run2() {}\n" +
+ " }\n" +
+ "}\n",
+ -1,
+ "CreateMethodFix:undef(test.Test t)void:test.Test",
+ ("package test;\n" +
+ "public class Test {\n" +
+ " private static void undef(Test t) {\n" +
+ " throw new
UnsupportedOperationException(\"Not supported yet.\"); //To change body of
generated methods, choose Tools | Templates. \n" +
+ " }\n" +
+ " public void test(I<Test> r) {\n" +
+ " test(Test::undef);\n" +
+ " }\n" +
+ " public interface I<T> {\n" +
+ " public void run(T t);\n" +
+ " public default void run2() {}\n" +
+ " }\n" +
+ "}\n").replaceAll("[ \n\t\r]+", " "));
+ }
+
+ public void testMethodRefInstanceRefToInstance2() throws Exception {
+ sourceLevel = "1.8";
+ performFixTest("test/Test.java",
+ "package test;\n" +
+ "public class Test {\n" +
+ " public void test(I<Test> r) {\n" +
+ " test(this::undef);\n" +
+ " }\n" +
+ " public interface I<T> {\n" +
+ " public void run(T t);\n" +
+ " public default void run2() {}\n" +
+ " }\n" +
+ "}\n",
+ -1,
+ "CreateMethodFix:undef(test.Test t)void:test.Test",
+ ("package test;\n" +
+ "public class Test {\n" +
+ " public void test(I<Test> r) {\n" +
+ " test(this::undef);\n" +
+ " }\n" +
+ " private void undef(Test t) {\n" +
+ " throw new
UnsupportedOperationException(\"Not supported yet.\"); //To change body of
generated methods, choose Tools | Templates. \n" +
+ " }\n" +
+ " public interface I<T> {\n" +
+ " public void run(T t);\n" +
+ " public default void run2() {}\n" +
+ " }\n" +
+ "}\n").replaceAll("[ \n\t\r]+", " "));
+ }
+
+ public void testErroneousMethodRef() throws Exception {
+ sourceLevel = "1.8";
+ performAnalysisTest("test/Test.java",
+ "package test;\n" +
+ "public class Test {\n" +
+ " public void test(I<Test> r) {\n" +
+ " test(undef::undef);\n" +
+ " }\n" +
+ " public interface I<T> {\n" +
+ " public void run(T t);\n" +
+ " public default void run2() {}\n" +
+ " }\n" +
+ "}\n",
+ -1);
+ }
+
@Override
protected List<Fix> computeFixes(CompilationInfo info, String
diagnosticCode, int pos, TreePath path) throws Exception {
List<Fix> fixes = new CreateElement().analyze(info, diagnosticCode,
pos);
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists