mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8240658: Code completion not working for lambdas in method invocations that require type inference
Reviewed-by: vromero
This commit is contained in:
parent
b05290aaea
commit
68da63dcde
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user