/******************************************************************************* * Copyright (c) 2013, 2016 GK Software AG, and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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.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.Wildcard; 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. * * <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 */ 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. */ 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 {@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) */ InvocationSite currentInvocation; /** arguments of #currentInvocation, if any */ Expression[] invocationArguments; /** The inference variables for which as solution is sought. */ InferenceVariable[] inferenceVariables; /** 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. */ BoundSet currentBounds; /** 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. */ public int stepCompleted = NOT_INFERRED; public static final int NOT_INFERRED = 0; /** 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). */ public static final int TYPE_INFERRED = 2; /** Signals whether any type compatibility makes use of unchecked conversion. */ public List<ConstraintFormula> constraintsWithUncheckedConversion; public boolean usesUncheckedConversion; public InferenceContext18 outerContext; 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 */ private BoundSet innerInbox; /** Not per JLS: signal when current is ready to directly merge all bounds from inner. */ private boolean directlyAcceptingInnerBounds = false; 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). */ 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. * @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. */ 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. */ 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); 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. */ 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); } 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 */ 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. */ 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. */ 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 */ 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(); try { // bullets 1&2: definitions only. if (expectedType != null && expectedType != TypeBinding.VOID && invocationSite instanceof Expression && ((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); if (jdk8153748result != null) { this.currentBounds.incorporate(this); } } 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); return this.currentBounds = solution; // this is final, keep the result: } finally { this.stepCompleted = TYPE_INFERRED; } } // --- 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. */ private void pushBoundsToOuter() { InferenceContext18 outer = this.outerContext; if (outer != null && outer.stepCompleted >= APPLICABILITY_INFERRED) { if (outer.directlyAcceptingInnerBounds) { outer.currentBounds.addBounds(this.currentBounds, this.environment); } else if (outer.innerInbox == null) { outer.innerInbox = this.currentBounds.copy(); } else { outer.innerInbox.addBounds(this.currentBounds, this.environment); } } } /** 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. */ 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) 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 = this.substitute(parameter); ReductionResult result = addJDK_8153748ConstraintsFromExpression(argument, parameter, method); 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) 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()) { return addJDK_8153748ConstraintsFromInvocation(innerArgs, innerMethod.original()); } } else if (argument instanceof ConditionalExpression) { ConditionalExpression ce = (ConditionalExpression) argument; if (addJDK_8153748ConstraintsFromExpression(ce.valueIfTrue, parameter, method) == ReductionResult.FALSE) return ReductionResult.FALSE; return addJDK_8153748ConstraintsFromExpression(ce.valueIfFalse, parameter, method); } 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; } 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 { // -- 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 (substF.isProperType(true)) // 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; return innerContext.addConstraintsToC(arguments, c, innerMethod.genericMethod(), innerContext.inferenceKind, invocation); } 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); } 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 */ 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); // TODO If F<A'1, ..., A'm> is a well-formed type, ... return blockScope.environment().createParameterizedType(genericType, aprime, targetTypeWithWildCards.enclosingType()); } } return targetTypeWithWildCards; } /** * 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 */ 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 { 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. * @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 */ 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? */ 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. * @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. */ 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? } /** * <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 { ReferenceBinding[] glbs = Scope.greaterLowerBound((ReferenceBinding[])upperBounds); if (glbs == null) { throw new UnsupportedOperationException("no glb for "+Arrays.asList(upperBounds)); //$NON-NLS-1$ } else if (glbs.length == 1) { glb = glbs[0]; } else { IntersectionTypeBinding18 intersection = (IntersectionTypeBinding18) this.environment.createIntersectionType18(glbs); if (!ReferenceBinding.isConsistentIntersection(intersection.intersectingTypes)) { tmpBoundSet = prevBoundSet; // clean up break variables; // and start over } glb = intersection; } } } 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() { public LookupEnvironment environment() { return InferenceContext18.this.environment; } public boolean isRawSubstitution() { return false; } 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; } int captureId = 0; /** 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>() { 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. */ 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. */ 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'? */ 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? */ 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 */ 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.currentInvocation = 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) { // merge inference variables: if (this.inferenceVariables == null) { // no new ones, assume we aborted prematurely this.inferenceVariables = record.inferenceVariables; } else { int l1 = this.inferenceVariables.length; int l2 = record.inferenceVariables.length; // move to back, add previous to front: 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 Substitution getResultSubstitution(final BoundSet result) { return new Substitution() { public LookupEnvironment environment() { return InferenceContext18.this.environment; } public boolean isRawSubstitution() { return false; } 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() */ 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. */ public MethodBinding getReturnProblemMethodIfNeeded(TypeBinding expectedType, MethodBinding method) { if (InferenceContext18.SIMULATE_BUG_JDK_8026527 && expectedType != null && method.returnType instanceof ReferenceBinding) { if (method.returnType.erasure().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: 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$ } 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. */ 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. */ 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 */ 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; } }