1422 lines
57 KiB
Java
1422 lines
57 KiB
Java
/*
|
|
* Copyright (c) 2012, 2015, 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.source.tree.LambdaExpressionTree.BodyKind;
|
|
import com.sun.tools.javac.code.*;
|
|
import com.sun.tools.javac.tree.*;
|
|
import com.sun.tools.javac.util.*;
|
|
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
|
|
import com.sun.tools.javac.code.Symbol.*;
|
|
import com.sun.tools.javac.code.Type.*;
|
|
import com.sun.tools.javac.comp.Attr.ResultInfo;
|
|
import com.sun.tools.javac.comp.Infer.InferenceContext;
|
|
import com.sun.tools.javac.comp.Resolve.MethodResolutionPhase;
|
|
import com.sun.tools.javac.tree.JCTree.*;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.EnumSet;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.WeakHashMap;
|
|
|
|
import static com.sun.tools.javac.code.Kinds.VAL;
|
|
import static com.sun.tools.javac.code.TypeTag.*;
|
|
import static com.sun.tools.javac.tree.JCTree.Tag.*;
|
|
|
|
/**
|
|
* This is an helper class that is used to perform deferred type-analysis.
|
|
* Each time a poly expression occurs in argument position, javac attributes it
|
|
* with a temporary 'deferred type' that is checked (possibly multiple times)
|
|
* against an expected formal type.
|
|
*
|
|
* <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 DeferredAttr extends JCTree.Visitor {
|
|
protected static final Context.Key<DeferredAttr> deferredAttrKey =
|
|
new Context.Key<DeferredAttr>();
|
|
|
|
final Attr attr;
|
|
final Check chk;
|
|
final JCDiagnostic.Factory diags;
|
|
final Enter enter;
|
|
final Infer infer;
|
|
final Resolve rs;
|
|
final Log log;
|
|
final Symtab syms;
|
|
final TreeMaker make;
|
|
final Types types;
|
|
final Flow flow;
|
|
final Names names;
|
|
final TypeEnvs typeEnvs;
|
|
|
|
public static DeferredAttr instance(Context context) {
|
|
DeferredAttr instance = context.get(deferredAttrKey);
|
|
if (instance == null)
|
|
instance = new DeferredAttr(context);
|
|
return instance;
|
|
}
|
|
|
|
protected DeferredAttr(Context context) {
|
|
context.put(deferredAttrKey, this);
|
|
attr = Attr.instance(context);
|
|
chk = Check.instance(context);
|
|
diags = JCDiagnostic.Factory.instance(context);
|
|
enter = Enter.instance(context);
|
|
infer = Infer.instance(context);
|
|
rs = Resolve.instance(context);
|
|
log = Log.instance(context);
|
|
syms = Symtab.instance(context);
|
|
make = TreeMaker.instance(context);
|
|
types = Types.instance(context);
|
|
flow = Flow.instance(context);
|
|
names = Names.instance(context);
|
|
stuckTree = make.Ident(names.empty).setType(Type.stuckType);
|
|
typeEnvs = TypeEnvs.instance(context);
|
|
emptyDeferredAttrContext =
|
|
new DeferredAttrContext(AttrMode.CHECK, null, MethodResolutionPhase.BOX, infer.emptyContext, null, null) {
|
|
@Override
|
|
void addDeferredAttrNode(DeferredType dt, ResultInfo ri, DeferredStuckPolicy deferredStuckPolicy) {
|
|
Assert.error("Empty deferred context!");
|
|
}
|
|
@Override
|
|
void complete() {
|
|
Assert.error("Empty deferred context!");
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Empty deferred context!";
|
|
}
|
|
};
|
|
}
|
|
|
|
/** shared tree for stuck expressions */
|
|
final JCTree stuckTree;
|
|
|
|
/**
|
|
* This type represents a deferred type. A deferred type starts off with
|
|
* no information on the underlying expression type. Such info needs to be
|
|
* discovered through type-checking the deferred type against a target-type.
|
|
* Every deferred type keeps a pointer to the AST node from which it originated.
|
|
*/
|
|
public class DeferredType extends Type {
|
|
|
|
public JCExpression tree;
|
|
Env<AttrContext> env;
|
|
AttrMode mode;
|
|
SpeculativeCache speculativeCache;
|
|
|
|
DeferredType(JCExpression tree, Env<AttrContext> env) {
|
|
super(null);
|
|
this.tree = tree;
|
|
this.env = attr.copyEnv(env);
|
|
this.speculativeCache = new SpeculativeCache();
|
|
}
|
|
|
|
@Override
|
|
public TypeTag getTag() {
|
|
return DEFERRED;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "DeferredType";
|
|
}
|
|
|
|
/**
|
|
* A speculative cache is used to keep track of all overload resolution rounds
|
|
* that triggered speculative attribution on a given deferred type. Each entry
|
|
* stores a pointer to the speculative tree and the resolution phase in which the entry
|
|
* has been added.
|
|
*/
|
|
class SpeculativeCache {
|
|
|
|
private Map<Symbol, List<Entry>> cache =
|
|
new WeakHashMap<Symbol, List<Entry>>();
|
|
|
|
class Entry {
|
|
JCTree speculativeTree;
|
|
ResultInfo resultInfo;
|
|
|
|
public Entry(JCTree speculativeTree, ResultInfo resultInfo) {
|
|
this.speculativeTree = speculativeTree;
|
|
this.resultInfo = resultInfo;
|
|
}
|
|
|
|
boolean matches(MethodResolutionPhase phase) {
|
|
return resultInfo.checkContext.deferredAttrContext().phase == phase;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve a speculative cache entry corresponding to given symbol
|
|
* and resolution phase
|
|
*/
|
|
Entry get(Symbol msym, MethodResolutionPhase phase) {
|
|
List<Entry> entries = cache.get(msym);
|
|
if (entries == null) return null;
|
|
for (Entry e : entries) {
|
|
if (e.matches(phase)) return e;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Stores a speculative cache entry corresponding to given symbol
|
|
* and resolution phase
|
|
*/
|
|
void put(JCTree speculativeTree, ResultInfo resultInfo) {
|
|
Symbol msym = resultInfo.checkContext.deferredAttrContext().msym;
|
|
List<Entry> entries = cache.get(msym);
|
|
if (entries == null) {
|
|
entries = List.nil();
|
|
}
|
|
cache.put(msym, entries.prepend(new Entry(speculativeTree, resultInfo)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the type that has been computed during a speculative attribution round
|
|
*/
|
|
Type speculativeType(Symbol msym, MethodResolutionPhase phase) {
|
|
SpeculativeCache.Entry e = speculativeCache.get(msym, phase);
|
|
return e != null ? e.speculativeTree.type : Type.noType;
|
|
}
|
|
|
|
/**
|
|
* Check a deferred type against a potential target-type. Depending on
|
|
* the current attribution mode, a normal vs. speculative attribution
|
|
* round is performed on the underlying AST node. There can be only one
|
|
* speculative round for a given target method symbol; moreover, a normal
|
|
* attribution round must follow one or more speculative rounds.
|
|
*/
|
|
Type check(ResultInfo resultInfo) {
|
|
DeferredStuckPolicy deferredStuckPolicy;
|
|
if (resultInfo.pt.hasTag(NONE) || resultInfo.pt.isErroneous()) {
|
|
deferredStuckPolicy = dummyStuckPolicy;
|
|
} else if (resultInfo.checkContext.deferredAttrContext().mode == AttrMode.SPECULATIVE ||
|
|
resultInfo.checkContext.deferredAttrContext().insideOverloadPhase()) {
|
|
deferredStuckPolicy = new OverloadStuckPolicy(resultInfo, this);
|
|
} else {
|
|
deferredStuckPolicy = new CheckStuckPolicy(resultInfo, this);
|
|
}
|
|
return check(resultInfo, deferredStuckPolicy, basicCompleter);
|
|
}
|
|
|
|
private Type check(ResultInfo resultInfo, DeferredStuckPolicy deferredStuckPolicy,
|
|
DeferredTypeCompleter deferredTypeCompleter) {
|
|
DeferredAttrContext deferredAttrContext =
|
|
resultInfo.checkContext.deferredAttrContext();
|
|
Assert.check(deferredAttrContext != emptyDeferredAttrContext);
|
|
if (deferredStuckPolicy.isStuck()) {
|
|
deferredAttrContext.addDeferredAttrNode(this, resultInfo, deferredStuckPolicy);
|
|
return Type.noType;
|
|
} else {
|
|
try {
|
|
return deferredTypeCompleter.complete(this, resultInfo, deferredAttrContext);
|
|
} finally {
|
|
mode = deferredAttrContext.mode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A completer for deferred types. Defines an entry point for type-checking
|
|
* a deferred type.
|
|
*/
|
|
interface DeferredTypeCompleter {
|
|
/**
|
|
* Entry point for type-checking a deferred type. Depending on the
|
|
* circumstances, type-checking could amount to full attribution
|
|
* or partial structural check (aka potential applicability).
|
|
*/
|
|
Type complete(DeferredType dt, ResultInfo resultInfo, DeferredAttrContext deferredAttrContext);
|
|
}
|
|
|
|
|
|
/**
|
|
* A basic completer for deferred types. This completer type-checks a deferred type
|
|
* using attribution; depending on the attribution mode, this could be either standard
|
|
* or speculative attribution.
|
|
*/
|
|
DeferredTypeCompleter basicCompleter = new DeferredTypeCompleter() {
|
|
public Type complete(DeferredType dt, ResultInfo resultInfo, DeferredAttrContext deferredAttrContext) {
|
|
switch (deferredAttrContext.mode) {
|
|
case SPECULATIVE:
|
|
//Note: if a symbol is imported twice we might do two identical
|
|
//speculative rounds...
|
|
Assert.check(dt.mode == null || dt.mode == AttrMode.SPECULATIVE);
|
|
JCTree speculativeTree = attribSpeculative(dt.tree, dt.env, resultInfo);
|
|
dt.speculativeCache.put(speculativeTree, resultInfo);
|
|
return speculativeTree.type;
|
|
case CHECK:
|
|
Assert.check(dt.mode != null);
|
|
return attr.attribTree(dt.tree, dt.env, resultInfo);
|
|
}
|
|
Assert.error();
|
|
return null;
|
|
}
|
|
};
|
|
|
|
DeferredTypeCompleter dummyCompleter = new DeferredTypeCompleter() {
|
|
public Type complete(DeferredType dt, ResultInfo resultInfo, DeferredAttrContext deferredAttrContext) {
|
|
Assert.check(deferredAttrContext.mode == AttrMode.CHECK);
|
|
return dt.tree.type = Type.stuckType;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Policy for detecting stuck expressions. Different criteria might cause
|
|
* an expression to be judged as stuck, depending on whether the check
|
|
* is performed during overload resolution or after most specific.
|
|
*/
|
|
interface DeferredStuckPolicy {
|
|
/**
|
|
* Has the policy detected that a given expression should be considered stuck?
|
|
*/
|
|
boolean isStuck();
|
|
/**
|
|
* Get the set of inference variables a given expression depends upon.
|
|
*/
|
|
Set<Type> stuckVars();
|
|
/**
|
|
* Get the set of inference variables which might get new constraints
|
|
* if a given expression is being type-checked.
|
|
*/
|
|
Set<Type> depVars();
|
|
}
|
|
|
|
/**
|
|
* Basic stuck policy; an expression is never considered to be stuck.
|
|
*/
|
|
DeferredStuckPolicy dummyStuckPolicy = new DeferredStuckPolicy() {
|
|
@Override
|
|
public boolean isStuck() {
|
|
return false;
|
|
}
|
|
@Override
|
|
public Set<Type> stuckVars() {
|
|
return Collections.emptySet();
|
|
}
|
|
@Override
|
|
public Set<Type> depVars() {
|
|
return Collections.emptySet();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The 'mode' in which the deferred type is to be type-checked
|
|
*/
|
|
public enum AttrMode {
|
|
/**
|
|
* A speculative type-checking round is used during overload resolution
|
|
* mainly to generate constraints on inference variables. Side-effects
|
|
* arising from type-checking the expression associated with the deferred
|
|
* type are reversed after the speculative round finishes. This means the
|
|
* expression tree will be left in a blank state.
|
|
*/
|
|
SPECULATIVE,
|
|
/**
|
|
* This is the plain type-checking mode. Produces side-effects on the underlying AST node
|
|
*/
|
|
CHECK;
|
|
}
|
|
|
|
/**
|
|
* Routine that performs speculative type-checking; the input AST node is
|
|
* cloned (to avoid side-effects cause by Attr) and compiler state is
|
|
* restored after type-checking. All diagnostics (but critical ones) are
|
|
* disabled during speculative type-checking.
|
|
*/
|
|
JCTree attribSpeculative(JCTree tree, Env<AttrContext> env, ResultInfo resultInfo) {
|
|
final JCTree newTree = new TreeCopier<Object>(make).copy(tree);
|
|
Env<AttrContext> speculativeEnv = env.dup(newTree, env.info.dup(env.info.scope.dupUnshared()));
|
|
speculativeEnv.info.scope.owner = env.info.scope.owner;
|
|
Log.DeferredDiagnosticHandler deferredDiagnosticHandler =
|
|
new Log.DeferredDiagnosticHandler(log, new Filter<JCDiagnostic>() {
|
|
public boolean accepts(final JCDiagnostic d) {
|
|
class PosScanner extends TreeScanner {
|
|
boolean found = false;
|
|
|
|
@Override
|
|
public void scan(JCTree tree) {
|
|
if (tree != null &&
|
|
tree.pos() == d.getDiagnosticPosition()) {
|
|
found = true;
|
|
}
|
|
super.scan(tree);
|
|
}
|
|
};
|
|
PosScanner posScanner = new PosScanner();
|
|
posScanner.scan(newTree);
|
|
return posScanner.found;
|
|
}
|
|
});
|
|
try {
|
|
attr.attribTree(newTree, speculativeEnv, resultInfo);
|
|
unenterScanner.scan(newTree);
|
|
return newTree;
|
|
} finally {
|
|
unenterScanner.scan(newTree);
|
|
log.popDiagnosticHandler(deferredDiagnosticHandler);
|
|
}
|
|
}
|
|
//where
|
|
protected UnenterScanner unenterScanner = new UnenterScanner();
|
|
|
|
class UnenterScanner extends TreeScanner {
|
|
@Override
|
|
public void visitClassDef(JCClassDecl tree) {
|
|
ClassSymbol csym = tree.sym;
|
|
//if something went wrong during method applicability check
|
|
//it is possible that nested expressions inside argument expression
|
|
//are left unchecked - in such cases there's nothing to clean up.
|
|
if (csym == null) return;
|
|
typeEnvs.remove(csym);
|
|
chk.compiled.remove(csym.flatname);
|
|
syms.classes.remove(csym.flatname);
|
|
super.visitClassDef(tree);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A deferred context is created on each method check. A deferred context is
|
|
* used to keep track of information associated with the method check, such as
|
|
* the symbol of the method being checked, the overload resolution phase,
|
|
* the kind of attribution mode to be applied to deferred types and so forth.
|
|
* As deferred types are processed (by the method check routine) stuck AST nodes
|
|
* are added (as new deferred attribution nodes) to this context. The complete()
|
|
* routine makes sure that all pending nodes are properly processed, by
|
|
* progressively instantiating all inference variables on which one or more
|
|
* deferred attribution node is stuck.
|
|
*/
|
|
class DeferredAttrContext {
|
|
|
|
/** attribution mode */
|
|
final AttrMode mode;
|
|
|
|
/** symbol of the method being checked */
|
|
final Symbol msym;
|
|
|
|
/** method resolution step */
|
|
final Resolve.MethodResolutionPhase phase;
|
|
|
|
/** inference context */
|
|
final InferenceContext inferenceContext;
|
|
|
|
/** parent deferred context */
|
|
final DeferredAttrContext parent;
|
|
|
|
/** Warner object to report warnings */
|
|
final Warner warn;
|
|
|
|
/** list of deferred attribution nodes to be processed */
|
|
ArrayList<DeferredAttrNode> deferredAttrNodes = new ArrayList<DeferredAttrNode>();
|
|
|
|
DeferredAttrContext(AttrMode mode, Symbol msym, MethodResolutionPhase phase,
|
|
InferenceContext inferenceContext, DeferredAttrContext parent, Warner warn) {
|
|
this.mode = mode;
|
|
this.msym = msym;
|
|
this.phase = phase;
|
|
this.parent = parent;
|
|
this.warn = warn;
|
|
this.inferenceContext = inferenceContext;
|
|
}
|
|
|
|
/**
|
|
* Adds a node to the list of deferred attribution nodes - used by Resolve.rawCheckArgumentsApplicable
|
|
* Nodes added this way act as 'roots' for the out-of-order method checking process.
|
|
*/
|
|
void addDeferredAttrNode(final DeferredType dt, ResultInfo resultInfo,
|
|
DeferredStuckPolicy deferredStuckPolicy) {
|
|
deferredAttrNodes.add(new DeferredAttrNode(dt, resultInfo, deferredStuckPolicy));
|
|
}
|
|
|
|
/**
|
|
* Incrementally process all nodes, by skipping 'stuck' nodes and attributing
|
|
* 'unstuck' ones. If at any point no progress can be made (no 'unstuck' nodes)
|
|
* some inference variable might get eagerly instantiated so that all nodes
|
|
* can be type-checked.
|
|
*/
|
|
void complete() {
|
|
while (!deferredAttrNodes.isEmpty()) {
|
|
Map<Type, Set<Type>> depVarsMap = new LinkedHashMap<Type, Set<Type>>();
|
|
List<Type> stuckVars = List.nil();
|
|
boolean progress = false;
|
|
//scan a defensive copy of the node list - this is because a deferred
|
|
//attribution round can add new nodes to the list
|
|
for (DeferredAttrNode deferredAttrNode : List.from(deferredAttrNodes)) {
|
|
if (!deferredAttrNode.process(this)) {
|
|
List<Type> restStuckVars =
|
|
List.from(deferredAttrNode.deferredStuckPolicy.stuckVars())
|
|
.intersect(inferenceContext.restvars());
|
|
stuckVars = stuckVars.prependList(restStuckVars);
|
|
//update dependency map
|
|
for (Type t : List.from(deferredAttrNode.deferredStuckPolicy.depVars())
|
|
.intersect(inferenceContext.restvars())) {
|
|
Set<Type> prevDeps = depVarsMap.get(t);
|
|
if (prevDeps == null) {
|
|
prevDeps = new LinkedHashSet<Type>();
|
|
depVarsMap.put(t, prevDeps);
|
|
}
|
|
prevDeps.addAll(restStuckVars);
|
|
}
|
|
} else {
|
|
deferredAttrNodes.remove(deferredAttrNode);
|
|
progress = true;
|
|
}
|
|
}
|
|
if (!progress) {
|
|
if (insideOverloadPhase()) {
|
|
for (DeferredAttrNode deferredNode: deferredAttrNodes) {
|
|
deferredNode.dt.tree.type = Type.noType;
|
|
}
|
|
return;
|
|
}
|
|
//remove all variables that have already been instantiated
|
|
//from the list of stuck variables
|
|
try {
|
|
inferenceContext.solveAny(stuckVars, depVarsMap, warn);
|
|
inferenceContext.notifyChange();
|
|
} catch (Infer.GraphStrategy.NodeNotFoundException ex) {
|
|
//this means that we are in speculative mode and the
|
|
//set of contraints are too tight for progess to be made.
|
|
//Just leave the remaining expressions as stuck.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean insideOverloadPhase() {
|
|
DeferredAttrContext dac = this;
|
|
if (dac == emptyDeferredAttrContext) {
|
|
return false;
|
|
}
|
|
if (dac.mode == AttrMode.SPECULATIVE) {
|
|
return true;
|
|
}
|
|
return dac.parent.insideOverloadPhase();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class representing a deferred attribution node. It keeps track of
|
|
* a deferred type, along with the expected target type information.
|
|
*/
|
|
class DeferredAttrNode {
|
|
|
|
/** underlying deferred type */
|
|
DeferredType dt;
|
|
|
|
/** underlying target type information */
|
|
ResultInfo resultInfo;
|
|
|
|
/** stuck policy associated with this node */
|
|
DeferredStuckPolicy deferredStuckPolicy;
|
|
|
|
DeferredAttrNode(DeferredType dt, ResultInfo resultInfo, DeferredStuckPolicy deferredStuckPolicy) {
|
|
this.dt = dt;
|
|
this.resultInfo = resultInfo;
|
|
this.deferredStuckPolicy = deferredStuckPolicy;
|
|
}
|
|
|
|
/**
|
|
* Process a deferred attribution node.
|
|
* Invariant: a stuck node cannot be processed.
|
|
*/
|
|
@SuppressWarnings("fallthrough")
|
|
boolean process(final DeferredAttrContext deferredAttrContext) {
|
|
switch (deferredAttrContext.mode) {
|
|
case SPECULATIVE:
|
|
if (deferredStuckPolicy.isStuck()) {
|
|
dt.check(resultInfo, dummyStuckPolicy, new StructuralStuckChecker());
|
|
return true;
|
|
} else {
|
|
Assert.error("Cannot get here");
|
|
}
|
|
case CHECK:
|
|
if (deferredStuckPolicy.isStuck()) {
|
|
//stuck expression - see if we can propagate
|
|
if (deferredAttrContext.parent != emptyDeferredAttrContext &&
|
|
Type.containsAny(deferredAttrContext.parent.inferenceContext.inferencevars,
|
|
List.from(deferredStuckPolicy.stuckVars()))) {
|
|
deferredAttrContext.parent.addDeferredAttrNode(dt,
|
|
resultInfo.dup(new Check.NestedCheckContext(resultInfo.checkContext) {
|
|
@Override
|
|
public InferenceContext inferenceContext() {
|
|
return deferredAttrContext.parent.inferenceContext;
|
|
}
|
|
@Override
|
|
public DeferredAttrContext deferredAttrContext() {
|
|
return deferredAttrContext.parent;
|
|
}
|
|
}), deferredStuckPolicy);
|
|
dt.tree.type = Type.stuckType;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
Assert.check(!deferredAttrContext.insideOverloadPhase(),
|
|
"attribution shouldn't be happening here");
|
|
ResultInfo instResultInfo =
|
|
resultInfo.dup(deferredAttrContext.inferenceContext.asInstType(resultInfo.pt));
|
|
dt.check(instResultInfo, dummyStuckPolicy, basicCompleter);
|
|
return true;
|
|
}
|
|
default:
|
|
throw new AssertionError("Bad mode");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Structural checker for stuck expressions
|
|
*/
|
|
class StructuralStuckChecker extends TreeScanner implements DeferredTypeCompleter {
|
|
|
|
ResultInfo resultInfo;
|
|
InferenceContext inferenceContext;
|
|
Env<AttrContext> env;
|
|
|
|
public Type complete(DeferredType dt, ResultInfo resultInfo, DeferredAttrContext deferredAttrContext) {
|
|
this.resultInfo = resultInfo;
|
|
this.inferenceContext = deferredAttrContext.inferenceContext;
|
|
this.env = dt.env;
|
|
dt.tree.accept(this);
|
|
dt.speculativeCache.put(stuckTree, resultInfo);
|
|
return Type.noType;
|
|
}
|
|
|
|
@Override
|
|
public void visitLambda(JCLambda tree) {
|
|
Check.CheckContext checkContext = resultInfo.checkContext;
|
|
Type pt = resultInfo.pt;
|
|
if (!inferenceContext.inferencevars.contains(pt)) {
|
|
//must be a functional descriptor
|
|
Type descriptorType = null;
|
|
try {
|
|
descriptorType = types.findDescriptorType(pt);
|
|
} catch (Types.FunctionDescriptorLookupError ex) {
|
|
checkContext.report(null, ex.getDiagnostic());
|
|
}
|
|
|
|
if (descriptorType.getParameterTypes().length() != tree.params.length()) {
|
|
checkContext.report(tree,
|
|
diags.fragment("incompatible.arg.types.in.lambda"));
|
|
}
|
|
|
|
Type currentReturnType = descriptorType.getReturnType();
|
|
boolean returnTypeIsVoid = currentReturnType.hasTag(VOID);
|
|
if (tree.getBodyKind() == BodyKind.EXPRESSION) {
|
|
boolean isExpressionCompatible = !returnTypeIsVoid ||
|
|
TreeInfo.isExpressionStatement((JCExpression)tree.getBody());
|
|
if (!isExpressionCompatible) {
|
|
resultInfo.checkContext.report(tree.pos(),
|
|
diags.fragment("incompatible.ret.type.in.lambda",
|
|
diags.fragment("missing.ret.val", currentReturnType)));
|
|
}
|
|
} else {
|
|
LambdaBodyStructChecker lambdaBodyChecker =
|
|
new LambdaBodyStructChecker();
|
|
|
|
tree.body.accept(lambdaBodyChecker);
|
|
boolean isVoidCompatible = lambdaBodyChecker.isVoidCompatible;
|
|
|
|
if (returnTypeIsVoid) {
|
|
if (!isVoidCompatible) {
|
|
resultInfo.checkContext.report(tree.pos(),
|
|
diags.fragment("unexpected.ret.val"));
|
|
}
|
|
} else {
|
|
boolean isValueCompatible = lambdaBodyChecker.isPotentiallyValueCompatible
|
|
&& !canLambdaBodyCompleteNormally(tree);
|
|
if (!isValueCompatible && !isVoidCompatible) {
|
|
log.error(tree.body.pos(),
|
|
"lambda.body.neither.value.nor.void.compatible");
|
|
}
|
|
|
|
if (!isValueCompatible) {
|
|
resultInfo.checkContext.report(tree.pos(),
|
|
diags.fragment("incompatible.ret.type.in.lambda",
|
|
diags.fragment("missing.ret.val", currentReturnType)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
boolean canLambdaBodyCompleteNormally(JCLambda tree) {
|
|
JCLambda newTree = new TreeCopier<>(make).copy(tree);
|
|
/* attr.lambdaEnv will create a meaningful env for the
|
|
* lambda expression. This is specially useful when the
|
|
* lambda is used as the init of a field. But we need to
|
|
* remove any added symbol.
|
|
*/
|
|
Env<AttrContext> localEnv = attr.lambdaEnv(newTree, env);
|
|
try {
|
|
List<JCVariableDecl> tmpParams = newTree.params;
|
|
while (tmpParams.nonEmpty()) {
|
|
tmpParams.head.vartype = make.at(tmpParams.head).Type(syms.errType);
|
|
tmpParams = tmpParams.tail;
|
|
}
|
|
|
|
attr.attribStats(newTree.params, localEnv);
|
|
|
|
/* set pt to Type.noType to avoid generating any bound
|
|
* which may happen if lambda's return type is an
|
|
* inference variable
|
|
*/
|
|
Attr.ResultInfo bodyResultInfo = attr.new ResultInfo(VAL, Type.noType);
|
|
localEnv.info.returnResult = bodyResultInfo;
|
|
|
|
// discard any log output
|
|
Log.DiagnosticHandler diagHandler = new Log.DiscardDiagnosticHandler(log);
|
|
try {
|
|
JCBlock body = (JCBlock)newTree.body;
|
|
/* we need to attribute the lambda body before
|
|
* doing the aliveness analysis. This is because
|
|
* constant folding occurs during attribution
|
|
* and the reachability of some statements depends
|
|
* on constant values, for example:
|
|
*
|
|
* while (true) {...}
|
|
*/
|
|
attr.attribStats(body.stats, localEnv);
|
|
|
|
attr.preFlow(newTree);
|
|
/* make an aliveness / reachability analysis of the lambda
|
|
* to determine if it can complete normally
|
|
*/
|
|
flow.analyzeLambda(localEnv, newTree, make, true);
|
|
} finally {
|
|
log.popDiagnosticHandler(diagHandler);
|
|
}
|
|
return newTree.canCompleteNormally;
|
|
} finally {
|
|
JCBlock body = (JCBlock)newTree.body;
|
|
unenterScanner.scan(body.stats);
|
|
localEnv.info.scope.leave();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitNewClass(JCNewClass tree) {
|
|
//do nothing
|
|
}
|
|
|
|
@Override
|
|
public void visitApply(JCMethodInvocation tree) {
|
|
//do nothing
|
|
}
|
|
|
|
@Override
|
|
public void visitReference(JCMemberReference tree) {
|
|
Check.CheckContext checkContext = resultInfo.checkContext;
|
|
Type pt = resultInfo.pt;
|
|
if (!inferenceContext.inferencevars.contains(pt)) {
|
|
try {
|
|
types.findDescriptorType(pt);
|
|
} catch (Types.FunctionDescriptorLookupError ex) {
|
|
checkContext.report(null, ex.getDiagnostic());
|
|
}
|
|
Env<AttrContext> localEnv = env.dup(tree);
|
|
JCExpression exprTree = (JCExpression)attribSpeculative(tree.getQualifierExpression(), localEnv,
|
|
attr.memberReferenceQualifierResult(tree));
|
|
ListBuffer<Type> argtypes = new ListBuffer<>();
|
|
for (Type t : types.findDescriptorType(pt).getParameterTypes()) {
|
|
argtypes.append(Type.noType);
|
|
}
|
|
JCMemberReference mref2 = new TreeCopier<Void>(make).copy(tree);
|
|
mref2.expr = exprTree;
|
|
Symbol lookupSym =
|
|
rs.resolveMemberReferenceByArity(localEnv, mref2, exprTree.type,
|
|
tree.name, argtypes.toList(), inferenceContext);
|
|
switch (lookupSym.kind) {
|
|
//note: as argtypes are erroneous types, type-errors must
|
|
//have been caused by arity mismatch
|
|
case Kinds.ABSENT_MTH:
|
|
case Kinds.WRONG_MTH:
|
|
case Kinds.WRONG_MTHS:
|
|
case Kinds.WRONG_STATICNESS:
|
|
checkContext.report(tree, diags.fragment("incompatible.arg.types.in.mref"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This visitor looks for return statements, its analysis will determine if
|
|
* a lambda body is void or value compatible. We must analyze return
|
|
* statements contained in the lambda body only, thus any return statement
|
|
* contained in an inner class or inner lambda body, should be ignored.
|
|
*/
|
|
class LambdaBodyStructChecker extends TreeScanner {
|
|
boolean isVoidCompatible = true;
|
|
boolean isPotentiallyValueCompatible = true;
|
|
|
|
@Override
|
|
public void visitClassDef(JCClassDecl tree) {
|
|
// do nothing
|
|
}
|
|
|
|
@Override
|
|
public void visitLambda(JCLambda tree) {
|
|
// do nothing
|
|
}
|
|
|
|
@Override
|
|
public void visitNewClass(JCNewClass tree) {
|
|
// do nothing
|
|
}
|
|
|
|
@Override
|
|
public void visitReturn(JCReturn tree) {
|
|
if (tree.expr != null) {
|
|
isVoidCompatible = false;
|
|
} else {
|
|
isPotentiallyValueCompatible = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** an empty deferred attribution context - all methods throw exceptions */
|
|
final DeferredAttrContext emptyDeferredAttrContext;
|
|
|
|
/**
|
|
* Map a list of types possibly containing one or more deferred types
|
|
* into a list of ordinary types. Each deferred type D is mapped into a type T,
|
|
* where T is computed by retrieving the type that has already been
|
|
* computed for D during a previous deferred attribution round of the given kind.
|
|
*/
|
|
class DeferredTypeMap extends Type.Mapping {
|
|
|
|
DeferredAttrContext deferredAttrContext;
|
|
|
|
protected DeferredTypeMap(AttrMode mode, Symbol msym, MethodResolutionPhase phase) {
|
|
super(String.format("deferredTypeMap[%s]", mode));
|
|
this.deferredAttrContext = new DeferredAttrContext(mode, msym, phase,
|
|
infer.emptyContext, emptyDeferredAttrContext, types.noWarnings);
|
|
}
|
|
|
|
@Override
|
|
public Type apply(Type t) {
|
|
if (!t.hasTag(DEFERRED)) {
|
|
return t.map(this);
|
|
} else {
|
|
DeferredType dt = (DeferredType)t;
|
|
return typeOf(dt);
|
|
}
|
|
}
|
|
|
|
protected Type typeOf(DeferredType dt) {
|
|
switch (deferredAttrContext.mode) {
|
|
case CHECK:
|
|
return dt.tree.type == null ? Type.noType : dt.tree.type;
|
|
case SPECULATIVE:
|
|
return dt.speculativeType(deferredAttrContext.msym, deferredAttrContext.phase);
|
|
}
|
|
Assert.error();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Specialized recovery deferred mapping.
|
|
* Each deferred type D is mapped into a type T, where T is computed either by
|
|
* (i) retrieving the type that has already been computed for D during a previous
|
|
* attribution round (as before), or (ii) by synthesizing a new type R for D
|
|
* (the latter step is useful in a recovery scenario).
|
|
*/
|
|
public class RecoveryDeferredTypeMap extends DeferredTypeMap {
|
|
|
|
public RecoveryDeferredTypeMap(AttrMode mode, Symbol msym, MethodResolutionPhase phase) {
|
|
super(mode, msym, phase != null ? phase : MethodResolutionPhase.BOX);
|
|
}
|
|
|
|
@Override
|
|
protected Type typeOf(DeferredType dt) {
|
|
Type owntype = super.typeOf(dt);
|
|
return owntype == Type.noType ?
|
|
recover(dt) : owntype;
|
|
}
|
|
|
|
/**
|
|
* Synthesize a type for a deferred type that hasn't been previously
|
|
* reduced to an ordinary type. Functional deferred types and conditionals
|
|
* are mapped to themselves, in order to have a richer diagnostic
|
|
* representation. Remaining deferred types are attributed using
|
|
* a default expected type (j.l.Object).
|
|
*/
|
|
private Type recover(DeferredType dt) {
|
|
dt.check(attr.new RecoveryInfo(deferredAttrContext) {
|
|
@Override
|
|
protected Type check(DiagnosticPosition pos, Type found) {
|
|
return chk.checkNonVoid(pos, super.check(pos, found));
|
|
}
|
|
});
|
|
return super.apply(dt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A special tree scanner that would only visit portions of a given tree.
|
|
* The set of nodes visited by the scanner can be customized at construction-time.
|
|
*/
|
|
abstract static class FilterScanner extends TreeScanner {
|
|
|
|
final Filter<JCTree> treeFilter;
|
|
|
|
FilterScanner(final Set<JCTree.Tag> validTags) {
|
|
this.treeFilter = new Filter<JCTree>() {
|
|
public boolean accepts(JCTree t) {
|
|
return validTags.contains(t.getTag());
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public void scan(JCTree tree) {
|
|
if (tree != null) {
|
|
if (treeFilter.accepts(tree)) {
|
|
super.scan(tree);
|
|
} else {
|
|
skip(tree);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* handler that is executed when a node has been discarded
|
|
*/
|
|
void skip(JCTree tree) {}
|
|
}
|
|
|
|
/**
|
|
* A tree scanner suitable for visiting the target-type dependent nodes of
|
|
* a given argument expression.
|
|
*/
|
|
static class PolyScanner extends FilterScanner {
|
|
|
|
PolyScanner() {
|
|
super(EnumSet.of(CONDEXPR, PARENS, LAMBDA, REFERENCE));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A tree scanner suitable for visiting the target-type dependent nodes nested
|
|
* within a lambda expression body.
|
|
*/
|
|
static class LambdaReturnScanner extends FilterScanner {
|
|
|
|
LambdaReturnScanner() {
|
|
super(EnumSet.of(BLOCK, CASE, CATCH, DOLOOP, FOREACHLOOP,
|
|
FORLOOP, IF, RETURN, SYNCHRONIZED, SWITCH, TRY, WHILELOOP));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This visitor is used to check that structural expressions conform
|
|
* to their target - this step is required as inference could end up
|
|
* inferring types that make some of the nested expressions incompatible
|
|
* with their corresponding instantiated target
|
|
*/
|
|
class CheckStuckPolicy extends PolyScanner implements DeferredStuckPolicy, Infer.FreeTypeListener {
|
|
|
|
Type pt;
|
|
Infer.InferenceContext inferenceContext;
|
|
Set<Type> stuckVars = new LinkedHashSet<Type>();
|
|
Set<Type> depVars = new LinkedHashSet<Type>();
|
|
|
|
@Override
|
|
public boolean isStuck() {
|
|
return !stuckVars.isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public Set<Type> stuckVars() {
|
|
return stuckVars;
|
|
}
|
|
|
|
@Override
|
|
public Set<Type> depVars() {
|
|
return depVars;
|
|
}
|
|
|
|
public CheckStuckPolicy(ResultInfo resultInfo, DeferredType dt) {
|
|
this.pt = resultInfo.pt;
|
|
this.inferenceContext = resultInfo.checkContext.inferenceContext();
|
|
scan(dt.tree);
|
|
if (!stuckVars.isEmpty()) {
|
|
resultInfo.checkContext.inferenceContext()
|
|
.addFreeTypeListener(List.from(stuckVars), this);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void typesInferred(InferenceContext inferenceContext) {
|
|
stuckVars.clear();
|
|
}
|
|
|
|
@Override
|
|
public void visitLambda(JCLambda tree) {
|
|
if (inferenceContext.inferenceVars().contains(pt)) {
|
|
stuckVars.add(pt);
|
|
}
|
|
if (!types.isFunctionalInterface(pt)) {
|
|
return;
|
|
}
|
|
Type descType = types.findDescriptorType(pt);
|
|
List<Type> freeArgVars = inferenceContext.freeVarsIn(descType.getParameterTypes());
|
|
if (tree.paramKind == JCLambda.ParameterKind.IMPLICIT &&
|
|
freeArgVars.nonEmpty()) {
|
|
stuckVars.addAll(freeArgVars);
|
|
depVars.addAll(inferenceContext.freeVarsIn(descType.getReturnType()));
|
|
}
|
|
scanLambdaBody(tree, descType.getReturnType());
|
|
}
|
|
|
|
@Override
|
|
public void visitReference(JCMemberReference tree) {
|
|
scan(tree.expr);
|
|
if (inferenceContext.inferenceVars().contains(pt)) {
|
|
stuckVars.add(pt);
|
|
return;
|
|
}
|
|
if (!types.isFunctionalInterface(pt)) {
|
|
return;
|
|
}
|
|
|
|
Type descType = types.findDescriptorType(pt);
|
|
List<Type> freeArgVars = inferenceContext.freeVarsIn(descType.getParameterTypes());
|
|
if (freeArgVars.nonEmpty() &&
|
|
tree.overloadKind == JCMemberReference.OverloadKind.OVERLOADED) {
|
|
stuckVars.addAll(freeArgVars);
|
|
depVars.addAll(inferenceContext.freeVarsIn(descType.getReturnType()));
|
|
}
|
|
}
|
|
|
|
void scanLambdaBody(JCLambda lambda, final Type pt) {
|
|
if (lambda.getBodyKind() == JCTree.JCLambda.BodyKind.EXPRESSION) {
|
|
Type prevPt = this.pt;
|
|
try {
|
|
this.pt = pt;
|
|
scan(lambda.body);
|
|
} finally {
|
|
this.pt = prevPt;
|
|
}
|
|
} else {
|
|
LambdaReturnScanner lambdaScanner = new LambdaReturnScanner() {
|
|
@Override
|
|
public void visitReturn(JCReturn tree) {
|
|
if (tree.expr != null) {
|
|
Type prevPt = CheckStuckPolicy.this.pt;
|
|
try {
|
|
CheckStuckPolicy.this.pt = pt;
|
|
CheckStuckPolicy.this.scan(tree.expr);
|
|
} finally {
|
|
CheckStuckPolicy.this.pt = prevPt;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
lambdaScanner.scan(lambda.body);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This visitor is used to check that structural expressions conform
|
|
* to their target - this step is required as inference could end up
|
|
* inferring types that make some of the nested expressions incompatible
|
|
* with their corresponding instantiated target
|
|
*/
|
|
class OverloadStuckPolicy extends CheckStuckPolicy implements DeferredStuckPolicy {
|
|
|
|
boolean stuck;
|
|
|
|
@Override
|
|
public boolean isStuck() {
|
|
return super.isStuck() || stuck;
|
|
}
|
|
|
|
public OverloadStuckPolicy(ResultInfo resultInfo, DeferredType dt) {
|
|
super(resultInfo, dt);
|
|
}
|
|
|
|
@Override
|
|
public void visitLambda(JCLambda tree) {
|
|
super.visitLambda(tree);
|
|
if (tree.paramKind == JCLambda.ParameterKind.IMPLICIT) {
|
|
stuck = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitReference(JCMemberReference tree) {
|
|
super.visitReference(tree);
|
|
if (tree.overloadKind == JCMemberReference.OverloadKind.OVERLOADED) {
|
|
stuck = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Does the argument expression {@code expr} need speculative type-checking?
|
|
*/
|
|
boolean isDeferred(Env<AttrContext> env, JCExpression expr) {
|
|
DeferredChecker dc = new DeferredChecker(env);
|
|
dc.scan(expr);
|
|
return dc.result.isPoly();
|
|
}
|
|
|
|
/**
|
|
* The kind of an argument expression. This is used by the analysis that
|
|
* determines as to whether speculative attribution is necessary.
|
|
*/
|
|
enum ArgumentExpressionKind {
|
|
|
|
/** kind that denotes poly argument expression */
|
|
POLY,
|
|
/** kind that denotes a standalone expression */
|
|
NO_POLY,
|
|
/** kind that denotes a primitive/boxed standalone expression */
|
|
PRIMITIVE;
|
|
|
|
/**
|
|
* Does this kind denote a poly argument expression
|
|
*/
|
|
public final boolean isPoly() {
|
|
return this == POLY;
|
|
}
|
|
|
|
/**
|
|
* Does this kind denote a primitive standalone expression
|
|
*/
|
|
public final boolean isPrimitive() {
|
|
return this == PRIMITIVE;
|
|
}
|
|
|
|
/**
|
|
* Compute the kind of a standalone expression of a given type
|
|
*/
|
|
static ArgumentExpressionKind standaloneKind(Type type, Types types) {
|
|
return types.unboxedTypeOrType(type).isPrimitive() ?
|
|
ArgumentExpressionKind.PRIMITIVE :
|
|
ArgumentExpressionKind.NO_POLY;
|
|
}
|
|
|
|
/**
|
|
* Compute the kind of a method argument expression given its symbol
|
|
*/
|
|
static ArgumentExpressionKind methodKind(Symbol sym, Types types) {
|
|
Type restype = sym.type.getReturnType();
|
|
if (sym.type.hasTag(FORALL) &&
|
|
restype.containsAny(((ForAll)sym.type).tvars)) {
|
|
return ArgumentExpressionKind.POLY;
|
|
} else {
|
|
return ArgumentExpressionKind.standaloneKind(restype, types);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tree scanner used for checking as to whether an argument expression
|
|
* requires speculative attribution
|
|
*/
|
|
final class DeferredChecker extends FilterScanner {
|
|
|
|
Env<AttrContext> env;
|
|
ArgumentExpressionKind result;
|
|
|
|
public DeferredChecker(Env<AttrContext> env) {
|
|
super(deferredCheckerTags);
|
|
this.env = env;
|
|
}
|
|
|
|
@Override
|
|
public void visitLambda(JCLambda tree) {
|
|
//a lambda is always a poly expression
|
|
result = ArgumentExpressionKind.POLY;
|
|
}
|
|
|
|
@Override
|
|
public void visitReference(JCMemberReference tree) {
|
|
//perform arity-based check
|
|
Env<AttrContext> localEnv = env.dup(tree);
|
|
JCExpression exprTree = (JCExpression)attribSpeculative(tree.getQualifierExpression(), localEnv,
|
|
attr.memberReferenceQualifierResult(tree));
|
|
JCMemberReference mref2 = new TreeCopier<Void>(make).copy(tree);
|
|
mref2.expr = exprTree;
|
|
Symbol res =
|
|
rs.getMemberReference(tree, localEnv, mref2,
|
|
exprTree.type, tree.name);
|
|
tree.sym = res;
|
|
if (res.kind >= Kinds.ERRONEOUS ||
|
|
res.type.hasTag(FORALL) ||
|
|
(res.flags() & Flags.VARARGS) != 0 ||
|
|
(TreeInfo.isStaticSelector(exprTree, tree.name.table.names) &&
|
|
exprTree.type.isRaw())) {
|
|
tree.overloadKind = JCMemberReference.OverloadKind.OVERLOADED;
|
|
} else {
|
|
tree.overloadKind = JCMemberReference.OverloadKind.UNOVERLOADED;
|
|
}
|
|
//a method reference is always a poly expression
|
|
result = ArgumentExpressionKind.POLY;
|
|
}
|
|
|
|
@Override
|
|
public void visitTypeCast(JCTypeCast tree) {
|
|
//a cast is always a standalone expression
|
|
result = ArgumentExpressionKind.NO_POLY;
|
|
}
|
|
|
|
@Override
|
|
public void visitConditional(JCConditional tree) {
|
|
scan(tree.truepart);
|
|
if (!result.isPrimitive()) {
|
|
result = ArgumentExpressionKind.POLY;
|
|
return;
|
|
}
|
|
scan(tree.falsepart);
|
|
result = reduce(ArgumentExpressionKind.PRIMITIVE);
|
|
}
|
|
|
|
@Override
|
|
public void visitNewClass(JCNewClass tree) {
|
|
result = (TreeInfo.isDiamond(tree) || attr.findDiamonds) ?
|
|
ArgumentExpressionKind.POLY : ArgumentExpressionKind.NO_POLY;
|
|
}
|
|
|
|
@Override
|
|
public void visitApply(JCMethodInvocation tree) {
|
|
Name name = TreeInfo.name(tree.meth);
|
|
|
|
//fast path
|
|
if (tree.typeargs.nonEmpty() ||
|
|
name == name.table.names._this ||
|
|
name == name.table.names._super) {
|
|
result = ArgumentExpressionKind.NO_POLY;
|
|
return;
|
|
}
|
|
|
|
//slow path
|
|
Symbol sym = quicklyResolveMethod(env, tree);
|
|
|
|
if (sym == null) {
|
|
result = ArgumentExpressionKind.POLY;
|
|
return;
|
|
}
|
|
|
|
result = analyzeCandidateMethods(sym, ArgumentExpressionKind.PRIMITIVE,
|
|
argumentKindAnalyzer);
|
|
}
|
|
//where
|
|
private boolean isSimpleReceiver(JCTree rec) {
|
|
switch (rec.getTag()) {
|
|
case IDENT:
|
|
return true;
|
|
case SELECT:
|
|
return isSimpleReceiver(((JCFieldAccess)rec).selected);
|
|
case TYPEAPPLY:
|
|
case TYPEARRAY:
|
|
return true;
|
|
case ANNOTATED_TYPE:
|
|
return isSimpleReceiver(((JCAnnotatedType)rec).underlyingType);
|
|
case APPLY:
|
|
return true;
|
|
case NEWCLASS:
|
|
JCNewClass nc = (JCNewClass) rec;
|
|
return nc.encl == null && nc.def == null && !TreeInfo.isDiamond(nc);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
private ArgumentExpressionKind reduce(ArgumentExpressionKind kind) {
|
|
return argumentKindAnalyzer.reduce(result, kind);
|
|
}
|
|
MethodAnalyzer<ArgumentExpressionKind> argumentKindAnalyzer =
|
|
new MethodAnalyzer<ArgumentExpressionKind>() {
|
|
@Override
|
|
public ArgumentExpressionKind process(MethodSymbol ms) {
|
|
return ArgumentExpressionKind.methodKind(ms, types);
|
|
}
|
|
@Override
|
|
public ArgumentExpressionKind reduce(ArgumentExpressionKind kind1,
|
|
ArgumentExpressionKind kind2) {
|
|
switch (kind1) {
|
|
case PRIMITIVE: return kind2;
|
|
case NO_POLY: return kind2.isPoly() ? kind2 : kind1;
|
|
case POLY: return kind1;
|
|
default:
|
|
Assert.error();
|
|
return null;
|
|
}
|
|
}
|
|
@Override
|
|
public boolean shouldStop(ArgumentExpressionKind result) {
|
|
return result.isPoly();
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public void visitLiteral(JCLiteral tree) {
|
|
Type litType = attr.litType(tree.typetag);
|
|
result = ArgumentExpressionKind.standaloneKind(litType, types);
|
|
}
|
|
|
|
@Override
|
|
void skip(JCTree tree) {
|
|
result = ArgumentExpressionKind.NO_POLY;
|
|
}
|
|
|
|
private Symbol quicklyResolveMethod(Env<AttrContext> env, final JCMethodInvocation tree) {
|
|
final JCExpression rec = tree.meth.hasTag(SELECT) ?
|
|
((JCFieldAccess)tree.meth).selected :
|
|
null;
|
|
|
|
if (rec != null && !isSimpleReceiver(rec)) {
|
|
return null;
|
|
}
|
|
|
|
Type site;
|
|
|
|
if (rec != null) {
|
|
switch (rec.getTag()) {
|
|
case APPLY:
|
|
Symbol recSym = quicklyResolveMethod(env, (JCMethodInvocation) rec);
|
|
if (recSym == null)
|
|
return null;
|
|
Symbol resolvedReturnType =
|
|
analyzeCandidateMethods(recSym, syms.errSymbol, returnSymbolAnalyzer);
|
|
if (resolvedReturnType == null)
|
|
return null;
|
|
site = resolvedReturnType.type;
|
|
break;
|
|
case NEWCLASS:
|
|
JCNewClass nc = (JCNewClass) rec;
|
|
site = attribSpeculative(nc.clazz, env, attr.unknownTypeExprInfo).type;
|
|
break;
|
|
default:
|
|
site = attribSpeculative(rec, env, attr.unknownTypeExprInfo).type;
|
|
break;
|
|
}
|
|
} else {
|
|
site = env.enclClass.sym.type;
|
|
}
|
|
|
|
while (site.hasTag(TYPEVAR)) {
|
|
site = site.getUpperBound();
|
|
}
|
|
|
|
site = types.capture(site);
|
|
|
|
List<Type> args = rs.dummyArgs(tree.args.length());
|
|
Name name = TreeInfo.name(tree.meth);
|
|
|
|
Resolve.LookupHelper lh = rs.new LookupHelper(name, site, args, List.<Type>nil(), MethodResolutionPhase.VARARITY) {
|
|
@Override
|
|
Symbol lookup(Env<AttrContext> env, MethodResolutionPhase phase) {
|
|
return rec == null ?
|
|
rs.findFun(env, name, argtypes, typeargtypes, phase.isBoxingRequired(), phase.isVarargsRequired()) :
|
|
rs.findMethod(env, site, name, argtypes, typeargtypes, phase.isBoxingRequired(), phase.isVarargsRequired(), false);
|
|
}
|
|
@Override
|
|
Symbol access(Env<AttrContext> env, DiagnosticPosition pos, Symbol location, Symbol sym) {
|
|
return sym;
|
|
}
|
|
};
|
|
|
|
return rs.lookupMethod(env, tree, site.tsym, rs.arityMethodCheck, lh);
|
|
}
|
|
//where:
|
|
MethodAnalyzer<Symbol> returnSymbolAnalyzer = new MethodAnalyzer<Symbol>() {
|
|
@Override
|
|
public Symbol process(MethodSymbol ms) {
|
|
ArgumentExpressionKind kind = ArgumentExpressionKind.methodKind(ms, types);
|
|
if (kind == ArgumentExpressionKind.POLY || ms.getReturnType().hasTag(TYPEVAR))
|
|
return null;
|
|
return ms.getReturnType().tsym;
|
|
}
|
|
@Override
|
|
public Symbol reduce(Symbol s1, Symbol s2) {
|
|
return s1 == syms.errSymbol ? s2 : s1 == s2 ? s1 : null;
|
|
}
|
|
@Override
|
|
public boolean shouldStop(Symbol result) {
|
|
return result == null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Process the result of Resolve.lookupMethod. If sym is a method symbol, the result of
|
|
* MethodAnalyzer.process is returned. If sym is an ambiguous symbol, all the candidate
|
|
* methods are inspected one by one, using MethodAnalyzer.process. The outcomes are
|
|
* reduced using MethodAnalyzer.reduce (using defaultValue as the first value over which
|
|
* the reduction runs). MethodAnalyzer.shouldStop can be used to stop the inspection early.
|
|
*/
|
|
<E> E analyzeCandidateMethods(Symbol sym, E defaultValue, MethodAnalyzer<E> analyzer) {
|
|
switch (sym.kind) {
|
|
case Kinds.MTH:
|
|
return analyzer.process((MethodSymbol) sym);
|
|
case Kinds.AMBIGUOUS:
|
|
Resolve.AmbiguityError err = (Resolve.AmbiguityError)sym.baseSymbol();
|
|
E res = defaultValue;
|
|
for (Symbol s : err.ambiguousSyms) {
|
|
if (s.kind == Kinds.MTH) {
|
|
res = analyzer.reduce(res, analyzer.process((MethodSymbol) s));
|
|
if (analyzer.shouldStop(res))
|
|
return res;
|
|
}
|
|
}
|
|
return res;
|
|
default:
|
|
return defaultValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Analyzer for methods - used by analyzeCandidateMethods. */
|
|
interface MethodAnalyzer<E> {
|
|
E process(MethodSymbol ms);
|
|
E reduce(E e1, E e2);
|
|
boolean shouldStop(E result);
|
|
}
|
|
|
|
//where
|
|
private EnumSet<JCTree.Tag> deferredCheckerTags =
|
|
EnumSet.of(LAMBDA, REFERENCE, PARENS, TYPECAST,
|
|
CONDEXPR, NEWCLASS, APPLY, LITERAL);
|
|
}
|