8240658: Code completion not working for lambdas in method invocations that require type inference

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2020-09-14 08:13:10 +00:00
parent b05290aaea
commit 68da63dcde
9 changed files with 699 additions and 36 deletions

View File

@ -220,7 +220,11 @@ public class Flags {
*/
public static final long UNION = 1L<<39;
// Flag bit (1L << 40) is available.
/**
* Flags an erroneous TypeSymbol as viable for recovery.
* TypeSymbols only.
*/
public static final long RECOVERABLE = 1L<<40;
/**
* Flag that marks an 'effectively final' local variable.
@ -508,6 +512,7 @@ public class Flags {
MATCH_BINDING(Flags.MATCH_BINDING),
MATCH_BINDING_TO_OUTER(Flags.MATCH_BINDING_TO_OUTER),
RECORD(Flags.RECORD),
RECOVERABLE(Flags.RECOVERABLE),
SEALED(Flags.SEALED),
NON_SEALED(Flags.NON_SEALED) {
@Override

View File

@ -120,6 +120,7 @@ public class Attr extends JCTree.Visitor {
final Annotate annotate;
final ArgumentAttr argumentAttr;
final MatchBindingsComputer matchBindingsComputer;
final AttrRecover attrRecover;
public static Attr instance(Context context) {
Attr instance = context.get(attrKey);
@ -157,6 +158,7 @@ public class Attr extends JCTree.Visitor {
dependencies = Dependencies.instance(context);
argumentAttr = ArgumentAttr.instance(context);
matchBindingsComputer = MatchBindingsComputer.instance(context);
attrRecover = AttrRecover.instance(context);
Options options = Options.instance(context);
@ -419,8 +421,9 @@ public class Attr extends JCTree.Visitor {
JavaFileObject prev = log.useSource(env.toplevel.sourcefile);
try {
deferredAttr.attribSpeculative(root, env, resultInfo,
null, DeferredAttr.AttributionMode.ANALYZER,
null, DeferredAttr.AttributionMode.ATTRIB_TO_TREE,
argumentAttr.withLocalCacheContext());
attrRecover.doRecovery();
} catch (BreakAttr b) {
return b.env;
} catch (AssertionError ae) {
@ -738,6 +741,7 @@ public class Attr extends JCTree.Visitor {
Env<AttrContext> analyzeEnv = analyzer.copyEnvIfNeeded(tree, env);
Type result = attribTree(tree, env, statInfo);
analyzer.analyzeIfNeeded(tree, analyzeEnv);
attrRecover.doRecovery();
return result;
}
@ -2092,6 +2096,7 @@ public class Attr extends JCTree.Visitor {
}
void preFlow(JCTree tree) {
attrRecover.doRecovery();
new PostAttrAnalyzer() {
@Override
public void scan(JCTree tree) {
@ -3114,6 +3119,7 @@ public class Attr extends JCTree.Visitor {
}
void preFlow(JCLambda tree) {
attrRecover.doRecovery();
new PostAttrAnalyzer() {
@Override
public void scan(JCTree tree) {
@ -4307,10 +4313,7 @@ public class Attr extends JCTree.Visitor {
Env<AttrContext> env,
ResultInfo resultInfo) {
if (resultInfo.pkind.contains(KindSelector.POLY)) {
Type pt = resultInfo.pt.map(deferredAttr.new RecoveryDeferredTypeMap(AttrMode.SPECULATIVE, sym, env.info.pendingResolutionPhase));
Type owntype = checkIdInternal(tree, site, sym, pt, env, resultInfo);
resultInfo.pt.map(deferredAttr.new RecoveryDeferredTypeMap(AttrMode.CHECK, sym, env.info.pendingResolutionPhase));
return owntype;
return attrRecover.recoverMethodInvocation(tree, site, sym, env, resultInfo);
} else {
return checkIdInternal(tree, site, sym, resultInfo.pt, env, resultInfo);
}
@ -4916,9 +4919,12 @@ public class Attr extends JCTree.Visitor {
}
public void visitErroneous(JCErroneous tree) {
if (tree.errs != null)
if (tree.errs != null) {
Env<AttrContext> errEnv = env.dup(env.tree);
errEnv.info.returnResult = unknownExprInfo;
for (JCTree err : tree.errs)
attribTree(err, env, new ResultInfo(KindSelector.ERR, pt()));
attribTree(err, errEnv, new ResultInfo(KindSelector.ERR, pt()));
}
result = tree.type = syms.errType;
}

View File

@ -0,0 +1,282 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javac.comp;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ArrayType;
import com.sun.tools.javac.code.Type.ErrorType;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.comp.Attr.ResultInfo;
import com.sun.tools.javac.comp.DeferredAttr.AttrMode;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCErroneous;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCReturn;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;
/** This is an error recovery addon for Attr. Currently, it recovers
* method invocations with lambdas, that require type inference.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class AttrRecover {
protected static final Context.Key<AttrRecover> attrRepairKey = new Context.Key<>();
final Attr attr;
final DeferredAttr deferredAttr;
final Names names;
final TreeMaker make;
final Symtab syms;
final Types types;
public static AttrRecover instance(Context context) {
AttrRecover instance = context.get(attrRepairKey);
if (instance == null)
instance = new AttrRecover(context);
return instance;
}
protected AttrRecover(Context context) {
context.put(attrRepairKey, this);
attr = Attr.instance(context);
deferredAttr = DeferredAttr.instance(context);
names = Names.instance(context);
make = TreeMaker.instance(context);
syms = Symtab.instance(context);
types = Types.instance(context);
}
private final ListBuffer<RecoverTodo> recoveryTodo = new ListBuffer<>();
public void doRecovery() {
while (recoveryTodo.nonEmpty()) {
RecoverTodo todo = recoveryTodo.remove();
ListBuffer<Runnable> rollback = new ListBuffer<>();
boolean repaired = false;
RECOVER: if (todo.env.tree.hasTag(Tag.APPLY)) {
JCMethodInvocation mit = (JCMethodInvocation) todo.env.tree;
boolean vararg = (todo.candSym.flags() & Flags.VARARGS) != 0;
if (!vararg &&
mit.args.length() > todo.candSym.type.getParameterTypes().length()) {
break RECOVER; //too many actual parameters, skip
}
List<JCExpression> args = mit.args;
List<Type> formals = todo.candSym.type.getParameterTypes();
while (args.nonEmpty() && formals.nonEmpty()) {
JCExpression arg = args.head;
Type formal = formals.tail.nonEmpty() || !vararg
? formals.head : ((ArrayType) formals.head).elemtype;
if (arg.hasTag(JCTree.Tag.LAMBDA)) {
final JCTree.JCLambda lambda = (JCLambda) arg;
if (lambda.paramKind == JCLambda.ParameterKind.IMPLICIT) {
for (JCVariableDecl var : lambda.params) {
var.vartype = null; //reset type
}
}
if (types.isFunctionalInterface(formal)) {
Type functionalType = types.findDescriptorType(formal);
boolean voidCompatible = functionalType.getReturnType().hasTag(TypeTag.VOID);
lambda.body = new TreeTranslator() {
@Override
public void visitReturn(JCReturn tree) {
result = tree;
if (voidCompatible) {
if (tree.expr != null) {
JCErroneous err = make.Erroneous(List.of(tree));
result = err;
rollback.append(() -> {
lambda.body = new TreeTranslator() {
@SuppressWarnings("unchecked")
public <T extends JCTree> T translate(T t) {
if (t == err) return (T) tree;
else return super.translate(t);
}
}.translate(lambda.body);
});
}
} else {
if (tree.expr == null) {
tree.expr = make.Erroneous().setType(syms.errType);
rollback.append(() -> {
tree.expr = null;
});
}
}
}
@Override
public void visitLambda(JCLambda tree) {
//do not touch nested lambdas
}
@Override
public void visitClassDef(JCClassDecl tree) {
//do not touch nested classes
}
}.translate(lambda.body);
if (!voidCompatible) {
JCReturn ret = make.Return(make.Erroneous().setType(syms.errType));
((JCBlock) lambda.body).stats = ((JCBlock) lambda.body).stats.append(ret);
rollback.append(() -> {
((JCBlock) lambda.body).stats = List.filter(((JCBlock) lambda.body).stats, ret);
});
}
}
repaired = true;
}
args = args.tail;
if (formals.tail.nonEmpty() || !vararg) {
formals = formals.tail;
}
}
List<JCExpression> prevArgs = mit.args;
while (formals.nonEmpty()) {
mit.args = mit.args.append(make.Erroneous().setType(syms.errType));
formals = formals.tail;
repaired = true;
}
rollback.append(() -> {
mit.args = prevArgs;
});
}
Type owntype;
if (repaired) {
List<JCExpression> args = TreeInfo.args(todo.env.tree);
List<Type> pats = todo.resultInfo.pt.getParameterTypes();
while (pats.length() < args.length()) {
pats = pats.append(syms.errType);
}
owntype = attr.checkMethod(todo.site, todo.candSym,
attr.new ResultInfo(todo.resultInfo.pkind, todo.resultInfo.pt.getReturnType(), todo.resultInfo.checkContext, todo.resultInfo.checkMode),
todo.env, args, pats,
todo.resultInfo.pt.getTypeArguments());
rollback.stream().forEach(Runnable::run);
} else {
owntype = basicMethodInvocationRecovery(todo.tree, todo.site, todo.errSym, todo.env, todo.resultInfo);
}
todo.tree.type = owntype;
}
}
Type recoverMethodInvocation(JCTree tree,
Type site,
Symbol sym,
Env<AttrContext> env,
ResultInfo resultInfo) {
if ((sym.flags_field & Flags.RECOVERABLE) != 0 && env.info.attributionMode.recover()) {
recoveryTodo.append(new RecoverTodo(tree, site, sym, ((RecoveryErrorType) sym.type).candidateSymbol, attr.copyEnv(env), resultInfo));
return syms.errType;
} else {
return basicMethodInvocationRecovery(tree, site, sym, env, resultInfo);
}
}
private Type basicMethodInvocationRecovery(JCTree tree,
Type site,
Symbol sym,
Env<AttrContext> env,
ResultInfo resultInfo) {
Type pt = resultInfo.pt.map(deferredAttr.new RecoveryDeferredTypeMap(AttrMode.SPECULATIVE, sym, env.info.pendingResolutionPhase));
Type owntype = attr.checkIdInternal(tree, site, sym, pt, env, resultInfo);
resultInfo.pt.map(deferredAttr.new RecoveryDeferredTypeMap(AttrMode.CHECK, sym, env.info.pendingResolutionPhase));
return owntype;
}
void wrongMethodSymbolCandidate(TypeSymbol errSymbol, Symbol candSym, JCDiagnostic diag) {
List<JCDiagnostic> diags = List.of(diag);
boolean recoverable = false;
while (!recoverable && diags.nonEmpty()) {
JCDiagnostic d = diags.head;
diags = diags.tail;
switch (d.getCode()) {
case "compiler.misc.missing.ret.val":
case "compiler.misc.unexpected.ret.val":
case "compiler.misc.infer.arg.length.mismatch":
case "compiler.misc.arg.length.mismatch":
errSymbol.type = new RecoveryErrorType((Type.ErrorType) errSymbol.type, candSym);
errSymbol.flags_field |= Flags.RECOVERABLE;
return ;
default:
break;
}
for (Object a : d.getArgs()) {
if (a instanceof JCDiagnostic) {
diags = diags.prepend((JCDiagnostic) a);
}
}
}
}
private static class RecoveryErrorType extends ErrorType {
public final Symbol candidateSymbol;
public RecoveryErrorType(ErrorType original, Symbol candidateSymbol) {
super(original.getOriginalType(), original.tsym);
this.candidateSymbol = candidateSymbol;
}
}
private static class RecoverTodo {
public final JCTree tree;
public final Type site;
public final Symbol errSym;
public final Symbol candSym;
public final Env<AttrContext> env;
public final ResultInfo resultInfo;
public RecoverTodo(JCTree tree, Type site, Symbol errSym, Symbol candSym,
Env<AttrContext> env, Attr.ResultInfo resultInfo) {
this.tree = tree;
this.site = site;
this.errSym = errSym;
this.candSym = candSym;
this.env = env;
this.resultInfo = resultInfo;
}
}
}

View File

@ -1329,20 +1329,28 @@ public class DeferredAttr extends JCTree.Visitor {
*/
enum AttributionMode {
/**Normal, non-speculative, attribution.*/
FULL(false),
FULL(false, true),
/**Speculative attribution on behalf of an Analyzer.*/
ANALYZER(true),
ATTRIB_TO_TREE(true, true),
/**Speculative attribution on behalf of an Analyzer.*/
ANALYZER(true, false),
/**Speculative attribution.*/
SPECULATIVE(true);
SPECULATIVE(true, false);
AttributionMode(boolean isSpeculative) {
AttributionMode(boolean isSpeculative, boolean recover) {
this.isSpeculative = isSpeculative;
this.recover = recover;
}
boolean isSpeculative() {
return isSpeculative;
}
boolean recover() {
return recover;
}
final boolean isSpeculative;
final boolean recover;
}
}

View File

@ -96,6 +96,7 @@ public class Resolve {
Log log;
Symtab syms;
Attr attr;
AttrRecover attrRecover;
DeferredAttr deferredAttr;
Check chk;
Infer infer;
@ -126,6 +127,7 @@ public class Resolve {
names = Names.instance(context);
log = Log.instance(context);
attr = Attr.instance(context);
attrRecover = AttrRecover.instance(context);
deferredAttr = DeferredAttr.instance(context);
chk = Check.instance(context);
infer = Infer.instance(context);
@ -4043,12 +4045,12 @@ public class Resolve {
@Override
public Symbol access(Name name, TypeSymbol location) {
Symbol sym = bestCandidate();
return types.createErrorType(name, location, sym != null ? sym.type : syms.errSymbol.type).tsym;
}
protected Symbol bestCandidate() {
return errCandidate().fst;
Pair<Symbol, JCDiagnostic> cand = errCandidate();
TypeSymbol errSymbol = types.createErrorType(name, location, cand != null ? cand.fst.type : syms.errSymbol.type).tsym;
if (cand != null) {
attrRecover.wrongMethodSymbolCandidate(errSymbol, cand.fst, cand.snd);
}
return errSymbol;
}
protected Pair<Symbol, JCDiagnostic> errCandidate() {
@ -4181,11 +4183,12 @@ public class Resolve {
}
@Override
protected Symbol bestCandidate() {
protected Pair<Symbol, JCDiagnostic> errCandidate() {
Map<Symbol, JCDiagnostic> candidatesMap = mapCandidates();
Map<Symbol, JCDiagnostic> filteredCandidates = filterCandidates(candidatesMap);
if (filteredCandidates.size() == 1) {
return filteredCandidates.keySet().iterator().next();
return Pair.of(filteredCandidates.keySet().iterator().next(),
filteredCandidates.values().iterator().next());
}
return null;
}

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 8131025 8141092 8153761 8145263 8131019 8175886 8176184 8176241 8176110 8177466 8197439 8221759 8234896
* @bug 8131025 8141092 8153761 8145263 8131019 8175886 8176184 8176241 8176110 8177466 8197439 8221759 8234896 8240658
* @summary Test Completion and Documentation
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
@ -698,6 +698,27 @@ public class CompletionSuggestionTest extends KullaTesting {
assertCompletion("FI2<Object, String> fi = C::|", true, "statConvert1", "statConvert3");
}
public void testBrokenLambdaCompletion() {
assertEval("interface Consumer<T> { public void consume(T t); }");
assertEval("interface Function<T, R> { public R convert(T t); }");
assertEval("<T> void m1(T t, Consumer<T> f) { }");
assertCompletion("m1(\"\", x -> {x.tri|", "trim()");
assertEval("<T> void m2(T t, Function<T, String> f) { }");
assertCompletion("m2(\"\", x -> {x.tri|", "trim()");
assertEval("<T> void m3(T t, Consumer<T> f, int i) { }");
assertCompletion("m3(\"\", x -> {x.tri|", "trim()");
assertEval("<T> void m4(T t, Function<T, String> f, int i) { }");
assertCompletion("m4(\"\", x -> {x.tri|", "trim()");
assertEval("<T> T m5(Consumer<T> f) { return null; }");
assertCompletion("String s = m5(x -> {x.tri|", "trim()");
assertEval("<T> T m6(Function<T, String> f) { return null; }");
assertCompletion("String s = m6(x -> {x.tri|", "trim()");
assertEval("<T> T m7(Consumer<T> f, int i) { return null; }");
assertCompletion("String s = m7(x -> {x.tri|", "trim()");
assertEval("<T> T m8(Function<T, String> f, int i) { return null; }");
assertCompletion("String s = m8(x -> {x.tri|", "trim()");
}
@BeforeMethod
public void setUp() {
super.setUp();

View File

@ -23,12 +23,13 @@
/*
* @test
* @bug 8205418 8207229 8207230 8230847 8245786 8247334 8248641
* @bug 8205418 8207229 8207230 8230847 8245786 8247334 8248641 8240658
* @summary Test the outcomes from Trees.getScope
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.comp
* jdk.compiler/com.sun.tools.javac.tree
* jdk.compiler/com.sun.tools.javac.util
* @compile TestGetScopeResult.java
*/
import java.io.IOException;
@ -62,6 +63,7 @@ import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.api.JavacScope;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.comp.Analyzer;
@ -192,6 +194,19 @@ public class TestGetScopeResult {
}
}""",
invocationInMethodInvocation);
String[] infer = {
"c:java.lang.String",
"super:java.lang.Object",
"this:Test"
};
doTest("class Test { void test() { cand(\"\", c -> { }); } <T>void cand(T t, I<T> i) { } interface I<T> { public String test(T s); } }",
infer);
doTest("class Test { void test() { cand(\"\", c -> { }); } <T>void cand(T t, I<T> i, int j) { } interface I<T> { public void test(T s); } }",
infer);
doTest("class Test { void test() { cand(\"\", c -> { }); } <T>void cand(T t, I<T> i, int j) { } interface I<T> { public String test(T s); } }",
infer);
}
public void doTest(String code, String... expected) throws IOException {
@ -208,24 +223,29 @@ public class TestGetScopeResult {
}
JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, List.of(new MyFileObject()));
CompilationUnitTree cut = t.parse().iterator().next();
t.analyze();
List<String> actual = new ArrayList<>();
((JavacTaskImpl)t).enter();
new TreePathScanner<Void, Void>() {
@Override
public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
Scope scope = Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getBody()));
actual.addAll(dumpScope(scope));
return super.visitLambdaExpression(node, p);
for (int r = 0; r < 2; r++) {
List<String> actual = new ArrayList<>();
new TreePathScanner<Void, Void>() {
@Override
public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
Scope scope = Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getBody()));
actual.addAll(dumpScope(scope));
return super.visitLambdaExpression(node, p);
}
}.scan(cut, null);
List<String> expectedList = List.of(expected);
if (!expectedList.equals(actual)) {
throw new IllegalStateException("Unexpected scope content: " + actual + "\n" +
"expected: " + expectedList);
}
}.scan(cut, null);
List<String> expectedList = List.of(expected);
if (!expectedList.equals(actual)) {
throw new IllegalStateException("Unexpected scope content: " + actual + "\n" +
"expected: " + expectedList);
t.analyze();
}
}
}

View File

@ -0,0 +1,248 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8240658
* @summary Verify that broken method invocations with lambdas get type inference done
* @modules jdk.compiler
* @compile --enable-preview -source ${jdk.version} TestGetTypeMirrorReference.java
* @run main/othervm --enable-preview TestGetTypeMirrorReference
*/
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.lang.model.type.TypeMirror;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/*
* This test verifies proper error recovery for method invocations which need
* type inference, and have lambdas as arguments.
*
* The test will read the adjacent TestGetTypeMirrorReferenceData.java, parse and
* attribute it, and call Trees.getTypeMirror on each place marked, in a block
* comment, with:
* getTypeMirror:<expected-typemirror-kind>:<expected-typemirror-toString>
* The actual retrieved TypeMirror will be checked against the provided description,
* verifying the return value of TypeMirror.getKind and TypeMirror.toString().
*
* The AST for TestGetTypeMirrorReferenceData.java will also be printed using
* Tree.toString(), and compared to the expected AST form.
*/
public class TestGetTypeMirrorReference {
private static final String JDK_VERSION =
Integer.toString(Runtime.version().feature());
public static void main(String... args) throws IOException {
analyze("TestGetTypeMirrorReferenceData.java",
"""
package test;
public class TestGetTypeMirrorReferenceData {
public TestGetTypeMirrorReferenceData() {
super();
}
private static void test() {
Test.of(1).convert((c1)->{
Object o = c1;
});
Test.of(1).consume((c2)->{
Object o = c2;
return null;
});
Test.of(1).consumeWithParam((c3)->{
Object o = c3;
});
convert(0, (c4)->{
Object o = c4;
});
consume(0, (c5)->{
Object o = c5;
});
convertVarArgs(0, (c6)->{
Object o = c6;
}, 1, 2, 3, 4);
consumeVarArgs(0, (c7)->{
Object o = c7;
}, 1, 2, 3, 4);
convertVarArgs2(0, (c8)->{
Object o = c8;
}, (c8)->{
Object o = c8;
});
consumeVarArgs2(0, (c9)->{
Object o = c9;
}, (c9)->{
Object o = c9;
});
}
public <T, R>R convert(T t, Function<T, R> f, int i) {
return null;
}
public <T>void consume(T t, Consumer<T> c, int i) {
}
public <T, R>R convertVarArgs(T t, Function<T, R> c, int... i) {
return null;
}
public <T>void consumeVarArgs(T t, Consumer<T> c, int... i) {
}
public <T, R>R convertVarArgs2(T t, Function<T, R>... c) {
return null;
}
public <T>void consumeVarArgs2(T t, Consumer<T>... c) {
}
public static class Test<T> {
public Test() {
super();
}
public static <T>Test<T> of(T t) {
return new Test<>();
}
public <R>Test<R> convert(Function<T, R> c) {
return null;
}
public void consume(Consumer<T> c) {
}
public void consumeWithParam(Consumer<T> c, int i) {
}
}
public interface Function<T, R> {
public R map(T t);
}
public interface Consumer<T> {
public void run(T t);
}
}""");
}
private static void analyze(String fileName, String expectedAST) throws IOException {
try (StandardJavaFileManager fm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null)) {
List<JavaFileObject> files = new ArrayList<>();
File source = new File(System.getProperty("test.src", "."), fileName.replace('/', File.separatorChar)).getAbsoluteFile();
for (JavaFileObject f : fm.getJavaFileObjects(source)) {
files.add(f);
}
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
List<String> options = List.of("-source", JDK_VERSION,
"-XDshould-stop.at=FLOW");
JavacTask ct = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, diagnostics, options, null, files);
Trees trees = Trees.instance(ct);
CompilationUnitTree cut = ct.parse().iterator().next();
ct.analyze();
String actualAST = Arrays.stream(cut.toString().split("\n"))
.map(l -> l.stripTrailing())
.collect(Collectors.joining("\n"));
if (!expectedAST.equals(actualAST)) {
throw new AssertionError("Unexpected AST shape!\n" + actualAST);
}
Pattern p = Pattern.compile("/\\*getTypeMirror:(.*?)\\*/");
Matcher m = p.matcher(cut.getSourceFile().getCharContent(false));
while (m.find()) {
TreePath tp = pathFor(trees, cut, m.start() - 1);
String expected = m.group(1);
if (expected.startsWith("getParentPath:")) {
tp = tp.getParentPath();
expected = expected.substring("getParentPath:".length());
}
TypeMirror found = trees.getTypeMirror(tp);
String actual = found != null ? found.getKind() + ":" + typeToString(found) : "<null>";
if (!expected.equals(actual)) {
throw new IllegalStateException("expected=" + expected + "; actual=" + actual + "; tree: " + tp.getLeaf());
}
}
}
}
private static TreePath pathFor(final Trees trees, final CompilationUnitTree cut, final int pos) {
final TreePath[] result = new TreePath[1];
new TreePathScanner<Void, Void>() {
@Override public Void scan(Tree node, Void p) {
if ( node != null
&& trees.getSourcePositions().getStartPosition(cut, node) <= pos
&& pos <= trees.getSourcePositions().getEndPosition(cut, node)) {
result[0] = new TreePath(getCurrentPath(), node);
return super.scan(node, p);
}
return null;
}
}.scan(cut, null);
return result[0];
}
private static String typeToString(TypeMirror type) {
return type.toString();
}
static class TestFileObject extends SimpleJavaFileObject {
private final String text;
public TestFileObject(String text) {
super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
this.text = text;
}
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return text;
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test;
public class TestGetTypeMirrorReferenceData {
private static void test() {
Test.of(1).convert(c1 -> {Object o = c1/*getTypeMirror:DECLARED:java.lang.Integer*/;});
Test.of(1).consume(c2 -> {Object o = c2/*getTypeMirror:DECLARED:java.lang.Integer*/; return null;});
Test.of(1).consumeWithParam(c3 -> {Object o = c3/*getTypeMirror:DECLARED:java.lang.Integer*/;});
convert(0, c4 -> {Object o = c4/*getTypeMirror:DECLARED:java.lang.Integer*/;});
consume(0, c5 -> {Object o = c5/*getTypeMirror:DECLARED:java.lang.Integer*/;});
convertVarArgs(0, c6 -> {Object o = c6/*getTypeMirror:DECLARED:java.lang.Integer*/;}, 1, 2, 3, 4);
consumeVarArgs(0, c7 -> {Object o = c7/*getTypeMirror:DECLARED:java.lang.Integer*/;}, 1, 2, 3, 4);
convertVarArgs2(0, c8 -> {Object o = c8/*getTypeMirror:DECLARED:java.lang.Integer*/;}, c8 -> {Object o = c8/*getTypeMirror:DECLARED:java.lang.Integer*/;});
consumeVarArgs2(0, c9 -> {Object o = c9/*getTypeMirror:DECLARED:java.lang.Integer*/;}, c9 -> {Object o = c9/*getTypeMirror:DECLARED:java.lang.Integer*/;});
}
public <T, R> R convert(T t, Function<T, R> f, int i) {
return null;
}
public <T> void consume(T t, Consumer<T> c, int i) {
}
public <T, R> R convertVarArgs(T t, Function<T, R> c, int... i) {
return null;
}
public <T> void consumeVarArgs(T t, Consumer<T> c, int... i) {
}
public <T, R> R convertVarArgs2(T t, Function<T, R>... c) {
return null;
}
public <T> void consumeVarArgs2(T t, Consumer<T>... c) {
}
public static class Test<T> {
public static <T> Test<T> of(T t) {
return new Test<>();
}
public <R> Test<R> convert(Function<T, R> c) {
return null;
}
public void consume(Consumer<T> c) {}
public void consumeWithParam(Consumer<T> c, int i) {}
}
public interface Function<T, R> {
public R map(T t);
}
public interface Consumer<T> {
public void run(T t);
}
}