aoqi@0: /* aoqi@0: * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.tools.javac.comp; aoqi@0: aoqi@0: import com.sun.source.tree.LambdaExpressionTree.BodyKind; aoqi@0: import com.sun.tools.javac.code.*; aoqi@0: import com.sun.tools.javac.tree.*; aoqi@0: import com.sun.tools.javac.util.*; aoqi@0: import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; aoqi@0: import com.sun.tools.javac.code.Symbol.*; aoqi@0: import com.sun.tools.javac.code.Type.*; aoqi@0: import com.sun.tools.javac.comp.Attr.ResultInfo; aoqi@0: import com.sun.tools.javac.comp.Infer.InferenceContext; aoqi@0: import com.sun.tools.javac.comp.Resolve.MethodResolutionPhase; aoqi@0: import com.sun.tools.javac.tree.JCTree.*; aoqi@0: aoqi@0: import java.util.ArrayList; aoqi@0: import java.util.Collections; aoqi@0: import java.util.EnumSet; aoqi@0: import java.util.LinkedHashMap; aoqi@0: import java.util.LinkedHashSet; aoqi@0: import java.util.Map; aoqi@0: import java.util.Set; aoqi@0: import java.util.WeakHashMap; aoqi@0: aoqi@0: import static com.sun.tools.javac.code.Kinds.VAL; aoqi@0: import static com.sun.tools.javac.code.TypeTag.*; aoqi@0: import static com.sun.tools.javac.tree.JCTree.Tag.*; aoqi@0: aoqi@0: /** aoqi@0: * This is an helper class that is used to perform deferred type-analysis. aoqi@0: * Each time a poly expression occurs in argument position, javac attributes it aoqi@0: * with a temporary 'deferred type' that is checked (possibly multiple times) aoqi@0: * against an expected formal type. aoqi@0: * aoqi@0: *

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