Copyright (c) 2013, 2019 GK Software AG, and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: Stephan Herrmann - initial API and implementation IBM Corporation - Bug fixes
/******************************************************************************* * Copyright (c) 2013, 2019 GK Software AG, and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Stephan Herrmann - initial API and implementation * IBM Corporation - Bug fixes *******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FunctionalExpression; import org.eclipse.jdt.internal.compiler.ast.Invocation; import org.eclipse.jdt.internal.compiler.ast.LambdaExpression; import org.eclipse.jdt.internal.compiler.ast.ReferenceExpression; import org.eclipse.jdt.internal.compiler.ast.SwitchExpression; import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants.BoundCheckStatus; import org.eclipse.jdt.internal.compiler.util.Sorting;
Main class for new type inference as per JLS8 sect 18. Keeps contextual state and drives the algorithm.

Inference Basics

Each instance of InferenceContext18 manages instances of the above and coordinates the inference process.

Queries and utilities

Phases of Inference

Some of the above operations accumulate their results into currentBounds, whereas the last phase returns the resulting bound set while keeping the previous state in currentBounds.

18.5. Uses of Inference

These are the main entries from the compiler into the inference engine:
18.5.1 Invocation Applicability Inference
inferInvocationApplicability(MethodBinding, TypeBinding[], boolean). Prepare the initial state for inference of a generic invocation - no target type used at this point. Need to call solve(boolean) with true afterwards to produce the intermediate result.
Called indirectly from Scope.findMethod(ReferenceBinding, char[], TypeBinding[], InvocationSite, boolean) et al to select applicable methods into overload resolution.
18.5.2 Invocation Type Inference
inferInvocationType(TypeBinding, InvocationSite, MethodBinding). After a most specific method has been picked, and given a target type determine the final generic instantiation. As long as a target type is still unavailable this phase keeps getting deferred.
Different wrappers exist for the convenience of different callers.
18.5.3 Functional Interface Parameterization Inference
Controlled from FunctionalExpression.resolveType(BlockScope).
18.5.4 More Specific Method Inference
Not Yet Implemented
For 18.5.1 and 18.5.2 high-level control is implemented in ParameterizedGenericMethodBinding.computeCompatibleMethod(MethodBinding, TypeBinding[], Scope, InvocationSite).

Inference Lifecycle

  • Decision whether or not an invocation is a variable-arity invocation is made by first attempting to solve 18.5.1 in mode CHECK_LOOSE. Only if that fails, another attempt is made in mode CHECK_VARARG. Which of these two attempts was successful is stored in inferenceKind. This field must be consulted whenever arguments of an invocation should be further processed. See also getParameter(TypeBinding[], int, boolean) and its clients.
  • /** * Main class for new type inference as per JLS8 sect 18. * Keeps contextual state and drives the algorithm. * * <h2>Inference Basics</h2> * <ul> * <li>18.1.1 Inference variables: {@link InferenceVariable}</li> * <li>18.1.2 Constraint Formulas: subclasses of {@link ConstraintFormula}</li> * <li>18.1.3 Bounds: {@link TypeBound}<br/> * Capture bounds are directly captured in {@link BoundSet#captures}, throws-bounds in {@link BoundSet#inThrows}.<br/> * Also: {@link BoundSet}: main state during inference.</li> * </ul> * Each instance of {@link InferenceContext18} manages instances of the above and coordinates the inference process. * <h3>Queries and utilities</h3> * <ul> * <li>{@link TypeBinding#isProperType(boolean)}: * used to exclude "types" that mention inference variables (18.1.1).</li> * <li>{@link TypeBinding#mentionsAny(TypeBinding[], int)}: * does the receiver type binding mention any of the given types?</li> * <li>{@link TypeBinding#substituteInferenceVariable(InferenceVariable, TypeBinding)}: * replace occurrences of an inference variable with a proper type.</li> * <li>{@link TypeBinding#collectInferenceVariables(Set)}: * collect all inference variables mentioned in the receiver type into the given set.</li> * <li>{@link TypeVariableBinding#getTypeBounds(InferenceVariable, InferenceSubstitution)}: * Compute the initial type bounds for one inference variable as per JLS8 sect 18.1.3.</li> * </ul> * <h2>Phases of Inference</h2> * <ul> * <li>18.2 <b>Reduction</b>: {@link #reduce()} with most work happening in implementations of * {@link ConstraintFormula#reduce(InferenceContext18)}: * <ul> * <li>18.2.1 Expression Compatibility Constraints: {@link ConstraintExpressionFormula#reduce(InferenceContext18)}.</li> * <li>18.2.2 Type Compatibility Constraints ff. {@link ConstraintTypeFormula#reduce(InferenceContext18)}.</li> * </ul></li> * <li>18.3 <b>Incorporation</b>: {@link BoundSet#incorporate(InferenceContext18)}; during inference new constraints * are accepted via {@link BoundSet#reduceOneConstraint(InferenceContext18, ConstraintFormula)} (combining 18.2 & 18.3)</li> * <li>18.4 <b>Resolution</b>: {@link #resolve(InferenceVariable[])}. * </ul> * Some of the above operations accumulate their results into {@link #currentBounds}, whereas * the last phase <em>returns</em> the resulting bound set while keeping the previous state in {@link #currentBounds}. * <h2>18.5. Uses of Inference</h2> * These are the main entries from the compiler into the inference engine: * <dl> * <dt>18.5.1 Invocation Applicability Inference</dt> * <dd>{@link #inferInvocationApplicability(MethodBinding, TypeBinding[], boolean)}. Prepare the initial state for * inference of a generic invocation - no target type used at this point. * Need to call {@link #solve(boolean)} with true afterwards to produce the intermediate result.<br/> * Called indirectly from {@link Scope#findMethod(ReferenceBinding, char[], TypeBinding[], InvocationSite, boolean)} et al * to select applicable methods into overload resolution.</dd> * <dt>18.5.2 Invocation Type Inference</dt> * <dd>{@link InferenceContext18#inferInvocationType(TypeBinding, InvocationSite, MethodBinding)}. After a * most specific method has been picked, and given a target type determine the final generic instantiation. * As long as a target type is still unavailable this phase keeps getting deferred.</br> * Different wrappers exist for the convenience of different callers.</dd> * <dt>18.5.3 Functional Interface Parameterization Inference</dt> * <dd>Controlled from {@link LambdaExpression#resolveType(BlockScope)}.</dd> * <dt>18.5.4 More Specific Method Inference</dt> * <dd><em>Not Yet Implemented</em></dd> * </dl> * For 18.5.1 and 18.5.2 high-level control is implemented in * {@link ParameterizedGenericMethodBinding#computeCompatibleMethod(MethodBinding, TypeBinding[], Scope, InvocationSite)}. * <h2>Inference Lifecycle</h2> * <li>Decision whether or not an invocation is a <b>variable-arity</b> invocation is made by first attempting * to solve 18.5.1 in mode {@link #CHECK_LOOSE}. Only if that fails, another attempt is made in mode {@link #CHECK_VARARG}. * Which of these two attempts was successful is stored in {@link #inferenceKind}. * This field must be consulted whenever arguments of an invocation should be further processed. * See also {@link #getParameter(TypeBinding[], int, boolean)} and its clients.</li> * </ul> */
    public class InferenceContext18 {
    to conform with javac regarding https://bugs.openjdk.java.net/browse/JDK-8026527
    /** to conform with javac regarding https://bugs.openjdk.java.net/browse/JDK-8026527 */
    static final boolean SIMULATE_BUG_JDK_8026527 = true;
    Temporary workaround until we know fully what to do with https://bugs.openjdk.java.net/browse/JDK-8054721 It looks likely that we have a bug independent of this JLS bug in that we clear the capture bounds eagerly.
    /** Temporary workaround until we know fully what to do with https://bugs.openjdk.java.net/browse/JDK-8054721 * It looks likely that we have a bug independent of this JLS bug in that we clear the capture bounds eagerly. */
    static final boolean SHOULD_WORKAROUND_BUG_JDK_8054721 = true; // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=437444#c24 onwards static final boolean SHOULD_WORKAROUND_BUG_JDK_8153748 = true; // emulating javac behaviour after private email communication
    Detail flag to control the extent of SIMULATE_BUG_JDK_8026527. A setting of 'false' implements the advice from http://mail.openjdk.java.net/pipermail/lambda-spec-experts/2013-December/000447.html i.e., raw types are not considered as compatible in constraints/bounds derived from invocation arguments, but only for constraints derived from type variable bounds.
    /** * Detail flag to control the extent of {@link #SIMULATE_BUG_JDK_8026527}. * A setting of 'false' implements the advice from http://mail.openjdk.java.net/pipermail/lambda-spec-experts/2013-December/000447.html * i.e., raw types are not considered as compatible in constraints/bounds derived from invocation arguments, * but only for constraints derived from type variable bounds. */
    static final boolean ARGUMENT_CONSTRAINTS_ARE_SOFT = false; // --- Main State of the Inference: ---
    the invocation being inferred (for 18.5.1 and 18.5.2)
    /** the invocation being inferred (for 18.5.1 and 18.5.2) */
    InvocationSite currentInvocation;
    arguments of #currentInvocation, if any
    /** arguments of #currentInvocation, if any */
    Expression[] invocationArguments;
    The inference variables for which as solution is sought.
    /** The inference variables for which as solution is sought. */
    InferenceVariable[] inferenceVariables;
    Constraints that have not yet been reduced and incorporated.
    /** Constraints that have not yet been reduced and incorporated. */
    ConstraintFormula[] initialConstraints; ConstraintExpressionFormula[] finalConstraints; // for final revalidation at a "macroscopic" level
    The accumulated type bounds etc.
    /** The accumulated type bounds etc. */
    BoundSet currentBounds;
    One of CHECK_STRICT, CHECK_LOOSE, or CHECK_VARARGS.
    /** One of CHECK_STRICT, CHECK_LOOSE, or CHECK_VARARGS. */
    int inferenceKind;
    Marks how much work has been done so far? Used to avoid performing any of these tasks more than once.
    /** Marks how much work has been done so far? Used to avoid performing any of these tasks more than once. */
    public int stepCompleted = NOT_INFERRED; public static final int NOT_INFERRED = 0;
    Applicability Inference (18.5.1) has been completed.
    /** Applicability Inference (18.5.1) has been completed. */
    public static final int APPLICABILITY_INFERRED = 1;
    Invocation Type Inference (18.5.2) has been completed (for some target type).
    /** Invocation Type Inference (18.5.2) has been completed (for some target type). */
    public static final int TYPE_INFERRED = 2; public static final int TYPE_INFERRED_FINAL = 3; // as above plus asserting that target type was a proper type
    Signals whether any type compatibility makes use of unchecked conversion.
    /** Signals whether any type compatibility makes use of unchecked conversion. */
    public List<ConstraintFormula> constraintsWithUncheckedConversion; public boolean usesUncheckedConversion; public InferenceContext18 outerContext; private Set<InferenceContext18> seenInnerContexts; Scope scope; LookupEnvironment environment; ReferenceBinding object; // java.lang.Object public BoundSet b2; private BoundSet b3;
    Not per JLS: inbox for emulation of how javac passes type bounds from inner to outer
    /** Not per JLS: inbox for emulation of how javac passes type bounds from inner to outer */
    private BoundSet innerInbox;
    Not per JLS: signal when current is ready to directly merge all bounds from inner.
    /** Not per JLS: signal when current is ready to directly merge all bounds from inner. */
    private boolean directlyAcceptingInnerBounds = false;
    Not per JLS: pushing bounds from inner to outer may have to be deferred till after overload resolution, store here a runnable to perform the push.
    /** Not per JLS: pushing bounds from inner to outer may have to be deferred till after overload resolution, store here a runnable to perform the push. */
    private Runnable pushToOuterJob = null; public static boolean isSameSite(InvocationSite site1, InvocationSite site2) { if (site1 == site2) return true; if (site1 == null || site2 == null) return false; if (site1.sourceStart() == site2.sourceStart() && site1.sourceEnd() == site2.sourceEnd()) return true; return false; } public static final int CHECK_UNKNOWN = 0; public static final int CHECK_STRICT = 1; public static final int CHECK_LOOSE = 2; public static final int CHECK_VARARG = 3; static class SuspendedInferenceRecord { InvocationSite site; Expression[] invocationArguments; InferenceVariable[] inferenceVariables; int inferenceKind; boolean usesUncheckedConversion; SuspendedInferenceRecord(InvocationSite site, Expression[] invocationArguments, InferenceVariable[] inferenceVariables, int inferenceKind, boolean usesUncheckedConversion) { this.site = site; this.invocationArguments = invocationArguments; this.inferenceVariables = inferenceVariables; this.inferenceKind = inferenceKind; this.usesUncheckedConversion = usesUncheckedConversion; } }
    Construct an inference context for an invocation (method/constructor).
    /** Construct an inference context for an invocation (method/constructor). */
    public InferenceContext18(Scope scope, Expression[] arguments, InvocationSite site, InferenceContext18 outerContext) { this.scope = scope; this.environment = scope.environment(); this.object = scope.getJavaLangObject(); this.invocationArguments = arguments; this.currentInvocation = site; this.outerContext = outerContext; if (site instanceof Invocation) scope.compilationUnitScope().registerInferredInvocation((Invocation) site); } public InferenceContext18(Scope scope) { this.scope = scope; this.environment = scope.environment(); this.object = scope.getJavaLangObject(); }
    JLS 18.1.3: Create initial bounds from a given set of type parameters declarations.
    Returns:the set of inference variables created for the given typeParameters
    /** * JLS 18.1.3: Create initial bounds from a given set of type parameters declarations. * @return the set of inference variables created for the given typeParameters */
    public InferenceVariable[] createInitialBoundSet(TypeVariableBinding[] typeParameters) { // if (this.currentBounds == null) { this.currentBounds = new BoundSet(); } if (typeParameters != null) { InferenceVariable[] newInferenceVariables = addInitialTypeVariableSubstitutions(typeParameters); this.currentBounds.addBoundsFromTypeParameters(this, typeParameters, newInferenceVariables); return newInferenceVariables; } return Binding.NO_INFERENCE_VARIABLES; }
    Substitute any type variables mentioned in 'type' by the corresponding inference variable, if one exists.
    /** * Substitute any type variables mentioned in 'type' by the corresponding inference variable, if one exists. */
    public TypeBinding substitute(TypeBinding type) { InferenceSubstitution inferenceSubstitution = new InferenceSubstitution(this); return inferenceSubstitution.substitute(inferenceSubstitution, type); }
    JLS 18.5.1: compute bounds from formal and actual parameters.
    /** JLS 18.5.1: compute bounds from formal and actual parameters. */
    public void createInitialConstraintsForParameters(TypeBinding[] parameters, boolean checkVararg, TypeBinding varArgsType, MethodBinding method) { if (this.invocationArguments == null) return; int len = checkVararg ? parameters.length - 1 : Math.min(parameters.length, this.invocationArguments.length); int maxConstraints = checkVararg ? this.invocationArguments.length : len; int numConstraints = 0; boolean ownConstraints; if (this.initialConstraints == null) { this.initialConstraints = new ConstraintFormula[maxConstraints]; ownConstraints = true; } else { numConstraints = this.initialConstraints.length; maxConstraints += numConstraints; System.arraycopy(this.initialConstraints, 0, this.initialConstraints=new ConstraintFormula[maxConstraints], 0, numConstraints); ownConstraints = false; // these are lifted from a nested poly expression. } for (int i = 0; i < len; i++) { TypeBinding thetaF = substitute(parameters[i]); if (this.invocationArguments[i].isPertinentToApplicability(parameters[i], method)) { this.initialConstraints[numConstraints++] = new ConstraintExpressionFormula(this.invocationArguments[i], thetaF, ReductionResult.COMPATIBLE, ARGUMENT_CONSTRAINTS_ARE_SOFT); } else if (!isTypeVariableOfCandidate(parameters[i], method)) { this.initialConstraints[numConstraints++] = new ConstraintExpressionFormula(this.invocationArguments[i], thetaF, ReductionResult.POTENTIALLY_COMPATIBLE); } // else we know it is potentially compatible, no need to assert. } if (checkVararg && varArgsType instanceof ArrayBinding) { varArgsType = ((ArrayBinding)varArgsType).elementsType(); TypeBinding thetaF = substitute(varArgsType); for (int i = len; i < this.invocationArguments.length; i++) { if (this.invocationArguments[i].isPertinentToApplicability(varArgsType, method)) { this.initialConstraints[numConstraints++] = new ConstraintExpressionFormula(this.invocationArguments[i], thetaF, ReductionResult.COMPATIBLE, ARGUMENT_CONSTRAINTS_ARE_SOFT); } else if (!isTypeVariableOfCandidate(varArgsType, method)) { this.initialConstraints[numConstraints++] = new ConstraintExpressionFormula(this.invocationArguments[i], thetaF, ReductionResult.POTENTIALLY_COMPATIBLE); } // else we know it is potentially compatible, no need to assert. } } if (numConstraints == 0) this.initialConstraints = ConstraintFormula.NO_CONSTRAINTS; else if (numConstraints < maxConstraints) System.arraycopy(this.initialConstraints, 0, this.initialConstraints = new ConstraintFormula[numConstraints], 0, numConstraints); if (ownConstraints) { // lifted constraints get validated at their own context. final int length = this.initialConstraints.length; System.arraycopy(this.initialConstraints, 0, this.finalConstraints = new ConstraintExpressionFormula[length], 0, length); } } private boolean isTypeVariableOfCandidate(TypeBinding type, MethodBinding candidate) { // cf. FunctionalExpression.isPertinentToApplicability() if (type instanceof TypeVariableBinding) { Binding declaringElement = ((TypeVariableBinding) type).declaringElement; if (declaringElement == candidate) return true; if (candidate.isConstructor() && declaringElement == candidate.declaringClass) return true; } return false; } private InferenceVariable[] addInitialTypeVariableSubstitutions(TypeBinding[] typeVariables) { int len = typeVariables.length; if (len == 0) { if (this.inferenceVariables == null) this.inferenceVariables = Binding.NO_INFERENCE_VARIABLES; return Binding.NO_INFERENCE_VARIABLES; } InferenceVariable[] newVariables = new InferenceVariable[len]; for (int i = 0; i < len; i++) newVariables[i] = InferenceVariable.get(typeVariables[i], i, this.currentInvocation, this.scope, this.object, true); addInferenceVariables(newVariables); return newVariables; } private void addInferenceVariables(InferenceVariable[] newVariables) { if (this.inferenceVariables == null || this.inferenceVariables.length == 0) { this.inferenceVariables = newVariables; } else { // merge into this.inferenceVariables: int len = newVariables.length; int prev = this.inferenceVariables.length; System.arraycopy(this.inferenceVariables, 0, this.inferenceVariables = new InferenceVariable[len+prev], 0, prev); System.arraycopy(newVariables, 0, this.inferenceVariables, prev, len); } }
    Add new inference variables for the given type variables.
    /** Add new inference variables for the given type variables. */
    public InferenceVariable[] addTypeVariableSubstitutions(TypeBinding[] typeVariables) { int len2 = typeVariables.length; InferenceVariable[] newVariables = new InferenceVariable[len2]; InferenceVariable[] toAdd = new InferenceVariable[len2]; int numToAdd = 0; for (int i = 0; i < typeVariables.length; i++) { if (typeVariables[i] instanceof InferenceVariable) newVariables[i] = (InferenceVariable) typeVariables[i]; // prevent double substitution of an already-substituted inferenceVariable else toAdd[numToAdd++] = newVariables[i] = InferenceVariable.get(typeVariables[i], i, this.currentInvocation, this.scope, this.object, false); } if (numToAdd > 0) { int start = 0; if (this.inferenceVariables != null) { int len1 = this.inferenceVariables.length; System.arraycopy(this.inferenceVariables, 0, this.inferenceVariables = new InferenceVariable[len1+numToAdd], 0, len1); start = len1; } else { this.inferenceVariables = new InferenceVariable[numToAdd]; } System.arraycopy(toAdd, 0, this.inferenceVariables, start, numToAdd); } return newVariables; }
    JLS 18.1.3 Bounds: throws α: the inference variable α appears in a throws clause
    /** JLS 18.1.3 Bounds: throws α: the inference variable α appears in a throws clause */
    public void addThrowsContraints(TypeBinding[] parameters, InferenceVariable[] variables, ReferenceBinding[] thrownExceptions) { for (int i = 0; i < parameters.length; i++) { TypeBinding parameter = parameters[i]; for (int j = 0; j < thrownExceptions.length; j++) { if (TypeBinding.equalsEquals(parameter, thrownExceptions[j])) { this.currentBounds.inThrows.add(variables[i].prototype()); break; } } } }
    JLS 18.5.1 Invocation Applicability Inference.
    /** JLS 18.5.1 Invocation Applicability Inference. */
    public void inferInvocationApplicability(MethodBinding method, TypeBinding[] arguments, boolean isDiamond) { ConstraintExpressionFormula.inferInvocationApplicability(this, method, arguments, isDiamond, this.inferenceKind); }
    Perform steps from JLS 18.5.2. needed for computing the bound set B3.
    /** Perform steps from JLS 18.5.2. needed for computing the bound set B3. */
    boolean computeB3(InvocationSite invocationSite, TypeBinding targetType, MethodBinding method) throws InferenceFailureException { boolean result = ConstraintExpressionFormula.inferPolyInvocationType(this, invocationSite, targetType, method); if (result) { mergeInnerBounds(); if (this.b3 == null) this.b3 = this.currentBounds.copy(); } return result; }
    JLS 18.5.2 Invocation Type Inference
    /** JLS 18.5.2 Invocation Type Inference */
    public BoundSet inferInvocationType(TypeBinding expectedType, InvocationSite invocationSite, MethodBinding method) throws InferenceFailureException { // not JLS: simply ensure that null hints from the return type have been seen even in standalone contexts: if (expectedType == null && method.returnType != null) substitute(method.returnType); // result is ignore, the only effect is on InferenceVariable.nullHints this.currentBounds = this.b2.copy(); int step = (expectedType == null || expectedType.isProperType(true)) ? TYPE_INFERRED_FINAL : TYPE_INFERRED; try { // bullets 1&2: definitions only. if (expectedType != null && expectedType != TypeBinding.VOID && invocationSite instanceof Expression && ((Expression) invocationSite).isTrulyExpression() && ((Expression)invocationSite).isPolyExpression(method)) { // 3. bullet: special treatment for poly expressions if (!computeB3(invocationSite, expectedType, method)) { return null; } } else { mergeInnerBounds(); this.b3 = this.currentBounds.copy(); } if (SHOULD_WORKAROUND_BUG_JDK_8153748) { // "before 18.5.2", but should not spill into b3 ... (heuristically) ReductionResult jdk8153748result = addJDK_8153748ConstraintsFromInvocation(this.invocationArguments, method, new InferenceSubstitution(this)); if (jdk8153748result != null) { if (!this.currentBounds.incorporate(this)) return null; } } pushBoundsToOuter(); this.directlyAcceptingInnerBounds = true; // 4. bullet: assemble C: Set<ConstraintFormula> c = new HashSet<ConstraintFormula>(); if (!addConstraintsToC(this.invocationArguments, c, method, this.inferenceKind, invocationSite)) return null; // 5. bullet: determine B4 from C List<Set<InferenceVariable>> components = this.currentBounds.computeConnectedComponents(this.inferenceVariables); while (!c.isEmpty()) { // * Set<ConstraintFormula> bottomSet = findBottomSet(c, allOutputVariables(c), components); if (bottomSet.isEmpty()) { bottomSet.add(pickFromCycle(c)); } // * c.removeAll(bottomSet); // * The union of the input variables of all the selected constraints, α1, ..., αm, ... Set<InferenceVariable> allInputs = new HashSet<InferenceVariable>(); Iterator<ConstraintFormula> bottomIt = bottomSet.iterator(); while (bottomIt.hasNext()) { allInputs.addAll(bottomIt.next().inputVariables(this)); } InferenceVariable[] variablesArray = allInputs.toArray(new InferenceVariable[allInputs.size()]); // ... is resolved if (!this.currentBounds.incorporate(this)) return null; BoundSet solution = resolve(variablesArray); // in rare cases resolving just one set of variables doesn't suffice, // don't bother with finding the necessary superset, just resolve all: if (solution == null) solution = resolve(this.inferenceVariables); // * ~ apply substitutions to all constraints: bottomIt = bottomSet.iterator(); while (bottomIt.hasNext()) { ConstraintFormula constraint = bottomIt.next(); if (solution != null) if (!constraint.applySubstitution(solution, variablesArray)) return null; // * reduce and incorporate if (!this.currentBounds.reduceOneConstraint(this, constraint)) return null; } } // 6. bullet: solve BoundSet solution = solve(); if (solution == null || !isResolved(solution)) { this.currentBounds = this.b2; // don't let bounds from unsuccessful attempt leak into subsequent attempts return null; } // we're done, start reporting: reportUncheckedConversions(solution); if (step == TYPE_INFERRED_FINAL) this.currentBounds = solution; // this is final, keep the result: return solution; } finally { this.stepCompleted = step; } } // --- not per JLS: emulate how javac passes type bounds from inner to outer: ---
    Not per JLS: push current bounds to outer inference if outer is ready for it.
    /** Not per JLS: push current bounds to outer inference if outer is ready for it. */
    private void pushBoundsToOuter() { pushBoundsTo(this.outerContext); }
    Not per JLS: invent more bubbling up of inner bounds.
    /** Not per JLS: invent more bubbling up of inner bounds. */
    public void pushBoundsTo(InferenceContext18 outer) { if (outer != null && outer.stepCompleted >= APPLICABILITY_INFERRED) { boolean deferred = outer.currentInvocation instanceof Invocation; // need to wait till after overload resolution? BoundSet toPush = deferred ? this.currentBounds.copy() : this.currentBounds; Runnable job = () -> { if (outer.directlyAcceptingInnerBounds) { outer.currentBounds.addBounds(toPush, this.environment); } else if (outer.innerInbox == null) { outer.innerInbox = deferred ? toPush : toPush.copy(); // copy now, unless already copied on behalf of 'deferred' } else { outer.innerInbox.addBounds(toPush, this.environment); } }; if (deferred) { this.pushToOuterJob = job; } else { job.run(); // TODO(stephan): ever reached? for ReferenceExpression? (would need a corresponding new call to flushBoundOutbox()). } } }
    Not JLS: after overload resolution is done, perform the push of type bounds to outer inference, if any.
    /** Not JLS: after overload resolution is done, perform the push of type bounds to outer inference, if any. */
    public void flushBoundOutbox() { if (this.pushToOuterJob != null) { this.pushToOuterJob.run(); this.pushToOuterJob = null; } }
    Not JLS: merge pending bounds of inner inference into current.
    /** Not JLS: merge pending bounds of inner inference into current. */
    private void mergeInnerBounds() { if (this.innerInbox != null) { this.currentBounds.addBounds(this.innerInbox, this.environment); this.innerInbox = null; } } interface InferenceOperation { boolean perform() throws InferenceFailureException; }
    Not per JLS: if operation succeeds merge new bounds from inner into current.
    /** Not per JLS: if operation succeeds merge new bounds from inner into current. */
    private boolean collectingInnerBounds(InferenceOperation operation) throws InferenceFailureException { boolean result = operation.perform(); if (result) mergeInnerBounds(); else this.innerInbox = null; return result; } // --- private ReductionResult addJDK_8153748ConstraintsFromInvocation(Expression[] arguments, MethodBinding method, InferenceSubstitution substitution) throws InferenceFailureException { // not per JLS, trying to mimic javac behavior boolean constraintAdded = false; if (arguments != null) { for (int i = 0; i < arguments.length; i++) { Expression argument = arguments[i]; TypeBinding parameter = getParameter(method.parameters, i, method.isVarargs()); parameter = substitution.substitute(substitution, parameter); ReductionResult result = addJDK_8153748ConstraintsFromExpression(argument, parameter, method, substitution); if (result == ReductionResult.FALSE) return ReductionResult.FALSE; if (result == ReductionResult.TRUE) constraintAdded = true; } } return constraintAdded ? ReductionResult.TRUE : null; } private ReductionResult addJDK_8153748ConstraintsFromExpression(Expression argument, TypeBinding parameter, MethodBinding method, InferenceSubstitution substitution) throws InferenceFailureException { if (argument instanceof FunctionalExpression) { return addJDK_8153748ConstraintsFromFunctionalExpr((FunctionalExpression) argument, parameter, method); } else if (argument instanceof Invocation && argument.isPolyExpression(method)) { Invocation invocation = (Invocation) argument; Expression[] innerArgs = invocation.arguments(); MethodBinding innerMethod = invocation.binding(); if (innerMethod != null && innerMethod.isValidBinding()) { substitution = enrichSubstitution(substitution, invocation, innerMethod); return addJDK_8153748ConstraintsFromInvocation(innerArgs, innerMethod.shallowOriginal(), substitution); } } else if (argument instanceof ConditionalExpression) { ConditionalExpression ce = (ConditionalExpression) argument; if (addJDK_8153748ConstraintsFromExpression(ce.valueIfTrue, parameter, method, substitution) == ReductionResult.FALSE) return ReductionResult.FALSE; return addJDK_8153748ConstraintsFromExpression(ce.valueIfFalse, parameter, method, substitution); } else if (argument instanceof SwitchExpression) { SwitchExpression se = (SwitchExpression) argument; ReductionResult result = ReductionResult.FALSE; for (Expression re : se.resultExpressions) { result = addJDK_8153748ConstraintsFromExpression(re, parameter, method, substitution); if (result == ReductionResult.FALSE) break; } return result; } return null; } private ReductionResult addJDK_8153748ConstraintsFromFunctionalExpr(FunctionalExpression functionalExpr, TypeBinding targetType, MethodBinding method) throws InferenceFailureException { if (!functionalExpr.isPertinentToApplicability(targetType, method)) { ConstraintFormula exprConstraint = new ConstraintExpressionFormula(functionalExpr, targetType, ReductionResult.COMPATIBLE, ARGUMENT_CONSTRAINTS_ARE_SOFT); if (collectingInnerBounds(() -> exprConstraint.inputVariables(this).isEmpty())) { // input variable would signal: not ready for inference if (!collectingInnerBounds(() -> reduceAndIncorporate(exprConstraint))) return ReductionResult.FALSE; ConstraintFormula excConstraint = new ConstraintExceptionFormula(functionalExpr, targetType); // ?? if (!collectingInnerBounds(() -> reduceAndIncorporate(excConstraint))) return ReductionResult.FALSE; return ReductionResult.TRUE; } } return null; } InferenceSubstitution enrichSubstitution(InferenceSubstitution substitution, Invocation innerInvocation, MethodBinding innerMethod) { if (innerMethod instanceof ParameterizedGenericMethodBinding) { InferenceContext18 innerContext = innerInvocation.getInferenceContext((ParameterizedMethodBinding) innerMethod); if (innerContext != null) return substitution.addContext(innerContext); } return substitution; } private boolean addConstraintsToC(Expression[] exprs, Set<ConstraintFormula> c, MethodBinding method, int inferenceKindForMethod, InvocationSite site) throws InferenceFailureException { TypeBinding[] fs; if (exprs != null) { int k = exprs.length; int p = method.parameters.length; if (method.isVarargs()) { if (k < p - 1) return false; } else if (k != p) { return false; } switch (inferenceKindForMethod) { case CHECK_STRICT: case CHECK_LOOSE: fs = method.parameters; break; case CHECK_VARARG: fs = varArgTypes(method.parameters, k); break; default: throw new IllegalStateException("Unexpected checkKind "+this.inferenceKind); //$NON-NLS-1$ } for (int i = 0; i < k; i++) { TypeBinding fsi = fs[Math.min(i, p-1)]; InferenceSubstitution inferenceSubstitution = new InferenceSubstitution(this.environment, this.inferenceVariables, site); TypeBinding substF = inferenceSubstitution.substitute(inferenceSubstitution,fsi); if (!addConstraintsToC_OneExpr(exprs[i], c, fsi, substF, method)) return false; } } return true; } private boolean addConstraintsToC_OneExpr(Expression expri, Set<ConstraintFormula> c, TypeBinding fsi, TypeBinding substF, MethodBinding method) throws InferenceFailureException { boolean substFIsProperType = substF.isProperType(true); // -- not per JLS, emulate javac behavior: substF = Scope.substitute(getResultSubstitution(this.b3), substF); // -- // For all i (1 ≤ i ≤ k), if ei is not pertinent to applicability, the set contains ⟨ei → θ Fi⟩. if (!expri.isPertinentToApplicability(fsi, method)) { c.add(new ConstraintExpressionFormula(expri, substF, ReductionResult.COMPATIBLE, ARGUMENT_CONSTRAINTS_ARE_SOFT)); } if (expri instanceof FunctionalExpression) { c.add(new ConstraintExceptionFormula((FunctionalExpression) expri, substF)); if (expri instanceof LambdaExpression) { // https://bugs.openjdk.java.net/browse/JDK-8038747 LambdaExpression lambda = (LambdaExpression) expri; BlockScope skope = lambda.enclosingScope; if (substF.isFunctionalInterface(skope)) { // could be an inference variable. ReferenceBinding t = (ReferenceBinding) substF; ParameterizedTypeBinding withWildCards = InferenceContext18.parameterizedWithWildcard(t); if (withWildCards != null) { t = ConstraintExpressionFormula.findGroundTargetType(this, skope, lambda, withWildCards); } MethodBinding functionType; if (t != null && (functionType = t.getSingleAbstractMethod(skope, true)) != null && (lambda = lambda.resolveExpressionExpecting(t, this.scope, this)) != null) { TypeBinding r = functionType.returnType; Expression[] resultExpressions = lambda.resultExpressions(); for (int i = 0, length = resultExpressions == null ? 0 : resultExpressions.length; i < length; i++) { Expression resultExpression = resultExpressions[i]; if (!addConstraintsToC_OneExpr(resultExpression, c, r.original(), r, method)) return false; } } } } } else if (expri instanceof Invocation && expri.isPolyExpression()) { if (substFIsProperType) // https://bugs.openjdk.java.net/browse/JDK-8052325 return true; Invocation invocation = (Invocation) expri; MethodBinding innerMethod = invocation.binding(); if (innerMethod == null) return true; // -> proceed with no new C set elements. Expression[] arguments = invocation.arguments(); TypeBinding[] argumentTypes = arguments == null ? Binding.NO_PARAMETERS : new TypeBinding[arguments.length]; for (int i = 0; i < argumentTypes.length; i++) argumentTypes[i] = arguments[i].resolvedType; InferenceContext18 innerContext = null; if (innerMethod instanceof ParameterizedGenericMethodBinding) innerContext = invocation.getInferenceContext((ParameterizedGenericMethodBinding) innerMethod); if (innerContext != null) { MethodBinding shallowMethod = innerMethod.shallowOriginal(); innerContext.outerContext = this; if (innerContext.stepCompleted < InferenceContext18.APPLICABILITY_INFERRED) // shouldn't happen, but let's play safe innerContext.inferInvocationApplicability(shallowMethod, argumentTypes, shallowMethod.isConstructor()); if (!innerContext.computeB3(invocation, substF, shallowMethod)) return false; if (innerContext.addConstraintsToC(arguments, c, innerMethod.genericMethod(), innerContext.inferenceKind, invocation)) { this.currentBounds.addBounds(innerContext.currentBounds, this.environment); return true; } return false; } else { int applicabilityKind = getInferenceKind(innerMethod, argumentTypes); return this.addConstraintsToC(arguments, c, innerMethod.genericMethod(), applicabilityKind, invocation); } } else if (expri instanceof ConditionalExpression) { ConditionalExpression ce = (ConditionalExpression) expri; return addConstraintsToC_OneExpr(ce.valueIfTrue, c, fsi, substF, method) && addConstraintsToC_OneExpr(ce.valueIfFalse, c, fsi, substF, method); } else if (expri instanceof SwitchExpression) { SwitchExpression se = (SwitchExpression) expri; for (Expression re : se.resultExpressions) { if (!addConstraintsToC_OneExpr(re, c, fsi, substF, method)) return false; } return true; } return true; } protected int getInferenceKind(MethodBinding nonGenericMethod, TypeBinding[] argumentTypes) { switch (this.scope.parameterCompatibilityLevel(nonGenericMethod, argumentTypes)) { case Scope.AUTOBOX_COMPATIBLE: return CHECK_LOOSE; case Scope.VARARGS_COMPATIBLE: return CHECK_VARARG; default: return CHECK_STRICT; } }
    18.5.3 Functional Interface Parameterization Inference
    /** * 18.5.3 Functional Interface Parameterization Inference */
    public ReferenceBinding inferFunctionalInterfaceParameterization(LambdaExpression lambda, BlockScope blockScope, ParameterizedTypeBinding targetTypeWithWildCards) { TypeBinding[] q = createBoundsForFunctionalInterfaceParameterizationInference(targetTypeWithWildCards); if (q == null || q.length != lambda.arguments().length) { // fail TODO: can this still happen here? } else { if (reduceWithEqualityConstraints(lambda.argumentTypes(), q)) { ReferenceBinding genericType = targetTypeWithWildCards.genericType(); TypeBinding[] a = targetTypeWithWildCards.arguments; // a is not-null by construction of parameterizedWithWildcard() TypeBinding[] aprime = getFunctionInterfaceArgumentSolutions(a); // If F<A'1, ..., A'm> is a well-formed type, ... ParameterizedTypeBinding ptb = blockScope.environment().createParameterizedType(genericType, aprime, targetTypeWithWildCards.enclosingType()); TypeVariableBinding[] vars = ptb.genericType().typeVariables(); ParameterizedTypeBinding captured = ptb.capture(blockScope, lambda.sourceStart, lambda.sourceEnd); for (int i = 0; i < vars.length; i++) { if (vars[i].boundCheck(captured, aprime[i], blockScope, lambda) == BoundCheckStatus.MISMATCH) return null; } return ptb; } } return targetTypeWithWildCards; }
    Create initial bound set for 18.5.3 Functional Interface Parameterization Inference
    Params:
    • functionalInterface – the functional interface F
    Returns:the parameter types Q1..Qk of the function type of the type F, or null
    /** * Create initial bound set for 18.5.3 Functional Interface Parameterization Inference * @param functionalInterface the functional interface F<A1,..Am> * @return the parameter types Q1..Qk of the function type of the type F<α1, ..., αm>, or null */
    TypeBinding[] createBoundsForFunctionalInterfaceParameterizationInference(ParameterizedTypeBinding functionalInterface) { if (this.currentBounds == null) this.currentBounds = new BoundSet(); TypeBinding[] a = functionalInterface.arguments; if (a == null) return null; InferenceVariable[] alpha = addInitialTypeVariableSubstitutions(a); for (int i = 0; i < a.length; i++) { TypeBound bound; if (a[i].kind() == Binding.WILDCARD_TYPE) { WildcardBinding wildcard = (WildcardBinding) a[i]; switch(wildcard.boundKind) { case Wildcard.EXTENDS : bound = new TypeBound(alpha[i], wildcard.allBounds(), ReductionResult.SUBTYPE); break; case Wildcard.SUPER : bound = new TypeBound(alpha[i], wildcard.bound, ReductionResult.SUPERTYPE); break; case Wildcard.UNBOUND : bound = new TypeBound(alpha[i], this.object, ReductionResult.SUBTYPE); break; default: continue; // cannot } } else { bound = new TypeBound(alpha[i], a[i], ReductionResult.SAME); } this.currentBounds.addBound(bound, this.environment); } TypeBinding falpha = substitute(functionalInterface); return falpha.getSingleAbstractMethod(this.scope, true).parameters; } public boolean reduceWithEqualityConstraints(TypeBinding[] p, TypeBinding[] q) { if (p != null) { for (int i = 0; i < p.length; i++) { try { if (!this.reduceAndIncorporate(ConstraintTypeFormula.create(p[i], q[i], ReductionResult.SAME))) return false; } catch (InferenceFailureException e) { return false; } } } return true; }
    18.5.4 More Specific Method Inference
    /** * 18.5.4 More Specific Method Inference */
    public boolean isMoreSpecificThan(MethodBinding m1, MethodBinding m2, boolean isVarArgs, boolean isVarArgs2) { // TODO: we don't yet distinguish vararg-with-passthrough from vararg-with-exactly-one-vararg-arg if (isVarArgs != isVarArgs2) { return isVarArgs2; } Expression[] arguments = this.invocationArguments; int numInvocArgs = arguments == null ? 0 : arguments.length; TypeVariableBinding[] p = m2.typeVariables(); TypeBinding[] s = m1.parameters; TypeBinding[] t = new TypeBinding[m2.parameters.length]; createInitialBoundSet(p); for (int i = 0; i < t.length; i++) t[i] = substitute(m2.parameters[i]); try { for (int i = 0; i < numInvocArgs; i++) { TypeBinding si = getParameter(s, i, isVarArgs); TypeBinding ti = getParameter(t, i, isVarArgs); Boolean result = moreSpecificMain(si, ti, this.invocationArguments[i]); if (result == Boolean.FALSE) return false; if (result == null) if (!reduceAndIncorporate(ConstraintTypeFormula.create(si, ti, ReductionResult.SUBTYPE))) return false; } if (t.length == numInvocArgs + 1) { TypeBinding skplus1 = getParameter(s, numInvocArgs, true); TypeBinding tkplus1 = getParameter(t, numInvocArgs, true); if (!reduceAndIncorporate(ConstraintTypeFormula.create(skplus1, tkplus1, ReductionResult.SUBTYPE))) return false; } return solve() != null; } catch (InferenceFailureException e) { return false; } } // FALSE: inference fails // TRUE: constraints have been incorporated // null: need to create the si <: ti constraint private Boolean moreSpecificMain(TypeBinding si, TypeBinding ti, Expression expri) throws InferenceFailureException { if (si.isProperType(true) && ti.isProperType(true)) { return expri.sIsMoreSpecific(si, ti, this.scope) ? Boolean.TRUE : Boolean.FALSE; } // "if Ti is not a functional interface type" specifically requests the si <: ti constraint created by our caller if (!ti.isFunctionalInterface(this.scope)) return null; TypeBinding funcI = ti.original(); // "It must be determined whether Si satisfies the following five conditions:" // (we negate each condition for early exit): if (si.isFunctionalInterface(this.scope)) { // bullet 1 if (siSuperI(si, funcI) || siSubI(si, funcI)) return null; // bullets 2 & 3 if (si instanceof IntersectionTypeBinding18) { TypeBinding[] elements = ((IntersectionTypeBinding18)si).intersectingTypes; checkSuper: { for (int i = 0; i < elements.length; i++) if (!siSuperI(elements[i], funcI)) break checkSuper; return null; // bullet 4 // each element of the intersection is a superinterface of I, or a parameterization of a superinterface of I. } for (int i = 0; i < elements.length; i++) if (siSubI(elements[i], funcI)) return null; // bullet 5 // some element of the intersection is a subinterface of I, or a parameterization of a subinterface of I. } // all passed, time to do some work: TypeBinding siCapture = si.capture(this.scope, expri.sourceStart, expri.sourceEnd); MethodBinding sam = siCapture.getSingleAbstractMethod(this.scope, false); // no wildcards should be left needing replacement TypeBinding[] u = sam.parameters; TypeBinding r1 = sam.isConstructor() ? sam.declaringClass : sam.returnType; sam = ti.getSingleAbstractMethod(this.scope, true); // TODO TypeBinding[] v = sam.parameters; TypeBinding r2 = sam.isConstructor() ? sam.declaringClass : sam.returnType; return Boolean.valueOf(checkExpression(expri, u, r1, v, r2)); } return null; } private boolean checkExpression(Expression expri, TypeBinding[] u, TypeBinding r1, TypeBinding[] v, TypeBinding r2) throws InferenceFailureException { if (expri instanceof LambdaExpression && !((LambdaExpression)expri).argumentsTypeElided()) { for (int i = 0; i < u.length; i++) { if (!reduceAndIncorporate(ConstraintTypeFormula.create(u[i], v[i], ReductionResult.SAME))) return false; } if (r2.id == TypeIds.T_void) return true; LambdaExpression lambda = (LambdaExpression) expri; Expression[] results = lambda.resultExpressions(); if (results != Expression.NO_EXPRESSIONS) { if (r1.isFunctionalInterface(this.scope) && r2.isFunctionalInterface(this.scope) && !(r1.isCompatibleWith(r2) || r2.isCompatibleWith(r1))) { // "these rules are applied recursively to R1 and R2, for each result expression in expi." // (what does "applied .. to R1 and R2" mean? Why mention R1/R2 and not U/V?) for (int i = 0; i < results.length; i++) { if (!checkExpression(results[i], u, r1, v, r2)) return false; } return true; } checkPrimitive1: if (r1.isPrimitiveType() && !r2.isPrimitiveType()) { // check: each result expression is a standalone expression of a primitive type for (int i = 0; i < results.length; i++) { if (results[i].isPolyExpression() || (results[i].resolvedType != null && !results[i].resolvedType.isPrimitiveType())) break checkPrimitive1; } return true; } checkPrimitive2: if (r2.isPrimitiveType() && !r1.isPrimitiveType()) { for (int i = 0; i < results.length; i++) { // for all expressions (not for any expression not) if (!( (!results[i].isPolyExpression() && (results[i].resolvedType != null && !results[i].resolvedType.isPrimitiveType())) // standalone of a referencetype || results[i].isPolyExpression())) // or a poly break checkPrimitive2; } return true; } } return reduceAndIncorporate(ConstraintTypeFormula.create(r1, r2, ReductionResult.SUBTYPE)); } else if (expri instanceof ReferenceExpression && ((ReferenceExpression)expri).isExactMethodReference()) { ReferenceExpression reference = (ReferenceExpression) expri; for (int i = 0; i < u.length; i++) { if (!reduceAndIncorporate(ConstraintTypeFormula.create(u[i], v[i], ReductionResult.SAME))) return false; } if (r2.id == TypeIds.T_void) return true; MethodBinding method = reference.getExactMethod(); TypeBinding returnType = method.isConstructor() ? method.declaringClass : method.returnType; if (r1.isPrimitiveType() && !r2.isPrimitiveType() && returnType.isPrimitiveType()) return true; if (r2.isPrimitiveType() && !r1.isPrimitiveType() && !returnType.isPrimitiveType()) return true; return reduceAndIncorporate(ConstraintTypeFormula.create(r1, r2, ReductionResult.SUBTYPE)); } else if (expri instanceof ConditionalExpression) { ConditionalExpression cond = (ConditionalExpression) expri; return checkExpression(cond.valueIfTrue, u, r1, v, r2) && checkExpression(cond.valueIfFalse, u, r1, v, r2); } else if (expri instanceof SwitchExpression) { SwitchExpression se = (SwitchExpression) expri; for (Expression re : se.resultExpressions) { if (!checkExpression(re, u, r1, v, r2)) return false; } return true; } else { return false; } } private boolean siSuperI(TypeBinding si, TypeBinding funcI) { if (TypeBinding.equalsEquals(si, funcI) || TypeBinding.equalsEquals(si.original(), funcI)) return true; TypeBinding[] superIfcs = funcI.superInterfaces(); if (superIfcs == null) return false; for (int i = 0; i < superIfcs.length; i++) { if (siSuperI(si, superIfcs[i].original())) return true; } return false; } private boolean siSubI(TypeBinding si, TypeBinding funcI) { if (TypeBinding.equalsEquals(si, funcI) || TypeBinding.equalsEquals(si.original(), funcI)) return true; TypeBinding[] superIfcs = si.superInterfaces(); if (superIfcs == null) return false; for (int i = 0; i < superIfcs.length; i++) { if (siSubI(superIfcs[i], funcI)) return true; } return false; } // ========== Below this point: implementation of the generic algorithm: ==========
    Try to solve the inference problem defined by constraints and bounds previously registered.
    Throws:
    Returns:a bound set representing the solution, or null if inference failed
    /** * Try to solve the inference problem defined by constraints and bounds previously registered. * @return a bound set representing the solution, or null if inference failed * @throws InferenceFailureException a compile error has been detected during inference */
    public /*@Nullable*/ BoundSet solve(boolean inferringApplicability) throws InferenceFailureException { if (!reduce()) return null; if (!this.currentBounds.incorporate(this)) return null; if (inferringApplicability) this.b2 = this.currentBounds.copy(); // Preserve the result after reduction, without effects of resolve() for later use in invocation type inference. BoundSet solution = resolve(this.inferenceVariables); /* If inferring applicability make a final pass over the initial constraints preserved as final constraints to make sure they hold true at a macroscopic level. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=426537#c55 onwards. */ if (inferringApplicability && solution != null && this.finalConstraints != null) { for (ConstraintExpressionFormula constraint: this.finalConstraints) { if (constraint.left.isPolyExpression()) continue; // avoid redundant re-inference, inner poly's own constraints get validated in its own context & poly invocation type inference proved compatibility against target. constraint.applySubstitution(solution, this.inferenceVariables); if (!this.currentBounds.reduceOneConstraint(this, constraint)) { return null; } } } return solution; } public /*@Nullable*/ BoundSet solve() throws InferenceFailureException { return solve(false); } public /*@Nullable*/ BoundSet solve(InferenceVariable[] toResolve) throws InferenceFailureException { if (!reduce()) return null; if (!this.currentBounds.incorporate(this)) return null; return resolve(toResolve); }
    JLS 18.2. reduce all initial constraints
    Throws:
    • InferenceFailureException –
    /** * JLS 18.2. reduce all initial constraints * @throws InferenceFailureException */
    private boolean reduce() throws InferenceFailureException { // Caution: This can be reentered recursively even as an earlier call is munching through the constraints ! for (int i = 0; this.initialConstraints != null && i < this.initialConstraints.length; i++) { final ConstraintFormula currentConstraint = this.initialConstraints[i]; if (currentConstraint == null) continue; this.initialConstraints[i] = null; if (!this.currentBounds.reduceOneConstraint(this, currentConstraint)) return false; } this.initialConstraints = null; return true; }
    Have all inference variables been instantiated successfully?
    /** * Have all inference variables been instantiated successfully? */
    public boolean isResolved(BoundSet boundSet) { if (this.inferenceVariables != null) { for (int i = 0; i < this.inferenceVariables.length; i++) { if (!boundSet.isInstantiated(this.inferenceVariables[i])) return false; } } return true; }
    Retrieve the resolved solutions for all given type variables.
    Params:
    • typeParameters –
    • boundSet – where instantiations are to be found
    Returns:array containing the substituted types or null elements for any type variable that could not be substituted.
    /** * Retrieve the resolved solutions for all given type variables. * @param typeParameters * @param boundSet where instantiations are to be found * @return array containing the substituted types or <code>null</code> elements for any type variable that could not be substituted. */
    public TypeBinding /*@Nullable*/[] getSolutions(TypeVariableBinding[] typeParameters, InvocationSite site, BoundSet boundSet) { int len = typeParameters.length; TypeBinding[] substitutions = new TypeBinding[len]; InferenceVariable[] outerVariables = null; if (this.outerContext != null && this.outerContext.stepCompleted < TYPE_INFERRED) outerVariables = this.outerContext.inferenceVariables; for (int i = 0; i < typeParameters.length; i++) { for (int j = 0; j < this.inferenceVariables.length; j++) { InferenceVariable variable = this.inferenceVariables[j]; if (isSameSite(variable.site, site) && TypeBinding.equalsEquals(variable.typeParameter, typeParameters[i])) { TypeBinding outerVar = null; if (outerVariables != null && (outerVar = boundSet.getEquivalentOuterVariable(variable, outerVariables)) != null) substitutions[i] = outerVar; else substitutions[i] = boundSet.getInstantiation(variable, this.environment); break; } } if (substitutions[i] == null) return null; } return substitutions; }
    When inference produces a new constraint, reduce it to a suitable type bound and add the latter to the bound set.
    /** When inference produces a new constraint, reduce it to a suitable type bound and add the latter to the bound set. */
    public boolean reduceAndIncorporate(ConstraintFormula constraint) throws InferenceFailureException { return this.currentBounds.reduceOneConstraint(this, constraint); // TODO(SH): should we immediately call a diat incorporate, or can we simply wait for the next round? }
    JLS 18.4 Resolution
    Throws:
    Returns:answer null if some constraint resolved to FALSE, otherwise the boundset representing the solution
    /** * <b>JLS 18.4</b> Resolution * @return answer null if some constraint resolved to FALSE, otherwise the boundset representing the solution * @throws InferenceFailureException */
    private /*@Nullable*/ BoundSet resolve(InferenceVariable[] toResolve) throws InferenceFailureException { this.captureId = 0; // NOTE: 18.5.2 ... // "(While it was necessary to demonstrate that the inference variables in B1 could be resolved // in order to establish applicability, the resulting instantiations are not considered part of B1.) // For this reason, resolve works on a temporary bound set, copied before any modification. BoundSet tmpBoundSet = this.currentBounds; if (this.inferenceVariables != null) { // find a minimal set of dependent variables: Set<InferenceVariable> variableSet; while ((variableSet = getSmallestVariableSet(tmpBoundSet, toResolve)) != null) { int oldNumUninstantiated = tmpBoundSet.numUninstantiatedVariables(this.inferenceVariables); final int numVars = variableSet.size(); if (numVars > 0) { final InferenceVariable[] variables = variableSet.toArray(new InferenceVariable[numVars]); variables: if (!tmpBoundSet.hasCaptureBound(variableSet)) { // try to instantiate this set of variables in a fresh copy of the bound set: BoundSet prevBoundSet = tmpBoundSet; tmpBoundSet = tmpBoundSet.copy(); for (int j = 0; j < variables.length; j++) { InferenceVariable variable = variables[j]; // try lower bounds: TypeBinding[] lowerBounds = tmpBoundSet.lowerBounds(variable, true/*onlyProper*/); if (lowerBounds != Binding.NO_TYPES) { TypeBinding lub = this.scope.lowerUpperBound(lowerBounds); if (lub == TypeBinding.VOID || lub == null) return null; tmpBoundSet.addBound(new TypeBound(variable, lub, ReductionResult.SAME), this.environment); } else { TypeBinding[] upperBounds = tmpBoundSet.upperBounds(variable, true/*onlyProper*/); // check exception bounds: if (tmpBoundSet.inThrows.contains(variable.prototype()) && tmpBoundSet.hasOnlyTrivialExceptionBounds(variable, upperBounds)) { TypeBinding runtimeException = this.scope.getType(TypeConstants.JAVA_LANG_RUNTIMEEXCEPTION, 3); tmpBoundSet.addBound(new TypeBound(variable, runtimeException, ReductionResult.SAME), this.environment); } else { // try upper bounds: TypeBinding glb = this.object; if (upperBounds != Binding.NO_TYPES) { if (upperBounds.length == 1) { glb = upperBounds[0]; } else { TypeBinding[] glbs = Scope.greaterLowerBound(upperBounds, this.scope, this.environment); if (glbs == null) { return null; } else if (glbs.length == 1) { glb = glbs[0]; } else { glb = intersectionFromGlb(glbs); if (glb == null) { // inconsistent intersection tmpBoundSet = prevBoundSet; // clean up break variables; // and start over } } } } tmpBoundSet.addBound(new TypeBound(variable, glb, ReductionResult.SAME), this.environment); } } } if (tmpBoundSet.incorporate(this)) continue; tmpBoundSet = prevBoundSet;// clean-up for second attempt } // Otherwise, a second attempt is made... Sorting.sortInferenceVariables(variables); // ensure stability of capture IDs final CaptureBinding18[] zs = new CaptureBinding18[numVars]; for (int j = 0; j < numVars; j++) zs[j] = freshCapture(variables[j]); final BoundSet kurrentBoundSet = tmpBoundSet; Substitution theta = new Substitution() { @Override public LookupEnvironment environment() { return InferenceContext18.this.environment; } @Override public boolean isRawSubstitution() { return false; } @Override public TypeBinding substitute(TypeVariableBinding typeVariable) { for (int j = 0; j < numVars; j++) if (TypeBinding.equalsEquals(variables[j], typeVariable)) return zs[j]; /* If we have an instantiation, lower it to the instantiation. We don't want downstream abstractions to be confused about multiple versions of bounds without and with instantiations propagated by incorporation. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=430686. There is no value whatsoever in continuing to speak in two tongues. Also fixes https://bugs.eclipse.org/bugs/show_bug.cgi?id=425031. */ if (typeVariable instanceof InferenceVariable) { InferenceVariable inferenceVariable = (InferenceVariable) typeVariable; TypeBinding instantiation = kurrentBoundSet.getInstantiation(inferenceVariable, null); if (instantiation != null) return instantiation; } return typeVariable; } }; for (int j = 0; j < numVars; j++) { InferenceVariable variable = variables[j]; CaptureBinding18 zsj = zs[j]; // add lower bounds: TypeBinding[] lowerBounds = tmpBoundSet.lowerBounds(variable, true/*onlyProper*/); if (lowerBounds != Binding.NO_TYPES) { TypeBinding lub = this.scope.lowerUpperBound(lowerBounds); if (lub != TypeBinding.VOID && lub != null) zsj.lowerBound = lub; } // add upper bounds: TypeBinding[] upperBounds = tmpBoundSet.upperBounds(variable, false/*onlyProper*/); if (upperBounds != Binding.NO_TYPES) { for (int k = 0; k < upperBounds.length; k++) upperBounds[k] = Scope.substitute(theta, upperBounds[k]); if (!setUpperBounds(zsj, upperBounds)) continue; // at violation of well-formedness skip this candidate and proceed } if (tmpBoundSet == this.currentBounds) tmpBoundSet = tmpBoundSet.copy(); Iterator<ParameterizedTypeBinding> captureKeys = tmpBoundSet.captures.keySet().iterator(); Set<ParameterizedTypeBinding> toRemove = new HashSet<ParameterizedTypeBinding>(); while (captureKeys.hasNext()) { ParameterizedTypeBinding key = captureKeys.next(); int len = key.arguments.length; for (int i = 0; i < len; i++) { if (TypeBinding.equalsEquals(key.arguments[i], variable)) { toRemove.add(key); break; } } } captureKeys = toRemove.iterator(); while (captureKeys.hasNext()) tmpBoundSet.captures.remove(captureKeys.next()); tmpBoundSet.addBound(new TypeBound(variable, zsj, ReductionResult.SAME), this.environment); } if (tmpBoundSet.incorporate(this)) { if (tmpBoundSet.numUninstantiatedVariables(this.inferenceVariables) == oldNumUninstantiated) return null; // abort because we made no progress continue; } return null; } } } return tmpBoundSet; } private TypeBinding intersectionFromGlb(TypeBinding[] glbs) { ReferenceBinding[] refGlbs = new ReferenceBinding[glbs.length]; for (int i = 0; i < glbs.length; i++) { TypeBinding typeBinding = glbs[i]; if (typeBinding instanceof ReferenceBinding) { refGlbs[i] = (ReferenceBinding) typeBinding; } else { return null; } } IntersectionTypeBinding18 intersection = (IntersectionTypeBinding18) this.environment.createIntersectionType18(refGlbs); if (ReferenceBinding.isConsistentIntersection(intersection.intersectingTypes)) return intersection; return null; } int captureId = 0;
    For 18.4: "Let Z1, ..., Zn be fresh type variables" use capture bindings.
    /** For 18.4: "Let Z1, ..., Zn be fresh type variables" use capture bindings. */
    private CaptureBinding18 freshCapture(InferenceVariable variable) { int id = this.captureId++; char[] sourceName = CharOperation.concat("Z".toCharArray(), '#', String.valueOf(id).toCharArray(), '-', variable.sourceName); //$NON-NLS-1$ int start = this.currentInvocation != null ? this.currentInvocation.sourceStart() : 0; int end = this.currentInvocation != null ? this.currentInvocation.sourceEnd() : 0; return new CaptureBinding18(this.scope.enclosingSourceType(), sourceName, variable.typeParameter.shortReadableName(), start, end, id, this.environment); } // === === private boolean setUpperBounds(CaptureBinding18 typeVariable, TypeBinding[] substitutedUpperBounds) { // 18.4: ... define the upper bound of Zi as glb(L1θ, ..., Lkθ) if (substitutedUpperBounds.length == 1) { return typeVariable.setUpperBounds(substitutedUpperBounds, this.object); // shortcut } else { TypeBinding[] glbs = Scope.greaterLowerBound(substitutedUpperBounds, this.scope, this.environment); if (glbs == null) return false; if (typeVariable.lowerBound != null) { for (int i = 0; i < glbs.length; i++) { if (!typeVariable.lowerBound.isCompatibleWith(glbs[i])) return false; // not well-formed } } // for deterministic results sort this array by id: sortTypes(glbs); if (!typeVariable.setUpperBounds(glbs, this.object)) return false; } return true; } static void sortTypes(TypeBinding[] types) { Arrays.sort(types, new Comparator<TypeBinding>() { @Override public int compare(TypeBinding o1, TypeBinding o2) { int i1 = o1.id, i2 = o2.id; return (i1<i2 ? -1 : (i1==i2 ? 0 : 1)); } }); }
    Find the smallest set of uninstantiated inference variables not depending on any uninstantiated variable outside the set.
    /** * Find the smallest set of uninstantiated inference variables not depending * on any uninstantiated variable outside the set. */
    private Set<InferenceVariable> getSmallestVariableSet(BoundSet bounds, InferenceVariable[] subSet) { // "Given a set of inference variables to resolve, let V be the union of this set and // all variables upon which the resolution of at least one variable in this set depends." Set<InferenceVariable> v = new HashSet<InferenceVariable>(); Map<InferenceVariable,Set<InferenceVariable>> dependencies = new HashMap<>(); // compute only once, store for the final loop over 'v'. for (InferenceVariable iv : subSet) { Set<InferenceVariable> tmp = new HashSet<>(); addDependencies(bounds, tmp, iv); dependencies.put(iv, tmp); v.addAll(tmp); } // "If every variable in V has an instantiation, then resolution succeeds and this procedure terminates." // -> (implicit if result remains unassigned) // "Otherwise, let { α1, ..., αn } be a non-empty subset of uninstantiated variables in V such that ... int min = Integer.MAX_VALUE; Set<InferenceVariable> result = null; // "i) for all i (1 ≤ i ≤ n), ..." for (InferenceVariable currentVariable : v) { if (!bounds.isInstantiated(currentVariable)) { // "... if αi depends on the resolution of a variable β, then either β has an instantiation or there is some j such that β = αj; ..." Set<InferenceVariable> set = dependencies.get(currentVariable); if (set == null) // not an element of the original subSet, still need to fetch this var's dependencies addDependencies(bounds, set = new HashSet<>(), currentVariable); // "... and ii) there exists no non-empty proper subset of { α1, ..., αn } with this property." int cur = set.size(); if (cur == 1) return set; // won't get smaller if (cur < min) { result = set; min = cur; } } } return result; } private void addDependencies(BoundSet boundSet, Set<InferenceVariable> variableSet, InferenceVariable currentVariable) { if (boundSet.isInstantiated(currentVariable)) return; // not added if (!variableSet.add(currentVariable)) return; // already present for (int j = 0; j < this.inferenceVariables.length; j++) { InferenceVariable nextVariable = this.inferenceVariables[j]; if (TypeBinding.equalsEquals(nextVariable, currentVariable)) continue; if (boundSet.dependsOnResolutionOf(currentVariable, nextVariable)) addDependencies(boundSet, variableSet, nextVariable); } } private ConstraintFormula pickFromCycle(Set<ConstraintFormula> c) { // Detail from 18.5.2 bullet 6.1 // Note on performance: this implementation could quite possibly be optimized a lot. // However, we only *very rarely* reach here, // so nobody should really be affected by the performance penalty paid here. // Note on spec conformance: the spec seems to require _all_ criteria (i)-(iv) to be fulfilled // with the sole exception of (iii), which should only be used, if _any_ constraints matching (i) & (ii) // also fulfill this condition. // Experiments, however, show that strict application of the above is prone to failing to pick any constraint, // causing non-termination of the algorithm. // Since that is not acceptable, I'm *interpreting* the spec to request a search for a constraint // that "best matches" the given conditions. // collect all constraints participating in a cycle HashMap<ConstraintFormula,Set<ConstraintFormula>> dependencies = new HashMap<ConstraintFormula, Set<ConstraintFormula>>(); Set<ConstraintFormula> cycles = new HashSet<ConstraintFormula>(); for (ConstraintFormula constraint : c) { Collection<InferenceVariable> infVars = constraint.inputVariables(this); for (ConstraintFormula other : c) { if (other == constraint) continue; if (dependsOn(infVars, other.outputVariables(this))) { // found a dependency, record it: Set<ConstraintFormula> targetSet = dependencies.get(constraint); if (targetSet == null) dependencies.put(constraint, targetSet = new HashSet<ConstraintFormula>()); targetSet.add(other); // look for a cycle: Set<ConstraintFormula> nodesInCycle = new HashSet<ConstraintFormula>(); if (isReachable(dependencies, other, constraint, new HashSet<ConstraintFormula>(), nodesInCycle)) { // found a cycle, record the involved nodes: cycles.addAll(nodesInCycle); } } } } Set<ConstraintFormula> outside = new HashSet<ConstraintFormula>(c); outside.removeAll(cycles); Set<ConstraintFormula> candidatesII = new HashSet<ConstraintFormula>(); // (i): participates in a cycle: candidates: for (ConstraintFormula candidate : cycles) { Collection<InferenceVariable> infVars = candidate.inputVariables(this); // (ii) does not depend on any constraints outside the cycle for (ConstraintFormula out : outside) { if (dependsOn(infVars, out.outputVariables(this))) continue candidates; } candidatesII.add(candidate); } if (candidatesII.isEmpty()) candidatesII = c; // not spec'ed but needed to avoid returning null below, witness: java.util.stream.Collectors // tentatively: (iii) has the form ⟨Expression → T⟩ Set<ConstraintFormula> candidatesIII = new HashSet<ConstraintFormula>(); for (ConstraintFormula candidate : candidatesII) { if (candidate instanceof ConstraintExpressionFormula) candidatesIII.add(candidate); } if (candidatesIII.isEmpty()) { candidatesIII = candidatesII; // no constraint fulfills (iii) -> ignore this condition } else { // candidatesIII contains all relevant constraints ⟨Expression → T⟩ // (iv) contains an expression that appears to the left of the expression // of every other constraint satisfying the previous three requirements // collect containment info regarding all expressions in candidate constraints: // (a) find minimal enclosing expressions: Map<ConstraintExpressionFormula,ConstraintExpressionFormula> expressionContainedBy = new HashMap<ConstraintExpressionFormula, ConstraintExpressionFormula>(); for (ConstraintFormula one : candidatesIII) { ConstraintExpressionFormula oneCEF = (ConstraintExpressionFormula) one; Expression exprOne = oneCEF.left; for (ConstraintFormula two : candidatesIII) { if (one == two) continue; ConstraintExpressionFormula twoCEF = (ConstraintExpressionFormula) two; Expression exprTwo = twoCEF.left; if (doesExpressionContain(exprOne, exprTwo)) { ConstraintExpressionFormula previous = expressionContainedBy.get(two); if (previous == null || doesExpressionContain(previous.left, exprOne)) // only if improving expressionContainedBy.put(twoCEF, oneCEF); } } } // (b) build the tree from the above Map<ConstraintExpressionFormula,Set<ConstraintExpressionFormula>> containmentForest = new HashMap<ConstraintExpressionFormula, Set<ConstraintExpressionFormula>>(); for (Map.Entry<ConstraintExpressionFormula, ConstraintExpressionFormula> parentRelation : expressionContainedBy.entrySet()) { ConstraintExpressionFormula parent = parentRelation.getValue(); Set<ConstraintExpressionFormula> children = containmentForest.get(parent); if (children == null) containmentForest.put(parent, children = new HashSet<ConstraintExpressionFormula>()); children.add(parentRelation.getKey()); } // approximate the spec by searching the largest containment tree: int bestRank = -1; ConstraintExpressionFormula candidate = null; for (ConstraintExpressionFormula parent : containmentForest.keySet()) { int rank = rankNode(parent, expressionContainedBy, containmentForest); if (rank > bestRank) { bestRank = rank; candidate = parent; } } if (candidate != null) return candidate; } if (candidatesIII.isEmpty()) throw new IllegalStateException("cannot pick constraint from cyclic set"); //$NON-NLS-1$ return candidatesIII.iterator().next(); }
    Does the first constraint depend on the other? The first constraint is represented by its input variables and the other constraint by its output variables.
    /** * Does the first constraint depend on the other? * The first constraint is represented by its input variables and the other constraint by its output variables. */
    private boolean dependsOn(Collection<InferenceVariable> inputsOfFirst, Collection<InferenceVariable> outputsOfOther) { for (InferenceVariable iv : inputsOfFirst) { for (InferenceVariable otherIV : outputsOfOther) if (this.currentBounds.dependsOnResolutionOf(iv, otherIV)) return true; } return false; }
    Does 'deps' contain a chain of dependencies leading from 'from' to 'to'?
    /** Does 'deps' contain a chain of dependencies leading from 'from' to 'to'? */
    private boolean isReachable(Map<ConstraintFormula,Set<ConstraintFormula>> deps, ConstraintFormula from, ConstraintFormula to, Set<ConstraintFormula> nodesVisited, Set<ConstraintFormula> nodesInCycle) { if (from == to) { nodesInCycle.add(from); return true; } if (!nodesVisited.add(from)) return false; Set<ConstraintFormula> targetSet = deps.get(from); if (targetSet != null) { for (ConstraintFormula tgt : targetSet) { if (isReachable(deps, tgt, to, nodesVisited, nodesInCycle)) { nodesInCycle.add(from); return true; } } } return false; }
    Does exprOne lexically contain exprTwo?
    /** Does exprOne lexically contain exprTwo? */
    private boolean doesExpressionContain(Expression exprOne, Expression exprTwo) { if (exprTwo.sourceStart > exprOne.sourceStart) { return exprTwo.sourceEnd <= exprOne.sourceEnd; } else if (exprTwo.sourceStart == exprOne.sourceStart) { return exprTwo.sourceEnd < exprOne.sourceEnd; } return false; }
    non-roots answer -1, roots answer the size of the spanned tree
    /** non-roots answer -1, roots answer the size of the spanned tree */
    private int rankNode(ConstraintExpressionFormula parent, Map<ConstraintExpressionFormula,ConstraintExpressionFormula> expressionContainedBy, Map<ConstraintExpressionFormula, Set<ConstraintExpressionFormula>> containmentForest) { if (expressionContainedBy.get(parent) != null) return -1; // not a root Set<ConstraintExpressionFormula> children = containmentForest.get(parent); if (children == null) return 1; // unconnected node or leaf int sum = 1; for (ConstraintExpressionFormula child : children) { int cRank = rankNode(child, expressionContainedBy, containmentForest); if (cRank > 0) sum += cRank; } return sum; } private Set<ConstraintFormula> findBottomSet(Set<ConstraintFormula> constraints, Set<InferenceVariable> allOutputVariables, List<Set<InferenceVariable>> components) { // 18.5.2 bullet 5.(1) // A subset of constraints is selected, satisfying the property that, // for each constraint, no input variable can influence an output variable of another constraint in C. ... // An inference variable α can influence an inference variable β if α depends on the resolution of β (§18.4), or vice versa; // or if there exists a third inference variable γ such that α can influence γ and γ can influence β. ... Set<ConstraintFormula> result = new HashSet<ConstraintFormula>(); constraintLoop: for (ConstraintFormula constraint : constraints) { for (InferenceVariable in : constraint.inputVariables(this)) { if (canInfluenceAnyOf(in, allOutputVariables, components)) continue constraintLoop; } result.add(constraint); } return result; } private boolean canInfluenceAnyOf(InferenceVariable in, Set<InferenceVariable> allOuts, List<Set<InferenceVariable>> components) { // can influence == lives in the same component for (Set<InferenceVariable> component : components) { if (component.contains(in)) { for (InferenceVariable out : allOuts) if (component.contains(out)) return true; return false; } } return false; } Set<InferenceVariable> allOutputVariables(Set<ConstraintFormula> constraints) { Set<InferenceVariable> result = new HashSet<InferenceVariable>(); Iterator<ConstraintFormula> it = constraints.iterator(); while (it.hasNext()) { result.addAll(it.next().outputVariables(this)); } return result; } private TypeBinding[] varArgTypes(TypeBinding[] parameters, int k) { TypeBinding[] types = new TypeBinding[k]; int declaredLength = parameters.length-1; System.arraycopy(parameters, 0, types, 0, declaredLength); TypeBinding last = ((ArrayBinding)parameters[declaredLength]).elementsType(); for (int i = declaredLength; i < k; i++) types[i] = last; return types; } public SuspendedInferenceRecord enterPolyInvocation(InvocationSite invocation, Expression[] innerArguments) { SuspendedInferenceRecord record = new SuspendedInferenceRecord(this.currentInvocation, this.invocationArguments, this.inferenceVariables, this.inferenceKind, this.usesUncheckedConversion); this.inferenceVariables = null; this.invocationArguments = innerArguments; this.currentInvocation = invocation; this.usesUncheckedConversion = false; return record; } public SuspendedInferenceRecord enterLambda(LambdaExpression lambda) { SuspendedInferenceRecord record = new SuspendedInferenceRecord(this.currentInvocation, this.invocationArguments, this.inferenceVariables, this.inferenceKind, this.usesUncheckedConversion); this.inferenceVariables = null; this.invocationArguments = null; this.usesUncheckedConversion = false; return record; } public void integrateInnerInferenceB2(InferenceContext18 innerCtx) { this.currentBounds.addBounds(innerCtx.b2, this.environment); this.inferenceVariables = innerCtx.inferenceVariables; this.inferenceKind = innerCtx.inferenceKind; if (!isSameSite(innerCtx.currentInvocation, this.currentInvocation)) innerCtx.outerContext = this; this.usesUncheckedConversion = innerCtx.usesUncheckedConversion; } public void resumeSuspendedInference(SuspendedInferenceRecord record, InferenceContext18 innerContext) { // merge inference variables: boolean firstTime = collectInnerContext(innerContext); if (this.inferenceVariables == null) { // no new ones, assume we aborted prematurely this.inferenceVariables = record.inferenceVariables; } else if(!firstTime) { // Use a set to eliminate duplicates. final Set<InferenceVariable> uniqueVariables = new LinkedHashSet<>(); uniqueVariables.addAll(Arrays.asList(record.inferenceVariables)); uniqueVariables.addAll(Arrays.asList(this.inferenceVariables)); this.inferenceVariables = uniqueVariables.toArray(new InferenceVariable[uniqueVariables.size()]); } else { int l1 = this.inferenceVariables.length; int l2 = record.inferenceVariables.length; System.arraycopy(this.inferenceVariables, 0, this.inferenceVariables=new InferenceVariable[l1+l2], l2, l1); System.arraycopy(record.inferenceVariables, 0, this.inferenceVariables, 0, l2); } // replace invocation site & arguments: this.currentInvocation = record.site; this.invocationArguments = record.invocationArguments; this.inferenceKind = record.inferenceKind; this.usesUncheckedConversion = record.usesUncheckedConversion; } private boolean collectInnerContext(final InferenceContext18 innerContext) { if(innerContext == null) { return false; } if(this.seenInnerContexts == null) { this.seenInnerContexts = new HashSet<>(); } return this.seenInnerContexts.add(innerContext); } private Substitution getResultSubstitution(final BoundSet result) { return new Substitution() { @Override public LookupEnvironment environment() { return InferenceContext18.this.environment; } @Override public boolean isRawSubstitution() { return false; } @Override public TypeBinding substitute(TypeVariableBinding typeVariable) { if (typeVariable instanceof InferenceVariable) { TypeBinding instantiation = result.getInstantiation((InferenceVariable) typeVariable, InferenceContext18.this.environment); if (instantiation != null) return instantiation; } return typeVariable; } }; } public boolean isVarArgs() { return this.inferenceKind == CHECK_VARARG; }
    Retrieve the rank'th parameter, possibly respecting varargs invocation, see 15.12.2.4. Returns null if out of bounds and CHECK_VARARG was not requested. Precondition: isVarArgs implies method.isVarargs()
    /** * Retrieve the rank'th parameter, possibly respecting varargs invocation, see 15.12.2.4. * Returns null if out of bounds and CHECK_VARARG was not requested. * Precondition: isVarArgs implies method.isVarargs() */
    public static TypeBinding getParameter(TypeBinding[] parameters, int rank, boolean isVarArgs) { if (isVarArgs) { if (rank >= parameters.length-1) return ((ArrayBinding)parameters[parameters.length-1]).elementsType(); } else if (rank >= parameters.length) { return null; } return parameters[rank]; }
    Create a problem method signaling failure of invocation type inference, unless the given candidate is tolerable to be compatible with buggy javac.
    /** * Create a problem method signaling failure of invocation type inference, * unless the given candidate is tolerable to be compatible with buggy javac. */
    public MethodBinding getReturnProblemMethodIfNeeded(TypeBinding expectedType, MethodBinding method) { if (InferenceContext18.SIMULATE_BUG_JDK_8026527 && expectedType != null && !(method.original() instanceof SyntheticFactoryMethodBinding) && (method.returnType instanceof ReferenceBinding || method.returnType instanceof ArrayBinding)) { if (!expectedType.isProperType(true)) return null; // not ready if (this.environment.convertToRawType(method.returnType.erasure(), false).isCompatibleWith(expectedType)) return method; // don't count as problem. } /* We used to check if expected type is null and if so return method, but that is wrong - it injects an incompatible method into overload resolution. if we get here with expected type set to null at all, the target context does not define a target type (vanilla context), so inference has done its best and nothing more to do than to signal error. */ ProblemMethodBinding problemMethod = new ProblemMethodBinding(method, method.selector, method.parameters, ProblemReasons.InvocationTypeInferenceFailure); problemMethod.returnType = expectedType != null ? expectedType : method.returnType; problemMethod.inferenceContext = this; return problemMethod; } // debugging: @Override public String toString() { StringBuffer buf = new StringBuffer("Inference Context"); //$NON-NLS-1$ switch (this.stepCompleted) { case NOT_INFERRED: buf.append(" (initial)");break; //$NON-NLS-1$ case APPLICABILITY_INFERRED: buf.append(" (applicability inferred)");break; //$NON-NLS-1$ case TYPE_INFERRED: buf.append(" (type inferred)");break; //$NON-NLS-1$ case TYPE_INFERRED_FINAL: buf.append(" (type inferred final)");break; //$NON-NLS-1$ } switch (this.inferenceKind) { case CHECK_STRICT: buf.append(" (strict)");break; //$NON-NLS-1$ case CHECK_LOOSE: buf.append(" (loose)");break; //$NON-NLS-1$ case CHECK_VARARG: buf.append(" (vararg)");break; //$NON-NLS-1$ } if (this.currentBounds != null && isResolved(this.currentBounds)) buf.append(" (resolved)"); //$NON-NLS-1$ buf.append('\n'); if (this.inferenceVariables != null) { buf.append("Inference Variables:\n"); //$NON-NLS-1$ for (int i = 0; i < this.inferenceVariables.length; i++) { buf.append('\t').append(this.inferenceVariables[i].sourceName).append("\t:\t"); //$NON-NLS-1$ if (this.currentBounds != null && this.currentBounds.isInstantiated(this.inferenceVariables[i])) buf.append(this.currentBounds.getInstantiation(this.inferenceVariables[i], this.environment).readableName()); else buf.append("NOT INSTANTIATED"); //$NON-NLS-1$ buf.append('\n'); } } if (this.initialConstraints != null) { buf.append("Initial Constraints:\n"); //$NON-NLS-1$ for (int i = 0; i < this.initialConstraints.length; i++) if (this.initialConstraints[i] != null) buf.append('\t').append(this.initialConstraints[i].toString()).append('\n'); } if (this.currentBounds != null) buf.append(this.currentBounds.toString()); return buf.toString(); }
    If 'type' is a parameterized type and one of its arguments is a wildcard answer the casted type, else null. A nonnull answer is ensured to also have nonnull arguments.
    /** * If 'type' is a parameterized type and one of its arguments is a wildcard answer the casted type, else null. * A nonnull answer is ensured to also have nonnull arguments. */
    public static ParameterizedTypeBinding parameterizedWithWildcard(TypeBinding type) { if (type == null || type.kind() != Binding.PARAMETERIZED_TYPE) return null; ParameterizedTypeBinding parameterizedType = (ParameterizedTypeBinding) type; TypeBinding[] arguments = parameterizedType.arguments; if (arguments != null) { for (int i = 0; i < arguments.length; i++) if (arguments[i].isWildcard()) return parameterizedType; } return null; } public TypeBinding[] getFunctionInterfaceArgumentSolutions(TypeBinding[] a) { int m = a.length; TypeBinding[] aprime = new TypeBinding[m]; for (int i = 0; i < this.inferenceVariables.length; i++) { InferenceVariable alphai = this.inferenceVariables[i]; TypeBinding t = this.currentBounds.getInstantiation(alphai, this.environment); if (t != null) aprime[i] = t; else aprime[i] = a[i]; } return aprime; }
    Record the fact that the given constraint requires unchecked conversion.
    /** Record the fact that the given constraint requires unchecked conversion. */
    public void recordUncheckedConversion(ConstraintTypeFormula constraint) { if (this.constraintsWithUncheckedConversion == null) this.constraintsWithUncheckedConversion = new ArrayList<ConstraintFormula>(); this.constraintsWithUncheckedConversion.add(constraint); this.usesUncheckedConversion = true; } void reportUncheckedConversions(BoundSet solution) { if (this.constraintsWithUncheckedConversion != null) { int len = this.constraintsWithUncheckedConversion.size(); Substitution substitution = getResultSubstitution(solution); for (int i = 0; i < len; i++) { ConstraintTypeFormula constraint = (ConstraintTypeFormula) this.constraintsWithUncheckedConversion.get(i); TypeBinding expectedType = constraint.right; TypeBinding providedType = constraint.left; if (!expectedType.isProperType(true)) { expectedType = Scope.substitute(substitution, expectedType); } if (!providedType.isProperType(true)) { providedType = Scope.substitute(substitution, providedType); } /* FIXME(stephan): enable once we solved: (a) avoid duplication with traditional reporting (b) improve location to report against if (this.currentInvocation instanceof Expression) this.scope.problemReporter().unsafeTypeConversion((Expression) this.currentInvocation, providedType, expectedType); */ } } }
    For use by 15.12.2.6 Method Invocation Type
    /** For use by 15.12.2.6 Method Invocation Type */
    public boolean usesUncheckedConversion() { return this.constraintsWithUncheckedConversion != null; } // INTERIM: infrastructure for detecting failures caused by specific known incompleteness: public static void missingImplementation(String msg) { throw new UnsupportedOperationException(msg); } public void forwardResults(BoundSet result, Invocation invocation, ParameterizedMethodBinding pmb, TypeBinding targetType) { if (targetType != null) invocation.registerResult(targetType, pmb); Expression[] arguments = invocation.arguments(); for (int i = 0, length = arguments == null ? 0 : arguments.length; i < length; i++) { Expression [] expressions = arguments[i].getPolyExpressions(); for (int j = 0, jLength = expressions.length; j < jLength; j++) { Expression expression = expressions[j]; if (!(expression instanceof Invocation)) continue; Invocation polyInvocation = (Invocation) expression; MethodBinding binding = polyInvocation.binding(); if (binding == null || !binding.isValidBinding()) continue; ParameterizedMethodBinding methodSubstitute = null; if (binding instanceof ParameterizedGenericMethodBinding) { MethodBinding shallowOriginal = binding.shallowOriginal(); TypeBinding[] solutions = getSolutions(shallowOriginal.typeVariables(), polyInvocation, result); if (solutions == null) // in CEF.reduce, we lift inner poly expressions into outer context only if their target type has inference variables. continue; methodSubstitute = this.environment.createParameterizedGenericMethod(shallowOriginal, solutions); } else { if (!binding.isConstructor() || !(binding instanceof ParameterizedMethodBinding)) continue; // throw ISE ? MethodBinding shallowOriginal = binding.shallowOriginal(); ReferenceBinding genericType = shallowOriginal.declaringClass; TypeBinding[] solutions = getSolutions(genericType.typeVariables(), polyInvocation, result); if (solutions == null) // in CEF.reduce, we lift inner poly expressions into outer context only if their target type has inference variables. continue; ParameterizedTypeBinding parameterizedType = this.environment.createParameterizedType(genericType, solutions, binding.declaringClass.enclosingType()); for (MethodBinding parameterizedMethod : parameterizedType.methods()) { if (parameterizedMethod.original() == shallowOriginal) { methodSubstitute = (ParameterizedMethodBinding) parameterizedMethod; break; } } } if (methodSubstitute == null || !methodSubstitute.isValidBinding()) continue; boolean variableArity = pmb.isVarargs(); final TypeBinding[] parameters = pmb.parameters; if (variableArity && parameters.length == arguments.length && i == length - 1) { TypeBinding returnType = methodSubstitute.returnType.capture(this.scope, expression.sourceStart, expression.sourceEnd); if (returnType.isCompatibleWith(parameters[parameters.length - 1], this.scope)) { variableArity = false; } } TypeBinding parameterType = InferenceContext18.getParameter(parameters, i, variableArity); forwardResults(result, polyInvocation, methodSubstitute, parameterType); } } } public void cleanUp() { this.b2 = null; this.currentBounds = null; } }