/* * 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; } // OPENJML - added this method to generally avoid using the constructor everywhere protected TreeCopier makeCopier(TreeMaker make) { return new TreeCopier<Object>(make); } /** * 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 = makeCopier(make).copy(tree); // OPENJML - changed to avoid using constructor directly 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 */ class DeferredChecker extends FilterScanner { // DRC - made not final 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 protected boolean isSimpleReceiver(JCTree rec) { // OpenJML- private to protected 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 && rec.getTag() != null) { // OpenJML - added the second guard switch (rec.getTag()) { case APPLY: if (((JCMethodInvocation)rec).meth == null) return null; // OPENJML - added to handle JML constucts 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); }