/*
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javac.comp;
import com.sun.tools.javac.code.Source.Feature;
import com.sun.tools.javac.code.Type.UndetVar.UndetVarListener;
import com.sun.tools.javac.code.Types.TypeMapping;
import com.sun.tools.javac.comp.Attr.CheckMode;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import com.sun.tools.javac.resources.CompilerProperties.Notes;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCTypeCast;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.GraphUtils.DottableNode;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.JCDiagnostic.Fragment;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Type.*;
import com.sun.tools.javac.code.Type.UndetVar.InferenceBound;
import com.sun.tools.javac.code.Symbol.*;
import com.sun.tools.javac.comp.DeferredAttr.AttrMode;
import com.sun.tools.javac.comp.DeferredAttr.DeferredAttrContext;
import com.sun.tools.javac.comp.Infer.GraphSolver.InferenceGraph;
import com.sun.tools.javac.comp.Infer.GraphSolver.InferenceGraph.Node;
import com.sun.tools.javac.comp.Resolve.InapplicableMethodException;
import com.sun.tools.javac.comp.Resolve.VerboseResolutionMode;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import static com.sun.tools.javac.code.TypeTag.*;
Helper class for type parameter inference, used by the attribution phase.
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.
/** Helper class for type parameter inference, used by the attribution phase.
*
* <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 Infer {
protected static final Context.Key<Infer> inferKey = new Context.Key<>();
Resolve rs;
Check chk;
Symtab syms;
Types types;
JCDiagnostic.Factory diags;
Log log;
should the graph solver be used? /** should the graph solver be used? */
boolean allowGraphInference;
folder in which the inference dependency graphs should be written.
/**
* folder in which the inference dependency graphs should be written.
*/
private final String dependenciesFolder;
List of graphs awaiting to be dumped to a file.
/**
* List of graphs awaiting to be dumped to a file.
*/
private List<String> pendingGraphs;
public static Infer instance(Context context) {
Infer instance = context.get(inferKey);
if (instance == null)
instance = new Infer(context);
return instance;
}
protected Infer(Context context) {
context.put(inferKey, this);
rs = Resolve.instance(context);
chk = Check.instance(context);
syms = Symtab.instance(context);
types = Types.instance(context);
diags = JCDiagnostic.Factory.instance(context);
log = Log.instance(context);
Options options = Options.instance(context);
Source source = Source.instance(context);
allowGraphInference = Feature.GRAPH_INFERENCE.allowedInSource(source)
&& options.isUnset("useLegacyInference");
dependenciesFolder = options.get("debug.dumpInferenceGraphsTo");
pendingGraphs = List.nil();
emptyContext = new InferenceContext(this, List.nil());
}
A value for prototypes that admit any type, including polymorphic ones. /** A value for prototypes that admit any type, including polymorphic ones. */
public static final Type anyPoly = new JCNoType();
This exception class is design to store a list of diagnostics corresponding
to inference errors that can arise during a method applicability check.
/**
* This exception class is design to store a list of diagnostics corresponding
* to inference errors that can arise during a method applicability check.
*/
public static class InferenceException extends InapplicableMethodException {
private static final long serialVersionUID = 0;
List<JCDiagnostic> messages = List.nil();
InferenceException() {
super(null);
}
@Override
public JCDiagnostic getDiagnostic() {
return messages.head;
}
}
InferenceException error(JCDiagnostic diag) {
InferenceException result = new InferenceException();
if (diag != null) {
result.messages = result.messages.append(diag);
}
return result;
}
// <editor-fold defaultstate="collapsed" desc="Inference routines">
Main inference entry point - instantiate a generic method type
using given argument types and (possibly) an expected target-type.
/**
* Main inference entry point - instantiate a generic method type
* using given argument types and (possibly) an expected target-type.
*/
Type instantiateMethod( Env<AttrContext> env,
List<Type> tvars,
MethodType mt,
Attr.ResultInfo resultInfo,
MethodSymbol msym,
List<Type> argtypes,
boolean allowBoxing,
boolean useVarargs,
Resolve.MethodResolutionContext resolveContext,
Warner warn) throws InferenceException {
//-System.err.println("instantiateMethod(" + tvars + ", " + mt + ", " + argtypes + ")"); //DEBUG
final InferenceContext inferenceContext = new InferenceContext(this, tvars); //B0
try {
DeferredAttr.DeferredAttrContext deferredAttrContext =
resolveContext.deferredAttrContext(msym, inferenceContext, resultInfo, warn);
resolveContext.methodCheck.argumentsAcceptable(env, deferredAttrContext, //B2
argtypes, mt.getParameterTypes(), warn);
if (allowGraphInference && resultInfo != null && resultInfo.pt == anyPoly) {
doIncorporation(inferenceContext, warn);
//we are inside method attribution - just return a partially inferred type
return new PartiallyInferredMethodType(mt, inferenceContext, env, warn);
} else if (allowGraphInference && resultInfo != null) {
//inject return constraints earlier
doIncorporation(inferenceContext, warn); //propagation
if (!warn.hasNonSilentLint(Lint.LintCategory.UNCHECKED)) {
boolean shouldPropagate = shouldPropagate(mt.getReturnType(), resultInfo, inferenceContext);
InferenceContext minContext = shouldPropagate ?
inferenceContext.min(roots(mt, deferredAttrContext), true, warn) :
inferenceContext;
Type newRestype = generateReturnConstraints(env.tree, resultInfo, //B3
mt, minContext);
mt = (MethodType)types.createMethodTypeWithReturn(mt, newRestype);
//propagate outwards if needed
if (shouldPropagate) {
//propagate inference context outwards and exit
minContext.dupTo(resultInfo.checkContext.inferenceContext());
deferredAttrContext.complete();
return mt;
}
}
}
deferredAttrContext.complete();
// minimize as yet undetermined type variables
if (allowGraphInference) {
inferenceContext.solve(warn);
} else {
inferenceContext.solveLegacy(true, warn, LegacyInferenceSteps.EQ_LOWER.steps); //minimizeInst
}
mt = (MethodType)inferenceContext.asInstType(mt);
if (!allowGraphInference &&
inferenceContext.restvars().nonEmpty() &&
resultInfo != null &&
!warn.hasNonSilentLint(Lint.LintCategory.UNCHECKED)) {
generateReturnConstraints(env.tree, resultInfo, mt, inferenceContext);
inferenceContext.solveLegacy(false, warn, LegacyInferenceSteps.EQ_UPPER.steps); //maximizeInst
mt = (MethodType)inferenceContext.asInstType(mt);
}
if (resultInfo != null && rs.verboseResolutionMode.contains(VerboseResolutionMode.DEFERRED_INST)) {
log.note(env.tree.pos, Notes.DeferredMethodInst(msym, mt, resultInfo.pt));
}
// return instantiated version of method type
return mt;
} finally {
if (resultInfo != null || !allowGraphInference) {
inferenceContext.notifyChange();
} else {
inferenceContext.notifyChange(inferenceContext.boundedVars());
}
if (resultInfo == null) {
/* if the is no result info then we can clear the capture types
* cache without affecting any result info check
*/
inferenceContext.captureTypeCache.clear();
}
dumpGraphsIfNeeded(env.tree, msym, resolveContext);
}
}
//where
private boolean shouldPropagate(Type restype, Attr.ResultInfo target, InferenceContext inferenceContext) {
return target.checkContext.inferenceContext() != emptyContext && //enclosing context is a generic method
inferenceContext.free(restype) && //return type contains inference vars
(!inferenceContext.inferencevars.contains(restype) || //no eager instantiation is required (as per 18.5.2)
!needsEagerInstantiation((UndetVar)inferenceContext.asUndetVar(restype), target.pt, inferenceContext));
}
private List<Type> roots(MethodType mt, DeferredAttrContext deferredAttrContext) {
if (deferredAttrContext != null && deferredAttrContext.mode == AttrMode.CHECK) {
ListBuffer<Type> roots = new ListBuffer<>();
roots.add(mt.getReturnType());
for (DeferredAttr.DeferredAttrNode n : deferredAttrContext.deferredAttrNodes) {
roots.addAll(n.deferredStuckPolicy.stuckVars());
roots.addAll(n.deferredStuckPolicy.depVars());
}
List<Type> thrownVars = deferredAttrContext.inferenceContext.inferencevars.stream()
.filter(tv -> (tv.tsym.flags() & Flags.THROWS) != 0).collect(List.collector());
List<Type> result = roots.toList();
result = result.appendList(thrownVars.diff(result));
return result;
} else {
return List.of(mt.getReturnType());
}
}
A partially infered method/constructor type; such a type can be checked multiple times
against different targets.
/**
* A partially infered method/constructor type; such a type can be checked multiple times
* against different targets.
*/
public class PartiallyInferredMethodType extends MethodType {
public PartiallyInferredMethodType(MethodType mtype, InferenceContext inferenceContext, Env<AttrContext> env, Warner warn) {
super(mtype.getParameterTypes(), mtype.getReturnType(), mtype.getThrownTypes(), mtype.tsym);
this.inferenceContext = inferenceContext;
this.env = env;
this.warn = warn;
}
The inference context. /** The inference context. */
final InferenceContext inferenceContext;
The attribution environment. /** The attribution environment. */
Env<AttrContext> env;
The warner. /** The warner. */
final Warner warn;
@Override
public boolean isPartial() {
return true;
}
Checks this type against a target; this means generating return type constraints, solve
and then roll back the results (to avoid poolluting the context).
/**
* Checks this type against a target; this means generating return type constraints, solve
* and then roll back the results (to avoid poolluting the context).
*/
Type check(Attr.ResultInfo resultInfo) {
Warner noWarnings = new Warner(null);
List<Type> saved_undet = null;
try {
/** we need to save the inference context before generating target type constraints.
* This constraints may pollute the inference context and make it useless in case we
* need to use it several times: with several targets.
*/
saved_undet = inferenceContext.save();
boolean unchecked = warn.hasNonSilentLint(Lint.LintCategory.UNCHECKED);
if (!unchecked) {
boolean shouldPropagate = shouldPropagate(getReturnType(), resultInfo, inferenceContext);
InferenceContext minContext = shouldPropagate ?
inferenceContext.min(roots(asMethodType(), null), false, warn) :
inferenceContext;
MethodType other = (MethodType)minContext.update(asMethodType());
Type newRestype = generateReturnConstraints(env.tree, resultInfo, //B3
other, minContext);
if (shouldPropagate) {
//propagate inference context outwards and exit
minContext.dupTo(resultInfo.checkContext.inferenceContext(),
resultInfo.checkContext.deferredAttrContext().insideOverloadPhase());
return newRestype;
}
}
inferenceContext.solve(noWarnings);
Type ret = inferenceContext.asInstType(this).getReturnType();
if (unchecked) {
//inline logic from Attr.checkMethod - if unchecked conversion was required, erase
//return type _after_ resolution, and check against target
ret = types.erasure(ret);
}
return resultInfo.check(env.tree, ret);
} catch (InferenceException ex) {
resultInfo.checkContext.report(null, ex.getDiagnostic());
Assert.error(); //cannot get here (the above should throw)
return null;
} finally {
if (saved_undet != null) {
inferenceContext.rollback(saved_undet);
}
}
}
}
private void dumpGraphsIfNeeded(DiagnosticPosition pos, Symbol msym, Resolve.MethodResolutionContext rsContext) {
int round = 0;
try {
for (String graph : pendingGraphs.reverse()) {
Assert.checkNonNull(dependenciesFolder);
Name name = msym.name == msym.name.table.names.init ?
msym.owner.name : msym.name;
String filename = String.format("%s@%s[mode=%s,step=%s]_%d.dot",
name,
pos.getStartPosition(),
rsContext.attrMode(),
rsContext.step,
round);
Path dotFile = Paths.get(dependenciesFolder, filename);
try (Writer w = Files.newBufferedWriter(dotFile)) {
w.append(graph);
}
round++;
}
} catch (IOException ex) {
Assert.error("Error occurred when dumping inference graph: " + ex.getMessage());
} finally {
pendingGraphs = List.nil();
}
}
Generate constraints from the generic method's return type. If the method
call occurs in a context where a type T is expected, use the expected
type to derive more constraints on the generic method inference variables.
/**
* Generate constraints from the generic method's return type. If the method
* call occurs in a context where a type T is expected, use the expected
* type to derive more constraints on the generic method inference variables.
*/
Type generateReturnConstraints(JCTree tree, Attr.ResultInfo resultInfo,
MethodType mt, InferenceContext inferenceContext) {
InferenceContext rsInfoInfContext = resultInfo.checkContext.inferenceContext();
Type from = mt.getReturnType();
if (mt.getReturnType().containsAny(inferenceContext.inferencevars) &&
rsInfoInfContext != emptyContext) {
from = types.capture(from);
//add synthetic captured ivars
for (Type t : from.getTypeArguments()) {
if (t.hasTag(TYPEVAR) && ((TypeVar)t).isCaptured()) {
inferenceContext.addVar((TypeVar)t);
}
}
}
Type qtype = inferenceContext.asUndetVar(from);
Type to = resultInfo.pt;
if (qtype.hasTag(VOID)) {
to = syms.voidType;
} else if (to.hasTag(NONE)) {
to = from.isPrimitive() ? from : syms.objectType;
} else if (qtype.hasTag(UNDETVAR)) {
if (needsEagerInstantiation((UndetVar)qtype, to, inferenceContext) &&
(allowGraphInference || !to.isPrimitive())) {
to = generateReferenceToTargetConstraint(tree, (UndetVar)qtype, to, resultInfo, inferenceContext);
}
} else if (rsInfoInfContext.free(resultInfo.pt)) {
//propagation - cache captured vars
qtype = inferenceContext.asUndetVar(rsInfoInfContext.cachedCapture(tree, from, !resultInfo.checkMode.updateTreeType()));
}
Assert.check(allowGraphInference || !rsInfoInfContext.free(to),
"legacy inference engine cannot handle constraints on both sides of a subtyping assertion");
//we need to skip capture?
Warner retWarn = new Warner();
if (!resultInfo.checkContext.compatible(qtype, rsInfoInfContext.asUndetVar(to), retWarn) ||
//unchecked conversion is not allowed in source 7 mode
(!allowGraphInference && retWarn.hasLint(Lint.LintCategory.UNCHECKED))) {
throw error(diags.fragment(Fragments.InferNoConformingInstanceExists(inferenceContext.restvars(), mt.getReturnType(), to)));
}
return from;
}
private boolean needsEagerInstantiation(UndetVar from, Type to, InferenceContext inferenceContext) {
if (to.isPrimitive()) {
/* T is a primitive type, and one of the primitive wrapper classes is an instantiation,
* upper bound, or lower bound for alpha in B2.
*/
for (Type t : from.getBounds(InferenceBound.values())) {
Type boundAsPrimitive = types.unboxedType(t);
if (boundAsPrimitive == null || boundAsPrimitive.hasTag(NONE)) {
continue;
}
return true;
}
return false;
}
Type captureOfTo = types.capture(to);
/* T is a reference type, but is not a wildcard-parameterized type, and either
*/
if (captureOfTo == to) { //not a wildcard parameterized type
/* i) B2 contains a bound of one of the forms alpha = S or S <: alpha,
* where S is a wildcard-parameterized type, or
*/
for (Type t : from.getBounds(InferenceBound.EQ, InferenceBound.LOWER)) {
Type captureOfBound = types.capture(t);
if (captureOfBound != t) {
return true;
}
}
/* ii) B2 contains two bounds of the forms S1 <: alpha and S2 <: alpha,
* where S1 and S2 have supertypes that are two different
* parameterizations of the same generic class or interface.
*/
for (Type aLowerBound : from.getBounds(InferenceBound.LOWER)) {
for (Type anotherLowerBound : from.getBounds(InferenceBound.LOWER)) {
if (aLowerBound != anotherLowerBound &&
!inferenceContext.free(aLowerBound) &&
!inferenceContext.free(anotherLowerBound) &&
commonSuperWithDiffParameterization(aLowerBound, anotherLowerBound)) {
return true;
}
}
}
}
/* T is a parameterization of a generic class or interface, G,
* and B2 contains a bound of one of the forms alpha = S or S <: alpha,
* where there exists no type of the form G<...> that is a
* supertype of S, but the raw type G is a supertype of S
*/
if (to.isParameterized()) {
for (Type t : from.getBounds(InferenceBound.EQ, InferenceBound.LOWER)) {
Type sup = types.asSuper(t, to.tsym);
if (sup != null && sup.isRaw()) {
return true;
}
}
}
return false;
}
private boolean commonSuperWithDiffParameterization(Type t, Type s) {
for (Pair<Type, Type> supers : getParameterizedSupers(t, s)) {
if (!types.isSameType(supers.fst, supers.snd)) return true;
}
return false;
}
private Type generateReferenceToTargetConstraint(JCTree tree, UndetVar from,
Type to, Attr.ResultInfo resultInfo,
InferenceContext inferenceContext) {
inferenceContext.solve(List.of(from.qtype), new Warner());
inferenceContext.notifyChange();
Type capturedType = resultInfo.checkContext.inferenceContext()
.cachedCapture(tree, from.getInst(), !resultInfo.checkMode.updateTreeType());
if (types.isConvertible(capturedType,
resultInfo.checkContext.inferenceContext().asUndetVar(to))) {
//effectively skip additional return-type constraint generation (compatibility)
return syms.objectType;
}
return to;
}
Infer cyclic inference variables as described in 15.12.2.8.
/**
* Infer cyclic inference variables as described in 15.12.2.8.
*/
void instantiateAsUninferredVars(List<Type> vars, InferenceContext inferenceContext) {
ListBuffer<Type> todo = new ListBuffer<>();
//step 1 - create fresh tvars
for (Type t : vars) {
UndetVar uv = (UndetVar)inferenceContext.asUndetVar(t);
List<Type> upperBounds = uv.getBounds(InferenceBound.UPPER);
if (Type.containsAny(upperBounds, vars)) {
TypeSymbol fresh_tvar = new TypeVariableSymbol(Flags.SYNTHETIC, uv.qtype.tsym.name, null, uv.qtype.tsym.owner);
fresh_tvar.type = new TypeVar(fresh_tvar, types.makeIntersectionType(uv.getBounds(InferenceBound.UPPER)), syms.botType);
todo.append(uv);
uv.setInst(fresh_tvar.type);
} else if (upperBounds.nonEmpty()) {
uv.setInst(types.glb(upperBounds));
} else {
uv.setInst(syms.objectType);
}
}
//step 2 - replace fresh tvars in their bounds
List<Type> formals = vars;
for (Type t : todo) {
UndetVar uv = (UndetVar)t;
TypeVar ct = (TypeVar)uv.getInst();
ct.setUpperBound( types.glb(inferenceContext.asInstTypes(types.getBounds(ct))) );
if (ct.getUpperBound().isErroneous()) {
//report inference error if glb fails
reportBoundError(uv, InferenceBound.UPPER);
}
formals = formals.tail;
}
}
Compute a synthetic method type corresponding to the requested polymorphic
method signature. The target return type is computed from the immediately
enclosing scope surrounding the polymorphic-signature call.
/**
* Compute a synthetic method type corresponding to the requested polymorphic
* method signature. The target return type is computed from the immediately
* enclosing scope surrounding the polymorphic-signature call.
*/
Type instantiatePolymorphicSignatureInstance(Env<AttrContext> env,
MethodSymbol spMethod, // sig. poly. method or null if none
Resolve.MethodResolutionContext resolveContext,
List<Type> argtypes) {
final Type restype;
if (spMethod == null || types.isSameType(spMethod.getReturnType(), syms.objectType)) {
// The return type of the polymorphic signature is polymorphic,
// and is computed from the enclosing tree E, as follows:
// if E is a cast, then use the target type of the cast expression
// as a return type; if E is an expression statement, the return
// type is 'void'; otherwise
// the return type is simply 'Object'. A correctness check ensures
// that env.next refers to the lexically enclosing environment in
// which the polymorphic signature call environment is nested.
switch (env.next.tree.getTag()) {
case TYPECAST:
JCTypeCast castTree = (JCTypeCast)env.next.tree;
restype = (TreeInfo.skipParens(castTree.expr) == env.tree) ?
castTree.clazz.type :
syms.objectType;
break;
case EXEC:
JCTree.JCExpressionStatement execTree =
(JCTree.JCExpressionStatement)env.next.tree;
restype = (TreeInfo.skipParens(execTree.expr) == env.tree) ?
syms.voidType :
syms.objectType;
break;
default:
restype = syms.objectType;
}
} else {
// The return type of the polymorphic signature is fixed
// (not polymorphic)
restype = spMethod.getReturnType();
}
List<Type> paramtypes = argtypes.map(new ImplicitArgType(spMethod, resolveContext.step));
List<Type> exType = spMethod != null ?
spMethod.getThrownTypes() :
List.of(syms.throwableType); // make it throw all exceptions
MethodType mtype = new MethodType(paramtypes,
restype,
exType,
syms.methodClass);
return mtype;
}
//where
class ImplicitArgType extends DeferredAttr.DeferredTypeMap {
public ImplicitArgType(Symbol msym, Resolve.MethodResolutionPhase phase) {
(rs.deferredAttr).super(AttrMode.SPECULATIVE, msym, phase);
}
@Override
public Type visitClassType(ClassType t, Void aVoid) {
return types.erasure(t);
}
@Override
public Type visitType(Type t, Void _unused) {
if (t.hasTag(DEFERRED)) {
return visit(super.visitType(t, null));
} else if (t.hasTag(BOT))
// nulls type as the marker type Null (which has no instances)
// infer as java.lang.Void for now
t = types.boxedClass(syms.voidType).type;
return t;
}
}
TypeMapping<Void> fromTypeVarFun = new StructuralTypeMapping<Void>() {
@Override
public Type visitTypeVar(TypeVar tv, Void aVoid) {
UndetVar uv = new UndetVar(tv, incorporationEngine(), types);
if ((tv.tsym.flags() & Flags.THROWS) != 0) {
uv.setThrow();
}
return uv;
}
};
This method is used to infer a suitable target SAM in case the original
SAM type contains one or more wildcards. An inference process is applied
so that wildcard bounds, as well as explicit lambda/method ref parameters
(where applicable) are used to constraint the solution.
/**
* This method is used to infer a suitable target SAM in case the original
* SAM type contains one or more wildcards. An inference process is applied
* so that wildcard bounds, as well as explicit lambda/method ref parameters
* (where applicable) are used to constraint the solution.
*/
public Type instantiateFunctionalInterface(DiagnosticPosition pos, Type funcInterface,
List<Type> paramTypes, Check.CheckContext checkContext) {
if (types.capture(funcInterface) == funcInterface) {
//if capture doesn't change the type then return the target unchanged
//(this means the target contains no wildcards!)
return funcInterface;
} else {
Type formalInterface = funcInterface.tsym.type;
InferenceContext funcInterfaceContext =
new InferenceContext(this, funcInterface.tsym.type.getTypeArguments());
Assert.check(paramTypes != null);
//get constraints from explicit params (this is done by
//checking that explicit param types are equal to the ones
//in the functional interface descriptors)
List<Type> descParameterTypes = types.findDescriptorType(formalInterface).getParameterTypes();
if (descParameterTypes.size() != paramTypes.size()) {
checkContext.report(pos, diags.fragment(Fragments.IncompatibleArgTypesInLambda));
return types.createErrorType(funcInterface);
}
for (Type p : descParameterTypes) {
if (!types.isSameType(funcInterfaceContext.asUndetVar(p), paramTypes.head)) {
checkContext.report(pos, diags.fragment(Fragments.NoSuitableFunctionalIntfInst(funcInterface)));
return types.createErrorType(funcInterface);
}
paramTypes = paramTypes.tail;
}
List<Type> actualTypeargs = funcInterface.getTypeArguments();
for (Type t : funcInterfaceContext.undetvars) {
UndetVar uv = (UndetVar)t;
Optional<Type> inst = uv.getBounds(InferenceBound.EQ).stream()
.filter(b -> !b.containsAny(formalInterface.getTypeArguments())).findFirst();
uv.setInst(inst.orElse(actualTypeargs.head));
actualTypeargs = actualTypeargs.tail;
}
Type owntype = funcInterfaceContext.asInstType(formalInterface);
if (!chk.checkValidGenericType(owntype)) {
//if the inferred functional interface type is not well-formed,
//or if it's not a subtype of the original target, issue an error
checkContext.report(pos, diags.fragment(Fragments.NoSuitableFunctionalIntfInst(funcInterface)));
}
//propagate constraints as per JLS 18.2.1
checkContext.compatible(owntype, funcInterface, types.noWarnings);
return owntype;
}
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Incorporation">
This class is the root of all incorporation actions.
/**
* This class is the root of all incorporation actions.
*/
public abstract class IncorporationAction {
UndetVar uv;
Type t;
IncorporationAction(UndetVar uv, Type t) {
this.uv = uv;
this.t = t;
}
public abstract IncorporationAction dup(UndetVar that);
Incorporation action entry-point. Subclasses should define the logic associated with
this incorporation action.
/**
* Incorporation action entry-point. Subclasses should define the logic associated with
* this incorporation action.
*/
abstract void apply(InferenceContext ic, Warner warn);
Helper function: perform subtyping through incorporation cache.
/**
* Helper function: perform subtyping through incorporation cache.
*/
boolean isSubtype(Type s, Type t, Warner warn) {
return doIncorporationOp(IncorporationBinaryOpKind.IS_SUBTYPE, s, t, warn);
}
Helper function: perform type-equivalence through incorporation cache.
/**
* Helper function: perform type-equivalence through incorporation cache.
*/
boolean isSameType(Type s, Type t) {
return doIncorporationOp(IncorporationBinaryOpKind.IS_SAME_TYPE, s, t, null);
}
@Override
public String toString() {
return String.format("%s[undet=%s,t=%s]", getClass().getSimpleName(), uv.qtype, t);
}
}
Bound-check incorporation action. A newly added bound is checked against existing bounds,
to verify its compatibility; each bound is checked using either subtyping or type equivalence.
/**
* Bound-check incorporation action. A newly added bound is checked against existing bounds,
* to verify its compatibility; each bound is checked using either subtyping or type equivalence.
*/
class CheckBounds extends IncorporationAction {
InferenceBound from;
BiFunction<InferenceContext, Type, Type> typeFunc;
BiPredicate<InferenceContext, Type> optFilter;
CheckBounds(UndetVar uv, Type t, InferenceBound from) {
this(uv, t, InferenceContext::asUndetVar, null, from);
}
CheckBounds(UndetVar uv, Type t, BiFunction<InferenceContext, Type, Type> typeFunc,
BiPredicate<InferenceContext, Type> typeFilter, InferenceBound from) {
super(uv, t);
this.from = from;
this.typeFunc = typeFunc;
this.optFilter = typeFilter;
}
@Override
public IncorporationAction dup(UndetVar that) {
return new CheckBounds(that, t, typeFunc, optFilter, from);
}
@Override
void apply(InferenceContext inferenceContext, Warner warn) {
t = typeFunc.apply(inferenceContext, t);
if (optFilter != null && optFilter.test(inferenceContext, t)) return;
for (InferenceBound to : boundsToCheck()) {
for (Type b : uv.getBounds(to)) {
b = typeFunc.apply(inferenceContext, b);
if (optFilter != null && optFilter.test(inferenceContext, b)) continue;
boolean success = checkBound(t, b, from, to, warn);
if (!success) {
report(from, to);
}
}
}
}
The list of bound kinds to be checked.
/**
* The list of bound kinds to be checked.
*/
EnumSet<InferenceBound> boundsToCheck() {
return (from == InferenceBound.EQ) ?
EnumSet.allOf(InferenceBound.class) :
EnumSet.complementOf(EnumSet.of(from));
}
Is source type 's' compatible with target type 't' given source and target bound kinds?
/**
* Is source type 's' compatible with target type 't' given source and target bound kinds?
*/
boolean checkBound(Type s, Type t, InferenceBound ib_s, InferenceBound ib_t, Warner warn) {
if (ib_s.lessThan(ib_t)) {
return isSubtype(s, t, warn);
} else if (ib_t.lessThan(ib_s)) {
return isSubtype(t, s, warn);
} else {
return isSameType(s, t);
}
}
Report a bound check error.
/**
* Report a bound check error.
*/
void report(InferenceBound from, InferenceBound to) {
//this is a workaround to preserve compatibility with existing messages
if (from == to) {
reportBoundError(uv, from);
} else if (from == InferenceBound.LOWER || to == InferenceBound.EQ) {
reportBoundError(uv, to, from);
} else {
reportBoundError(uv, from, to);
}
}
@Override
public String toString() {
return String.format("%s[undet=%s,t=%s,bound=%s]", getClass().getSimpleName(), uv.qtype, t, from);
}
}
Custom check executed by the legacy incorporation engine. Newly added bounds are checked
against existing eq bounds.
/**
* Custom check executed by the legacy incorporation engine. Newly added bounds are checked
* against existing eq bounds.
*/
class EqCheckLegacy extends CheckBounds {
EqCheckLegacy(UndetVar uv, Type t, InferenceBound from) {
super(uv, t, InferenceContext::asInstType, InferenceContext::free, from);
}
@Override
public IncorporationAction dup(UndetVar that) {
return new EqCheckLegacy(that, t, from);
}
@Override
EnumSet<InferenceBound> boundsToCheck() {
return (from == InferenceBound.EQ) ?
EnumSet.allOf(InferenceBound.class) :
EnumSet.of(InferenceBound.EQ);
}
}
Check that the inferred type conforms to all bounds.
/**
* Check that the inferred type conforms to all bounds.
*/
class CheckInst extends CheckBounds {
EnumSet<InferenceBound> to;
CheckInst(UndetVar uv, InferenceBound ib, InferenceBound... rest) {
this(uv, EnumSet.of(ib, rest));
}
CheckInst(UndetVar uv, EnumSet<InferenceBound> to) {
super(uv, uv.getInst(), InferenceBound.EQ);
this.to = to;
}
@Override
public IncorporationAction dup(UndetVar that) {
return new CheckInst(that, to);
}
@Override
EnumSet<InferenceBound> boundsToCheck() {
return to;
}
@Override
void report(InferenceBound from, InferenceBound to) {
reportInstError(uv, to);
}
}
Replace undetvars in bounds and check that the inferred type conforms to all bounds.
/**
* Replace undetvars in bounds and check that the inferred type conforms to all bounds.
*/
class SubstBounds extends CheckInst {
SubstBounds(UndetVar uv) {
super(uv, InferenceBound.LOWER, InferenceBound.EQ, InferenceBound.UPPER);
}
@Override
public IncorporationAction dup(UndetVar that) {
return new SubstBounds(that);
}
@Override
void apply(InferenceContext inferenceContext, Warner warn) {
for (Type undet : inferenceContext.undetvars) {
//we could filter out variables not mentioning uv2...
UndetVar uv2 = (UndetVar)undet;
uv2.substBounds(List.of(uv.qtype), List.of(uv.getInst()), types);
checkCompatibleUpperBounds(uv2, inferenceContext);
}
super.apply(inferenceContext, warn);
}
Make sure that the upper bounds we got so far lead to a solvable inference
variable by making sure that a glb exists.
/**
* Make sure that the upper bounds we got so far lead to a solvable inference
* variable by making sure that a glb exists.
*/
void checkCompatibleUpperBounds(UndetVar uv, InferenceContext inferenceContext) {
List<Type> hibounds =
Type.filter(uv.getBounds(InferenceBound.UPPER), new BoundFilter(inferenceContext));
final Type hb;
if (hibounds.isEmpty())
hb = syms.objectType;
else if (hibounds.tail.isEmpty())
hb = hibounds.head;
else
hb = types.glb(hibounds);
if (hb == null || hb.isErroneous())
reportBoundError(uv, InferenceBound.UPPER);
}
}
Perform pairwise comparison between common generic supertypes of two upper bounds.
/**
* Perform pairwise comparison between common generic supertypes of two upper bounds.
*/
class CheckUpperBounds extends IncorporationAction {
public CheckUpperBounds(UndetVar uv, Type t) {
super(uv, t);
}
@Override
public IncorporationAction dup(UndetVar that) {
return new CheckUpperBounds(that, t);
}
@Override
void apply(InferenceContext inferenceContext, Warner warn) {
List<Type> boundList = uv.getBounds(InferenceBound.UPPER).stream()
.collect(types.closureCollector(true, types::isSameType));
for (Type b2 : boundList) {
if (t == b2) continue;
/* This wildcard check is temporary workaround. This code may need to be
* revisited once spec bug JDK-7034922 is fixed.
*/
if (t != b2 && !t.hasTag(WILDCARD) && !b2.hasTag(WILDCARD)) {
for (Pair<Type, Type> commonSupers : getParameterizedSupers(t, b2)) {
List<Type> allParamsSuperBound1 = commonSupers.fst.allparams();
List<Type> allParamsSuperBound2 = commonSupers.snd.allparams();
while (allParamsSuperBound1.nonEmpty() && allParamsSuperBound2.nonEmpty()) {
//traverse the list of all params comparing them
if (!allParamsSuperBound1.head.hasTag(WILDCARD) &&
!allParamsSuperBound2.head.hasTag(WILDCARD)) {
if (!isSameType(inferenceContext.asUndetVar(allParamsSuperBound1.head),
inferenceContext.asUndetVar(allParamsSuperBound2.head))) {
reportBoundError(uv, InferenceBound.UPPER);
}
}
allParamsSuperBound1 = allParamsSuperBound1.tail;
allParamsSuperBound2 = allParamsSuperBound2.tail;
}
Assert.check(allParamsSuperBound1.isEmpty() && allParamsSuperBound2.isEmpty());
}
}
}
}
}
Perform propagation of bounds. Given a constraint of the kind alpha <: T
, three kind of propagation occur: T is copied into all matching bounds (i.e. lower/eq bounds) B of alpha such that B=beta (forward propagation)
if T=beta, matching bounds (i.e. upper bounds) of beta are copied into alpha (backwards propagation)
if T=beta, sets a symmetric bound on beta (i.e. beta :> alpha) (symmetric propagation)
/**
* Perform propagation of bounds. Given a constraint of the kind {@code alpha <: T}, three
* kind of propagation occur:
*
* <li>T is copied into all matching bounds (i.e. lower/eq bounds) B of alpha such that B=beta (forward propagation)</li>
* <li>if T=beta, matching bounds (i.e. upper bounds) of beta are copied into alpha (backwards propagation)</li>
* <li>if T=beta, sets a symmetric bound on beta (i.e. beta :> alpha) (symmetric propagation) </li>
*/
class PropagateBounds extends IncorporationAction {
InferenceBound ib;
public PropagateBounds(UndetVar uv, Type t, InferenceBound ib) {
super(uv, t);
this.ib = ib;
}
@Override
public IncorporationAction dup(UndetVar that) {
return new PropagateBounds(that, t, ib);
}
void apply(InferenceContext inferenceContext, Warner warner) {
Type undetT = inferenceContext.asUndetVar(t);
if (undetT.hasTag(UNDETVAR) && !((UndetVar)undetT).isCaptured()) {
UndetVar uv2 = (UndetVar)undetT;
//symmetric propagation
uv2.addBound(ib.complement(), uv, types);
//backwards propagation
for (InferenceBound ib2 : backwards()) {
for (Type b : uv2.getBounds(ib2)) {
uv.addBound(ib2, b, types);
}
}
}
//forward propagation
for (InferenceBound ib2 : forward()) {
for (Type l : uv.getBounds(ib2)) {
Type undet = inferenceContext.asUndetVar(l);
if (undet.hasTag(TypeTag.UNDETVAR) && !((UndetVar)undet).isCaptured()) {
UndetVar uv2 = (UndetVar)undet;
uv2.addBound(ib, inferenceContext.asInstType(t), types);
}
}
}
}
EnumSet<InferenceBound> forward() {
return (ib == InferenceBound.EQ) ?
EnumSet.of(InferenceBound.EQ) : EnumSet.complementOf(EnumSet.of(ib));
}
EnumSet<InferenceBound> backwards() {
return (ib == InferenceBound.EQ) ?
EnumSet.allOf(InferenceBound.class) : EnumSet.of(ib);
}
@Override
public String toString() {
return String.format("%s[undet=%s,t=%s,bound=%s]", getClass().getSimpleName(), uv.qtype, t, ib);
}
}
This class models an incorporation engine. The engine is responsible for listening to
changes in inference variables and register incorporation actions accordingly.
/**
* This class models an incorporation engine. The engine is responsible for listening to
* changes in inference variables and register incorporation actions accordingly.
*/
abstract class AbstractIncorporationEngine implements UndetVarListener {
@Override
public void varInstantiated(UndetVar uv) {
uv.incorporationActions.addFirst(new SubstBounds(uv));
}
@Override
public void varBoundChanged(UndetVar uv, InferenceBound ib, Type bound, boolean update) {
if (uv.isCaptured()) return;
uv.incorporationActions.addAll(getIncorporationActions(uv, ib, bound, update));
}
abstract List<IncorporationAction> getIncorporationActions(UndetVar uv, InferenceBound ib, Type t, boolean update);
}
A legacy incorporation engine. Used for source <= 7.
/**
* A legacy incorporation engine. Used for source <= 7.
*/
AbstractIncorporationEngine legacyEngine = new AbstractIncorporationEngine() {
List<IncorporationAction> getIncorporationActions(UndetVar uv, InferenceBound ib, Type t, boolean update) {
ListBuffer<IncorporationAction> actions = new ListBuffer<>();
Type inst = uv.getInst();
if (inst != null) {
actions.add(new CheckInst(uv, ib));
}
actions.add(new EqCheckLegacy(uv, t, ib));
return actions.toList();
}
};
The standard incorporation engine. Used for source >= 8.
/**
* The standard incorporation engine. Used for source >= 8.
*/
AbstractIncorporationEngine graphEngine = new AbstractIncorporationEngine() {
@Override
List<IncorporationAction> getIncorporationActions(UndetVar uv, InferenceBound ib, Type t, boolean update) {
ListBuffer<IncorporationAction> actions = new ListBuffer<>();
Type inst = uv.getInst();
if (inst != null) {
actions.add(new CheckInst(uv, ib));
}
actions.add(new CheckBounds(uv, t, ib));
if (update) {
return actions.toList();
}
if (ib == InferenceBound.UPPER) {
actions.add(new CheckUpperBounds(uv, t));
}
actions.add(new PropagateBounds(uv, t, ib));
return actions.toList();
}
};
Get the incorporation engine to be used in this compilation.
/**
* Get the incorporation engine to be used in this compilation.
*/
AbstractIncorporationEngine incorporationEngine() {
return allowGraphInference ? graphEngine : legacyEngine;
}
max number of incorporation rounds. /** max number of incorporation rounds. */
static final int MAX_INCORPORATION_STEPS = 10000;
Check bounds and perform incorporation.
/**
* Check bounds and perform incorporation.
*/
void doIncorporation(InferenceContext inferenceContext, Warner warn) throws InferenceException {
try {
boolean progress = true;
int round = 0;
while (progress && round < MAX_INCORPORATION_STEPS) {
progress = false;
for (Type t : inferenceContext.undetvars) {
UndetVar uv = (UndetVar)t;
if (!uv.incorporationActions.isEmpty()) {
progress = true;
uv.incorporationActions.removeFirst().apply(inferenceContext, warn);
}
}
round++;
}
} finally {
incorporationCache.clear();
}
}
/* If for two types t and s there is a least upper bound that contains
* parameterized types G1, G2 ... Gn, then there exists supertypes of 't' of the form
* G1<T1, ..., Tn>, G2<T1, ..., Tn>, ... Gn<T1, ..., Tn> and supertypes of 's' of the form
* G1<S1, ..., Sn>, G2<S1, ..., Sn>, ... Gn<S1, ..., Sn> which will be returned by this method.
* If no such common supertypes exists then an empty list is returned.
*
* As an example for the following input:
*
* t = java.util.ArrayList<java.lang.String>
* s = java.util.List<T>
*
* we get this ouput (singleton list):
*
* [Pair[java.util.List<java.lang.String>,java.util.List<T>]]
*/
private List<Pair<Type, Type>> getParameterizedSupers(Type t, Type s) {
Type lubResult = types.lub(t, s);
if (lubResult == syms.errType || lubResult == syms.botType) {
return List.nil();
}
List<Type> supertypesToCheck = lubResult.isIntersection() ?
((IntersectionClassType)lubResult).getComponents() :
List.of(lubResult);
ListBuffer<Pair<Type, Type>> commonSupertypes = new ListBuffer<>();
for (Type sup : supertypesToCheck) {
if (sup.isParameterized()) {
Type asSuperOfT = asSuper(t, sup);
Type asSuperOfS = asSuper(s, sup);
commonSupertypes.add(new Pair<>(asSuperOfT, asSuperOfS));
}
}
return commonSupertypes.toList();
}
//where
private Type asSuper(Type t, Type sup) {
return (sup.hasTag(ARRAY)) ?
new ArrayType(asSuper(types.elemtype(t), types.elemtype(sup)), syms.arrayClass) :
types.asSuper(t, sup.tsym);
}
boolean doIncorporationOp(IncorporationBinaryOpKind opKind, Type op1, Type op2, Warner warn) {
IncorporationBinaryOp newOp = new IncorporationBinaryOp(opKind, op1, op2);
Boolean res = incorporationCache.get(newOp);
if (res == null) {
incorporationCache.put(newOp, res = newOp.apply(warn));
}
return res;
}
Three kinds of basic operation are supported as part of an incorporation step:
(i) subtype check, (ii) same type check and (iii) bound addition (either
upper/lower/eq bound).
/**
* Three kinds of basic operation are supported as part of an incorporation step:
* (i) subtype check, (ii) same type check and (iii) bound addition (either
* upper/lower/eq bound).
*/
enum IncorporationBinaryOpKind {
IS_SUBTYPE() {
@Override
boolean apply(Type op1, Type op2, Warner warn, Types types) {
return types.isSubtypeUnchecked(op1, op2, warn);
}
},
IS_SAME_TYPE() {
@Override
boolean apply(Type op1, Type op2, Warner warn, Types types) {
return types.isSameType(op1, op2);
}
};
abstract boolean apply(Type op1, Type op2, Warner warn, Types types);
}
This class encapsulates a basic incorporation operation; incorporation
operations takes two type operands and a kind. Each operation performed
during an incorporation round is stored in a cache, so that operations
are not executed unnecessarily (which would potentially lead to adding
same bounds over and over).
/**
* This class encapsulates a basic incorporation operation; incorporation
* operations takes two type operands and a kind. Each operation performed
* during an incorporation round is stored in a cache, so that operations
* are not executed unnecessarily (which would potentially lead to adding
* same bounds over and over).
*/
class IncorporationBinaryOp {
IncorporationBinaryOpKind opKind;
Type op1;
Type op2;
IncorporationBinaryOp(IncorporationBinaryOpKind opKind, Type op1, Type op2) {
this.opKind = opKind;
this.op1 = op1;
this.op2 = op2;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof IncorporationBinaryOp)) {
return false;
} else {
IncorporationBinaryOp that = (IncorporationBinaryOp)o;
return opKind == that.opKind &&
types.isSameType(op1, that.op1) &&
types.isSameType(op2, that.op2);
}
}
@Override
public int hashCode() {
int result = opKind.hashCode();
result *= 127;
result += types.hashCode(op1);
result *= 127;
result += types.hashCode(op2);
return result;
}
boolean apply(Warner warn) {
return opKind.apply(op1, op2, warn, types);
}
}
an incorporation cache keeps track of all executed incorporation-related operations /** an incorporation cache keeps track of all executed incorporation-related operations */
Map<IncorporationBinaryOp, Boolean> incorporationCache = new HashMap<>();
protected static class BoundFilter implements Filter<Type> {
InferenceContext inferenceContext;
public BoundFilter(InferenceContext inferenceContext) {
this.inferenceContext = inferenceContext;
}
@Override
public boolean accepts(Type t) {
return !t.isErroneous() && !inferenceContext.free(t) &&
!t.hasTag(BOT);
}
}
Incorporation error: mismatch between inferred type and given bound.
/**
* Incorporation error: mismatch between inferred type and given bound.
*/
void reportInstError(UndetVar uv, InferenceBound ib) {
switch (ib) {
case EQ:
throw error(diags.fragment(Fragments.InferredDoNotConformToEqBounds(uv.getInst(), uv.getBounds(ib))));
case LOWER:
throw error(diags.fragment(Fragments.InferredDoNotConformToLowerBounds(uv.getInst(), uv.getBounds(ib))));
case UPPER:
throw error(diags.fragment(Fragments.InferredDoNotConformToUpperBounds(uv.getInst(), uv.getBounds(ib))));
}
}
Incorporation error: mismatch between two (or more) bounds of same kind.
/**
* Incorporation error: mismatch between two (or more) bounds of same kind.
*/
void reportBoundError(UndetVar uv, InferenceBound ib) {
switch (ib) {
case EQ:
throw error(diags.fragment(Fragments.IncompatibleEqBounds(uv.qtype, uv.getBounds(ib))));
case UPPER:
throw error(diags.fragment(Fragments.IncompatibleUpperBounds(uv.qtype, uv.getBounds(ib))));
case LOWER:
throw new AssertionError("this case shouldn't happen");
}
}
Incorporation error: mismatch between two (or more) bounds of different kinds.
/**
* Incorporation error: mismatch between two (or more) bounds of different kinds.
*/
void reportBoundError(UndetVar uv, InferenceBound ib1, InferenceBound ib2) {
throw error(diags.fragment(Fragments.IncompatibleBounds(
uv.qtype,
getBoundFragment(ib1, uv.getBounds(ib1)),
getBoundFragment(ib2, uv.getBounds(ib2)))));
}
Fragment getBoundFragment(InferenceBound ib, List<Type> types) {
switch (ib) {
case EQ: return Fragments.EqBounds(types);
case LOWER: return Fragments.LowerBounds(types);
case UPPER: return Fragments.UpperBounds(types);
}
throw new AssertionError("can't get to this place");
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Inference engine">
Graph inference strategy - act as an input to the inference solver; a strategy is
composed of two ingredients: (i) find a node to solve in the inference graph,
and (ii) tell th engine when we are done fixing inference variables
/**
* Graph inference strategy - act as an input to the inference solver; a strategy is
* composed of two ingredients: (i) find a node to solve in the inference graph,
* and (ii) tell th engine when we are done fixing inference variables
*/
interface GraphStrategy {
A NodeNotFoundException is thrown whenever an inference strategy fails
to pick the next node to solve in the inference graph.
/**
* A NodeNotFoundException is thrown whenever an inference strategy fails
* to pick the next node to solve in the inference graph.
*/
public static class NodeNotFoundException extends RuntimeException {
private static final long serialVersionUID = 0;
InferenceGraph graph;
public NodeNotFoundException(InferenceGraph graph) {
this.graph = graph;
}
}
Pick the next node (leaf) to solve in the graph
/**
* Pick the next node (leaf) to solve in the graph
*/
Node pickNode(InferenceGraph g) throws NodeNotFoundException;
Is this the last step?
/**
* Is this the last step?
*/
boolean done();
}
Simple solver strategy class that locates all leaves inside a graph
and picks the first leaf as the next node to solve
/**
* Simple solver strategy class that locates all leaves inside a graph
* and picks the first leaf as the next node to solve
*/
abstract class LeafSolver implements GraphStrategy {
public Node pickNode(InferenceGraph g) {
if (g.nodes.isEmpty()) {
//should not happen
throw new NodeNotFoundException(g);
}
return g.nodes.get(0);
}
}
This solver uses an heuristic to pick the best leaf - the heuristic
tries to select the node that has maximal probability to contain one
or more inference variables in a given list
/**
* This solver uses an heuristic to pick the best leaf - the heuristic
* tries to select the node that has maximal probability to contain one
* or more inference variables in a given list
*/
abstract class BestLeafSolver extends LeafSolver {
list of ivars of which at least one must be solved /** list of ivars of which at least one must be solved */
List<Type> varsToSolve;
BestLeafSolver(List<Type> varsToSolve) {
this.varsToSolve = varsToSolve;
}
Computes a path that goes from a given node to the leafs in the graph. Typically this will start from a node containing a variable in varsToSolve
. For any given path, the cost is computed as the total number of type-variables that should be eagerly instantiated across that path. /**
* Computes a path that goes from a given node to the leafs in the graph.
* Typically this will start from a node containing a variable in
* {@code varsToSolve}. For any given path, the cost is computed as the total
* number of type-variables that should be eagerly instantiated across that path.
*/
Pair<List<Node>, Integer> computeTreeToLeafs(Node n) {
Pair<List<Node>, Integer> cachedPath = treeCache.get(n);
if (cachedPath == null) {
//cache miss
if (n.isLeaf()) {
//if leaf, stop
cachedPath = new Pair<>(List.of(n), n.data.length());
} else {
//if non-leaf, proceed recursively
Pair<List<Node>, Integer> path = new Pair<>(List.of(n), n.data.length());
for (Node n2 : n.getAllDependencies()) {
if (n2 == n) continue;
Pair<List<Node>, Integer> subpath = computeTreeToLeafs(n2);
path = new Pair<>(path.fst.prependList(subpath.fst),
path.snd + subpath.snd);
}
cachedPath = path;
}
//save results in cache
treeCache.put(n, cachedPath);
}
return cachedPath;
}
cache used to avoid redundant computation of tree costs /** cache used to avoid redundant computation of tree costs */
final Map<Node, Pair<List<Node>, Integer>> treeCache = new HashMap<>();
constant value used to mark non-existent paths /** constant value used to mark non-existent paths */
final Pair<List<Node>, Integer> noPath = new Pair<>(null, Integer.MAX_VALUE);
Pick the leaf that minimize cost
/**
* Pick the leaf that minimize cost
*/
@Override
public Node pickNode(final InferenceGraph g) {
treeCache.clear(); //graph changes at every step - cache must be cleared
Pair<List<Node>, Integer> bestPath = noPath;
for (Node n : g.nodes) {
if (!Collections.disjoint(n.data, varsToSolve)) {
Pair<List<Node>, Integer> path = computeTreeToLeafs(n);
//discard all paths containing at least a node in the
//closure computed above
if (path.snd < bestPath.snd) {
bestPath = path;
}
}
}
if (bestPath == noPath) {
//no path leads there
throw new NodeNotFoundException(g);
}
return bestPath.fst.head;
}
}
The inference process can be thought of as a sequence of steps. Each step
instantiates an inference variable using a subset of the inference variable
bounds, if certain condition are met. Decisions such as the sequence in which
steps are applied, or which steps are to be applied are left to the inference engine.
/**
* The inference process can be thought of as a sequence of steps. Each step
* instantiates an inference variable using a subset of the inference variable
* bounds, if certain condition are met. Decisions such as the sequence in which
* steps are applied, or which steps are to be applied are left to the inference engine.
*/
enum InferenceStep {
Instantiate an inference variables using one of its (ground) equality
constraints
/**
* Instantiate an inference variables using one of its (ground) equality
* constraints
*/
EQ(InferenceBound.EQ) {
@Override
Type solve(UndetVar uv, InferenceContext inferenceContext) {
return filterBounds(uv, inferenceContext).head;
}
},
Instantiate an inference variables using its (ground) lower bounds. Such
bounds are merged together using lub().
/**
* Instantiate an inference variables using its (ground) lower bounds. Such
* bounds are merged together using lub().
*/
LOWER(InferenceBound.LOWER) {
@Override
Type solve(UndetVar uv, InferenceContext inferenceContext) {
Infer infer = inferenceContext.infer;
List<Type> lobounds = filterBounds(uv, inferenceContext);
//note: lobounds should have at least one element
Type owntype = lobounds.tail.tail == null ? lobounds.head : infer.types.lub(lobounds);
if (owntype.isPrimitive() || owntype.hasTag(ERROR)) {
throw infer.error(infer.diags.fragment(Fragments.NoUniqueMinimalInstanceExists(uv.qtype, lobounds)));
} else {
return owntype;
}
}
},
Infer uninstantiated/unbound inference variables occurring in 'throws'
clause as RuntimeException
/**
* Infer uninstantiated/unbound inference variables occurring in 'throws'
* clause as RuntimeException
*/
THROWS(InferenceBound.UPPER) {
@Override
public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
if (!t.isThrows()) {
//not a throws undet var
return false;
}
Types types = inferenceContext.types;
Symtab syms = inferenceContext.infer.syms;
return t.getBounds(InferenceBound.UPPER).stream()
.filter(b -> !inferenceContext.free(b))
.allMatch(u -> types.isSubtype(syms.runtimeExceptionType, u));
}
@Override
Type solve(UndetVar uv, InferenceContext inferenceContext) {
return inferenceContext.infer.syms.runtimeExceptionType;
}
},
Instantiate an inference variables using its (ground) upper bounds. Such
bounds are merged together using glb().
/**
* Instantiate an inference variables using its (ground) upper bounds. Such
* bounds are merged together using glb().
*/
UPPER(InferenceBound.UPPER) {
@Override
Type solve(UndetVar uv, InferenceContext inferenceContext) {
Infer infer = inferenceContext.infer;
List<Type> hibounds = filterBounds(uv, inferenceContext);
//note: hibounds should have at least one element
Type owntype = hibounds.tail.tail == null ? hibounds.head : infer.types.glb(hibounds);
if (owntype.isPrimitive() || owntype.hasTag(ERROR)) {
throw infer.error(infer.diags.fragment(Fragments.NoUniqueMaximalInstanceExists(uv.qtype, hibounds)));
} else {
return owntype;
}
}
},
Like the former; the only difference is that this step can only be applied
if all upper bounds are ground.
/**
* Like the former; the only difference is that this step can only be applied
* if all upper bounds are ground.
*/
UPPER_LEGACY(InferenceBound.UPPER) {
@Override
public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
return !inferenceContext.free(t.getBounds(ib)) && !t.isCaptured();
}
@Override
Type solve(UndetVar uv, InferenceContext inferenceContext) {
return UPPER.solve(uv, inferenceContext);
}
},
Like the former; the only difference is that this step can only be applied
if all upper/lower bounds are ground.
/**
* Like the former; the only difference is that this step can only be applied
* if all upper/lower bounds are ground.
*/
CAPTURED(InferenceBound.UPPER) {
@Override
public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
return t.isCaptured() &&
!inferenceContext.free(t.getBounds(InferenceBound.UPPER, InferenceBound.LOWER));
}
@Override
Type solve(UndetVar uv, InferenceContext inferenceContext) {
Infer infer = inferenceContext.infer;
Type upper = UPPER.filterBounds(uv, inferenceContext).nonEmpty() ?
UPPER.solve(uv, inferenceContext) :
infer.syms.objectType;
Type lower = LOWER.filterBounds(uv, inferenceContext).nonEmpty() ?
LOWER.solve(uv, inferenceContext) :
infer.syms.botType;
CapturedType prevCaptured = (CapturedType)uv.qtype;
return new CapturedType(prevCaptured.tsym.name, prevCaptured.tsym.owner,
upper, lower, prevCaptured.wildcard);
}
};
final InferenceBound ib;
InferenceStep(InferenceBound ib) {
this.ib = ib;
}
Find an instantiated type for a given inference variable within
a given inference context
/**
* Find an instantiated type for a given inference variable within
* a given inference context
*/
abstract Type solve(UndetVar uv, InferenceContext inferenceContext);
Can the inference variable be instantiated using this step?
/**
* Can the inference variable be instantiated using this step?
*/
public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
return filterBounds(t, inferenceContext).nonEmpty() && !t.isCaptured();
}
Return the subset of ground bounds in a given bound set (i.e. eq/lower/upper)
/**
* Return the subset of ground bounds in a given bound set (i.e. eq/lower/upper)
*/
List<Type> filterBounds(UndetVar uv, InferenceContext inferenceContext) {
return Type.filter(uv.getBounds(ib), new BoundFilter(inferenceContext));
}
}
This enumeration defines the sequence of steps to be applied when the
solver works in legacy mode. The steps in this enumeration reflect
the behavior of old inference routine (see JLS SE 7 15.12.2.7/15.12.2.8).
/**
* This enumeration defines the sequence of steps to be applied when the
* solver works in legacy mode. The steps in this enumeration reflect
* the behavior of old inference routine (see JLS SE 7 15.12.2.7/15.12.2.8).
*/
enum LegacyInferenceSteps {
EQ_LOWER(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER)),
EQ_UPPER(EnumSet.of(InferenceStep.EQ, InferenceStep.UPPER_LEGACY));
final EnumSet<InferenceStep> steps;
LegacyInferenceSteps(EnumSet<InferenceStep> steps) {
this.steps = steps;
}
}
This enumeration defines the sequence of steps to be applied when the
graph solver is used. This order is defined so as to maximize compatibility
w.r.t. old inference routine (see JLS SE 7 15.12.2.7/15.12.2.8).
/**
* This enumeration defines the sequence of steps to be applied when the
* graph solver is used. This order is defined so as to maximize compatibility
* w.r.t. old inference routine (see JLS SE 7 15.12.2.7/15.12.2.8).
*/
enum GraphInferenceSteps {
EQ(EnumSet.of(InferenceStep.EQ)),
EQ_LOWER(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER)),
EQ_LOWER_THROWS_UPPER_CAPTURED(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER, InferenceStep.UPPER, InferenceStep.THROWS, InferenceStep.CAPTURED));
final EnumSet<InferenceStep> steps;
GraphInferenceSteps(EnumSet<InferenceStep> steps) {
this.steps = steps;
}
}
There are two kinds of dependencies between inference variables. The basic
kind of dependency (or bound dependency) arises when a variable mention
another variable in one of its bounds. There's also a more subtle kind
of dependency that arises when a variable 'might' lead to better constraints
on another variable (this is typically the case with variables holding up
stuck expressions).
/**
* There are two kinds of dependencies between inference variables. The basic
* kind of dependency (or bound dependency) arises when a variable mention
* another variable in one of its bounds. There's also a more subtle kind
* of dependency that arises when a variable 'might' lead to better constraints
* on another variable (this is typically the case with variables holding up
* stuck expressions).
*/
enum DependencyKind implements GraphUtils.DependencyKind {
bound dependency /** bound dependency */
BOUND("dotted"),
stuck dependency /** stuck dependency */
STUCK("dashed");
final String dotSyle;
private DependencyKind(String dotSyle) {
this.dotSyle = dotSyle;
}
}
This is the graph inference solver - the solver organizes all inference variables in
a given inference context by bound dependencies - in the general case, such dependencies
would lead to a cyclic directed graph (hence the name); the dependency info is used to build
an acyclic graph, where all cyclic variables are bundled together. An inference
step corresponds to solving a node in the acyclic graph - this is done by
relying on a given strategy (see GraphStrategy).
/**
* This is the graph inference solver - the solver organizes all inference variables in
* a given inference context by bound dependencies - in the general case, such dependencies
* would lead to a cyclic directed graph (hence the name); the dependency info is used to build
* an acyclic graph, where all cyclic variables are bundled together. An inference
* step corresponds to solving a node in the acyclic graph - this is done by
* relying on a given strategy (see GraphStrategy).
*/
class GraphSolver {
InferenceContext inferenceContext;
Warner warn;
GraphSolver(InferenceContext inferenceContext, Warner warn) {
this.inferenceContext = inferenceContext;
this.warn = warn;
}
Solve variables in a given inference context. The amount of variables
to be solved, and the way in which the underlying acyclic graph is explored
depends on the selected solver strategy.
/**
* Solve variables in a given inference context. The amount of variables
* to be solved, and the way in which the underlying acyclic graph is explored
* depends on the selected solver strategy.
*/
void solve(GraphStrategy sstrategy) {
doIncorporation(inferenceContext, warn); //initial propagation of bounds
InferenceGraph inferenceGraph = new InferenceGraph();
while (!sstrategy.done()) {
if (dependenciesFolder != null) {
//add this graph to the pending queue
pendingGraphs = pendingGraphs.prepend(inferenceGraph.toDot());
}
InferenceGraph.Node nodeToSolve = sstrategy.pickNode(inferenceGraph);
List<Type> varsToSolve = List.from(nodeToSolve.data);
List<Type> saved_undet = inferenceContext.save();
try {
//repeat until all variables are solved
outer: while (Type.containsAny(inferenceContext.restvars(), varsToSolve)) {
//for each inference phase
for (GraphInferenceSteps step : GraphInferenceSteps.values()) {
if (inferenceContext.solveBasic(varsToSolve, step.steps).nonEmpty()) {
doIncorporation(inferenceContext, warn);
continue outer;
}
}
//no progress
throw error(null);
}
}
catch (InferenceException ex) {
//did we fail because of interdependent ivars?
inferenceContext.rollback(saved_undet);
instantiateAsUninferredVars(varsToSolve, inferenceContext);
doIncorporation(inferenceContext, warn);
}
inferenceGraph.deleteNode(nodeToSolve);
}
}
The dependencies between the inference variables that need to be solved
form a (possibly cyclic) graph. This class reduces the original dependency graph
to an acyclic version, where cyclic nodes are folded into a single 'super node'.
/**
* The dependencies between the inference variables that need to be solved
* form a (possibly cyclic) graph. This class reduces the original dependency graph
* to an acyclic version, where cyclic nodes are folded into a single 'super node'.
*/
class InferenceGraph {
This class represents a node in the graph. Each node corresponds
to an inference variable and has edges (dependencies) on other
nodes. The node defines an entry point that can be used to receive
updates on the structure of the graph this node belongs to (used to
keep dependencies in sync).
/**
* This class represents a node in the graph. Each node corresponds
* to an inference variable and has edges (dependencies) on other
* nodes. The node defines an entry point that can be used to receive
* updates on the structure of the graph this node belongs to (used to
* keep dependencies in sync).
*/
class Node extends GraphUtils.TarjanNode<ListBuffer<Type>, Node> implements DottableNode<ListBuffer<Type>, Node> {
node dependencies /** node dependencies */
Set<Node> deps;
Node(Type ivar) {
super(ListBuffer.of(ivar));
this.deps = new LinkedHashSet<>();
}
@Override
public GraphUtils.DependencyKind[] getSupportedDependencyKinds() {
return new GraphUtils.DependencyKind[] { DependencyKind.BOUND };
}
public Iterable<? extends Node> getAllDependencies() {
return deps;
}
@Override
public Collection<? extends Node> getDependenciesByKind(GraphUtils.DependencyKind dk) {
if (dk == DependencyKind.BOUND) {
return deps;
} else {
throw new IllegalStateException();
}
}
Adds dependency with given kind.
/**
* Adds dependency with given kind.
*/
protected void addDependency(Node depToAdd) {
deps.add(depToAdd);
}
Add multiple dependencies of same given kind.
/**
* Add multiple dependencies of same given kind.
*/
protected void addDependencies(Set<Node> depsToAdd) {
for (Node n : depsToAdd) {
addDependency(n);
}
}
Remove a dependency, regardless of its kind.
/**
* Remove a dependency, regardless of its kind.
*/
protected boolean removeDependency(Node n) {
return deps.remove(n);
}
Compute closure of a give node, by recursively walking
through all its dependencies.
/**
* Compute closure of a give node, by recursively walking
* through all its dependencies.
*/
protected Set<Node> closure() {
Set<Node> closure = new HashSet<>();
closureInternal(closure);
return closure;
}
private void closureInternal(Set<Node> closure) {
if (closure.add(this)) {
for (Node n : deps) {
n.closureInternal(closure);
}
}
}
Is this node a leaf? This means either the node has no dependencies,
or it just has self-dependencies.
/**
* Is this node a leaf? This means either the node has no dependencies,
* or it just has self-dependencies.
*/
protected boolean isLeaf() {
//no deps, or only one self dep
if (deps.isEmpty()) return true;
for (Node n : deps) {
if (n != this) {
return false;
}
}
return true;
}
Merge this node with another node, acquiring its dependencies.
This routine is used to merge all cyclic node together and
form an acyclic graph.
/**
* Merge this node with another node, acquiring its dependencies.
* This routine is used to merge all cyclic node together and
* form an acyclic graph.
*/
protected void mergeWith(List<? extends Node> nodes) {
for (Node n : nodes) {
Assert.check(n.data.length() == 1, "Attempt to merge a compound node!");
data.appendList(n.data);
addDependencies(n.deps);
}
//update deps
Set<Node> deps2 = new LinkedHashSet<>();
for (Node d : deps) {
if (data.contains(d.data.first())) {
deps2.add(this);
} else {
deps2.add(d);
}
}
deps = deps2;
}
Notify all nodes that something has changed in the graph
topology.
/**
* Notify all nodes that something has changed in the graph
* topology.
*/
private void graphChanged(Node from, Node to) {
if (removeDependency(from)) {
if (to != null) {
addDependency(to);
}
}
}
@Override
public Properties nodeAttributes() {
Properties p = new Properties();
p.put("label", "\"" + toString() + "\"");
return p;
}
@Override
public Properties dependencyAttributes(Node sink, GraphUtils.DependencyKind dk) {
Properties p = new Properties();
p.put("style", ((DependencyKind)dk).dotSyle);
StringBuilder buf = new StringBuilder();
String sep = "";
for (Type from : data) {
UndetVar uv = (UndetVar)inferenceContext.asUndetVar(from);
for (Type bound : uv.getBounds(InferenceBound.values())) {
if (bound.containsAny(List.from(sink.data))) {
buf.append(sep);
buf.append(bound);
sep = ",";
}
}
}
p.put("label", "\"" + buf.toString() + "\"");
return p;
}
}
the nodes in the inference graph /** the nodes in the inference graph */
ArrayList<Node> nodes;
InferenceGraph() {
initNodes();
}
Basic lookup helper for retrieving a graph node given an inference
variable type.
/**
* Basic lookup helper for retrieving a graph node given an inference
* variable type.
*/
public Node findNode(Type t) {
for (Node n : nodes) {
if (n.data.contains(t)) {
return n;
}
}
return null;
}
Delete a node from the graph. This update the underlying structure
of the graph (including dependencies) via listeners updates.
/**
* Delete a node from the graph. This update the underlying structure
* of the graph (including dependencies) via listeners updates.
*/
public void deleteNode(Node n) {
Assert.check(nodes.contains(n));
nodes.remove(n);
notifyUpdate(n, null);
}
Notify all nodes of a change in the graph. If the target node is null
the source node is assumed to be removed. /**
* Notify all nodes of a change in the graph. If the target node is
* {@code null} the source node is assumed to be removed.
*/
void notifyUpdate(Node from, Node to) {
for (Node n : nodes) {
n.graphChanged(from, to);
}
}
Create the graph nodes. First a simple node is created for every inference
variables to be solved. Then Tarjan is used to found all connected components
in the graph. For each component containing more than one node, a super node is
created, effectively replacing the original cyclic nodes.
/**
* Create the graph nodes. First a simple node is created for every inference
* variables to be solved. Then Tarjan is used to found all connected components
* in the graph. For each component containing more than one node, a super node is
* created, effectively replacing the original cyclic nodes.
*/
void initNodes() {
//add nodes
nodes = new ArrayList<>();
for (Type t : inferenceContext.restvars()) {
nodes.add(new Node(t));
}
//add dependencies
for (Node n_i : nodes) {
Type i = n_i.data.first();
for (Node n_j : nodes) {
Type j = n_j.data.first();
// don't compare a variable to itself
if (i != j) {
UndetVar uv_i = (UndetVar)inferenceContext.asUndetVar(i);
if (Type.containsAny(uv_i.getBounds(InferenceBound.values()), List.of(j))) {
//update i's bound dependencies
n_i.addDependency(n_j);
}
}
}
}
//merge cyclic nodes
ArrayList<Node> acyclicNodes = new ArrayList<>();
for (List<? extends Node> conSubGraph : GraphUtils.tarjan(nodes)) {
if (conSubGraph.length() > 1) {
Node root = conSubGraph.head;
root.mergeWith(conSubGraph.tail);
for (Node n : conSubGraph) {
notifyUpdate(n, root);
}
}
acyclicNodes.add(conSubGraph.head);
}
nodes = acyclicNodes;
}
Debugging: dot representation of this graph
/**
* Debugging: dot representation of this graph
*/
String toDot() {
StringBuilder buf = new StringBuilder();
for (Type t : inferenceContext.undetvars) {
UndetVar uv = (UndetVar)t;
buf.append(String.format("var %s - upper bounds = %s, lower bounds = %s, eq bounds = %s\\n",
uv.qtype, uv.getBounds(InferenceBound.UPPER), uv.getBounds(InferenceBound.LOWER),
uv.getBounds(InferenceBound.EQ)));
}
return GraphUtils.toDot(nodes, "inferenceGraph" + hashCode(), buf.toString());
}
}
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Inference context">
Functional interface for defining inference callbacks. Certain actions
(i.e. subtyping checks) might need to be redone after all inference variables
have been fixed.
/**
* Functional interface for defining inference callbacks. Certain actions
* (i.e. subtyping checks) might need to be redone after all inference variables
* have been fixed.
*/
interface FreeTypeListener {
void typesInferred(InferenceContext inferenceContext);
}
final InferenceContext emptyContext;
// </editor-fold>
}