/******************************************************************************* * Copyright (c) 2013, 2016 GK Software AG. * 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 * Lars Vogel <Lars.Vogel@vogella.com> - Contributions for * Bug 473178 * IBM Corporation - Bug fixes *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; /** * Implementation of 18.1.3 in JLS8. * This class is also responsible for incorporation as defined in 18.3. */ class BoundSet { static final BoundSet TRUE = new BoundSet(); // empty set of bounds static final BoundSet FALSE = new BoundSet(); // pseudo bounds /** * For a given inference variable this structure holds all type bounds * with a relation in { SUPERTYPE, SAME, SUBTYPE }. * These are internally stored in three sets, one for each of the relations. */ private class ThreeSets { Set<TypeBound> superBounds; Set<TypeBound> sameBounds; Set<TypeBound> subBounds; TypeBinding instantiation; Map<InferenceVariable,TypeBound> inverseBounds; // from right inference variable to bound public ThreeSets() { // empty, the sets are lazily initialized } public ParameterizedTypeBinding mergeTypeParameters(ParameterizedTypeBinding current, ParameterizedTypeBinding newB) { TypeBinding[] curTypeArgs = current.typeArguments(); TypeBinding[] newTypeArgs = newB.typeArguments(); TypeBinding[] merged = new TypeBinding[curTypeArgs.length]; System.arraycopy(curTypeArgs, 0, merged, 0, curTypeArgs.length); boolean wasMerged = false; for (int i = 0; i < curTypeArgs.length; i++) { if (TypeBinding.equalsEquals(curTypeArgs[i], newTypeArgs[i])) continue; // Don't mess with captures if(curTypeArgs[i].isCapture() || newTypeArgs[i].isCapture()) return null; if (curTypeArgs[i] instanceof InferenceVariable) { if (!(newTypeArgs[i] instanceof InferenceVariable)) { // Short circuit incorporation // Merge the type parameters, we are going to // end up with this bound during incorporation anyway because of the SAME // bound on the inference variable ThreeSets three = BoundSet.this.boundsPerVariable.get(curTypeArgs[i]); if(three != null && three.sameBounds != null && three.sameBounds.contains(new TypeBound((InferenceVariable) curTypeArgs[i], newTypeArgs[i], ReductionResult.SAME))) { merged[i] = newTypeArgs[i]; wasMerged = true; } } else if (!curTypeArgs[i].equals(newTypeArgs[i])) { return null; } } else { if (!(newTypeArgs[i] instanceof InferenceVariable)) { if (!TypeBinding.equalsEquals(curTypeArgs[i], newTypeArgs[i])) { return null; } } else { ThreeSets three = BoundSet.this.boundsPerVariable.get(newTypeArgs[i]); // We do not have a SAME bounds for this inference variable, do not substitute if(three == null || three.sameBounds == null || !three.sameBounds.contains(new TypeBound((InferenceVariable) newTypeArgs[i], curTypeArgs[i], ReductionResult.SAME))) { return null; } } } } if(wasMerged) { ParameterizedTypeBinding clone = (ParameterizedTypeBinding)current.clone(current.enclosingType()); clone.arguments = merged; return clone; } return null; } /** Add a type bound to the appropriate set. */ public boolean addBound(TypeBound bound) { Iterator<TypeBound> it = null; switch (bound.relation) { case ReductionResult.SUPERTYPE: if (this.superBounds == null) this.superBounds = new HashSet<>(); if (CompilerOptions.useunspecdtypeinferenceperformanceoptimization) { if (!bound.right.isProperType(true)) { it = this.superBounds.iterator(); while (it.hasNext()) { TypeBound b = it.next(); if (bound.right.isParameterizedType() && b.right.isParameterizedType() && b.right.original() == bound.right.original()) { //$IDENTITY-COMPARISON$ TypeBinding clone = mergeTypeParameters((ParameterizedTypeBinding) b.right, (ParameterizedTypeBinding) bound.right); if (clone != null) { b.right = clone; return false; } } } } } return this.superBounds.add(bound); case ReductionResult.SAME: if (this.sameBounds == null) this.sameBounds = new HashSet<>(); return this.sameBounds.add(bound); case ReductionResult.SUBTYPE: if (this.subBounds == null) this.subBounds = new HashSet<>(); if (CompilerOptions.useunspecdtypeinferenceperformanceoptimization) { if (!bound.right.isProperType(true)) { it = this.subBounds.iterator(); while (it.hasNext()) { TypeBound b = it.next(); if (bound.right.isParameterizedType() && b.right.isParameterizedType() && b.right.original() == bound.right.original()) { //$IDENTITY-COMPARISON$ TypeBinding clone = mergeTypeParameters((ParameterizedTypeBinding) b.right, (ParameterizedTypeBinding) bound.right); if (clone != null) { b.right = clone; return false; } } } } } return this.subBounds.add(bound); default: throw new IllegalArgumentException("Unexpected bound relation in : " + bound); //$NON-NLS-1$ } } // pre: this.superBounds != null public TypeBinding[] lowerBounds(boolean onlyProper, InferenceVariable variable) { TypeBinding[] boundTypes = new TypeBinding[this.superBounds.size()]; Iterator<TypeBound> it = this.superBounds.iterator(); long nullHints = variable.nullHints; int i = 0; while(it.hasNext()) { TypeBound current = it.next(); TypeBinding boundType = current.right; if (!onlyProper || boundType.isProperType(true)) { boundTypes[i++] = boundType; nullHints |= current.nullHints; } } if (i == 0) return Binding.NO_TYPES; if (i < boundTypes.length) System.arraycopy(boundTypes, 0, boundTypes=new TypeBinding[i], 0, i); useNullHints(nullHints, boundTypes, variable.environment); InferenceContext18.sortTypes(boundTypes); return boundTypes; } // pre: this.subBounds != null public TypeBinding[] upperBounds(boolean onlyProper, InferenceVariable variable) { ReferenceBinding[] rights = new ReferenceBinding[this.subBounds.size()]; TypeBinding simpleUpper = null; Iterator<TypeBound> it = this.subBounds.iterator(); long nullHints = variable.nullHints; int i = 0; while(it.hasNext()) { TypeBinding right=it.next().right; if (!onlyProper || right.isProperType(true)) { if (right instanceof ReferenceBinding) { rights[i++] = (ReferenceBinding) right; nullHints |= right.tagBits & TagBits.AnnotationNullMASK; } else { if (simpleUpper != null) return Binding.NO_TYPES; // shouldn't simpleUpper = right; } } } if (i == 0) return simpleUpper != null ? new TypeBinding[] { simpleUpper } : Binding.NO_TYPES; if (i == 1 && simpleUpper != null) return new TypeBinding[] { simpleUpper }; // no nullHints since not a reference type if (i < rights.length) System.arraycopy(rights, 0, rights=new ReferenceBinding[i], 0, i); useNullHints(nullHints, rights, variable.environment); InferenceContext18.sortTypes(rights); return rights; } // pre: beta is a prototype public boolean hasDependency(InferenceVariable beta) { if (this.superBounds != null && hasDependency(this.superBounds, beta)) return true; if (this.sameBounds != null && hasDependency(this.sameBounds, beta)) return true; if (this.subBounds != null && hasDependency(this.subBounds, beta)) return true; if (this.inverseBounds != null) { if (this.inverseBounds.containsKey(beta)) { // TODO: not yet observed in tests return true; } } return false; } // pre: var is a prototype private boolean hasDependency(Set<TypeBound> someBounds, InferenceVariable var) { Iterator<TypeBound> bIt = someBounds.iterator(); while (bIt.hasNext()) { TypeBound bound = bIt.next(); if (TypeBinding.equalsEquals(bound.right, var) || bound.right.mentionsAny(new TypeBinding[] {var}, -1)) return true; } return false; } /** Total number of type bounds in this container. */ public int size() { int size = 0; if (this.superBounds != null) size += this.superBounds.size(); if (this.sameBounds != null) size += this.sameBounds.size(); if (this.subBounds != null) size += this.subBounds.size(); return size; } public int flattenInto(TypeBound[] collected, int idx) { if (this.superBounds != null) { int len = this.superBounds.size(); System.arraycopy(this.superBounds.toArray(), 0, collected, idx, len); idx += len; } if (this.sameBounds != null) { int len = this.sameBounds.size(); System.arraycopy(this.sameBounds.toArray(), 0, collected, idx, len); idx += len; } if (this.subBounds != null) { int len = this.subBounds.size(); System.arraycopy(this.subBounds.toArray(), 0, collected, idx, len); idx += len; } return idx; } public ThreeSets copy() { ThreeSets copy = new ThreeSets(); if (this.superBounds != null) copy.superBounds = new HashSet<>(this.superBounds); if (this.sameBounds != null) copy.sameBounds = new HashSet<>(this.sameBounds); if (this.subBounds != null) copy.subBounds = new HashSet<>(this.subBounds); copy.instantiation = this.instantiation; return copy; } public TypeBinding findSingleWrapperType() { if (this.instantiation != null) { if (this.instantiation.isProperType(true)) { switch (this.instantiation.id) { case TypeIds.T_JavaLangByte: case TypeIds.T_JavaLangShort: case TypeIds.T_JavaLangCharacter: case TypeIds.T_JavaLangInteger: case TypeIds.T_JavaLangLong: case TypeIds.T_JavaLangFloat: case TypeIds.T_JavaLangDouble: case TypeIds.T_JavaLangBoolean: return this.instantiation; } } } if (this.subBounds != null) { Iterator<TypeBound> it = this.subBounds.iterator(); while(it.hasNext()) { TypeBinding boundType = it.next().right; if ((boundType).isProperType(true)) { switch (boundType.id) { case TypeIds.T_JavaLangByte: case TypeIds.T_JavaLangShort: case TypeIds.T_JavaLangCharacter: case TypeIds.T_JavaLangInteger: case TypeIds.T_JavaLangLong: case TypeIds.T_JavaLangFloat: case TypeIds.T_JavaLangDouble: case TypeIds.T_JavaLangBoolean: return boundType; } } } } if (this.superBounds != null) { Iterator<TypeBound> it = this.superBounds.iterator(); while(it.hasNext()) { TypeBinding boundType = it.next().right; if ((boundType).isProperType(true)) { switch (boundType.id) { case TypeIds.T_JavaLangByte: case TypeIds.T_JavaLangShort: case TypeIds.T_JavaLangCharacter: case TypeIds.T_JavaLangInteger: case TypeIds.T_JavaLangLong: case TypeIds.T_JavaLangFloat: case TypeIds.T_JavaLangDouble: case TypeIds.T_JavaLangBoolean: return boundType; } } } } return null; } /** * Not per JLS: enhance the given type bounds using the nullHints, if useful. * Will only ever be effective if any TypeBounds carry nullHints, * which only happens if any TypeBindings have non-zero (tagBits & AnnotationNullMASK), * which only happens if null annotations are enabled in the first place. */ private void useNullHints(long nullHints, TypeBinding[] boundTypes, LookupEnvironment environment) { if (nullHints == TagBits.AnnotationNullMASK) { // on contradiction remove null type annotations for (int i = 0; i < boundTypes.length; i++) boundTypes[i] = boundTypes[i].withoutToplevelNullAnnotation(); } else { AnnotationBinding[] annot = environment.nullAnnotationsFromTagBits(nullHints); if (annot != null) { // only get here if exactly one of @NonNull or @Nullable was hinted; now apply this hint: for (int i = 0; i < boundTypes.length; i++) boundTypes[i] = environment.createAnnotatedType(boundTypes[i], annot); } } } TypeBinding combineAndUseNullHints(TypeBinding type, long nullHints, LookupEnvironment environment) { // precondition: only called when null annotations are enabled. // TODO(optimization): may want to collect all nullHints in the ThreeSets, which, however, // needs a reference TypeBound->ThreeSets to propagate the bits as they are added. if (this.sameBounds != null) { Iterator<TypeBound> it = this.sameBounds.iterator(); while(it.hasNext()) nullHints |= it.next().nullHints; } if (this.superBounds != null) { Iterator<TypeBound> it = this.superBounds.iterator(); while(it.hasNext()) nullHints |= it.next().nullHints; } if (this.subBounds != null) { Iterator<TypeBound> it = this.subBounds.iterator(); while(it.hasNext()) nullHints |= it.next().nullHints; } if (nullHints == TagBits.AnnotationNullMASK) // on contradiction remove null type annotations return type.withoutToplevelNullAnnotation(); AnnotationBinding[] annot = environment.nullAnnotationsFromTagBits(nullHints); if (annot != null) // only get here if exactly one of @NonNull or @Nullable was hinted; now apply this hint: return environment.createAnnotatedType(type, annot); return type; } public void setInstantiation(TypeBinding type, InferenceVariable variable, LookupEnvironment environment) { if (environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) { long variableBits = variable.tagBits & TagBits.AnnotationNullMASK; long allBits = type.tagBits | variableBits; if (this.instantiation != null) allBits |= this.instantiation.tagBits; allBits &= TagBits.AnnotationNullMASK; if (allBits == TagBits.AnnotationNullMASK) { // contradiction allBits = variableBits; } if (allBits != (type.tagBits & TagBits.AnnotationNullMASK)) { AnnotationBinding[] annot = environment.nullAnnotationsFromTagBits(allBits); if (annot != null) type = environment.createAnnotatedType(type.withoutToplevelNullAnnotation(), annot); else if (type.hasNullTypeAnnotations()) type = type.withoutToplevelNullAnnotation(); } } this.instantiation = type; } } // main storage of type bounds: HashMap<InferenceVariable, ThreeSets> boundsPerVariable = new HashMap<>(); /** * 18.1.3 bullet 4: G<α1, ..., αn> = capture(G<A1, ..., An>) * On both sides we only enter types with nonnull arguments. */ HashMap<ParameterizedTypeBinding,ParameterizedTypeBinding> captures = new HashMap<>(); /** 18.1.3 bullet 5: throws α */ Set<InferenceVariable> inThrows = new HashSet<>(); private TypeBound [] incorporatedBounds = new TypeBound[0]; private TypeBound [] unincorporatedBounds = new TypeBound [1024]; private int unincorporatedBoundsCount = 0; private TypeBound [] mostRecentBounds = new TypeBound[4]; // for quick & dirty duplicate elimination. public BoundSet() {} // pre: typeParameters != null, variables[i].typeParameter == typeParameters[i] public void addBoundsFromTypeParameters(InferenceContext18 context, TypeVariableBinding[] typeParameters, InferenceVariable[] variables) { int length = typeParameters.length; for (int i = 0; i < length; i++) { TypeVariableBinding typeParameter = typeParameters[i]; InferenceVariable variable = variables[i]; TypeBound[] someBounds = typeParameter.getTypeBounds(variable, new InferenceSubstitution(context)); boolean hasProperBound = false; if (someBounds.length > 0) hasProperBound = addBounds(someBounds, context.environment); if (!hasProperBound) addBound(new TypeBound(variable, context.object, ReductionResult.SUBTYPE), context.environment); } } /** Answer a flat representation of this BoundSet. */ public TypeBound[] flatten() { int size = 0; Iterator<ThreeSets> outerIt = this.boundsPerVariable.values().iterator(); while (outerIt.hasNext()) size += outerIt.next().size(); TypeBound[] collected = new TypeBound[size]; if (size == 0) return collected; outerIt = this.boundsPerVariable.values().iterator(); int idx = 0; while (outerIt.hasNext()) idx = outerIt.next().flattenInto(collected, idx); return collected; } /** * For resolution we work with a copy of the bound set, to enable retrying. * @return the new bound set. */ public BoundSet copy() { BoundSet copy = new BoundSet(); Iterator<Entry<InferenceVariable, ThreeSets>> setsIterator = this.boundsPerVariable.entrySet().iterator(); while (setsIterator.hasNext()) { Entry<InferenceVariable, ThreeSets> entry = setsIterator.next(); copy.boundsPerVariable.put(entry.getKey(), entry.getValue().copy()); } copy.inThrows.addAll(this.inThrows); copy.captures.putAll(this.captures); System.arraycopy(this.incorporatedBounds, 0, copy.incorporatedBounds = new TypeBound[this.incorporatedBounds.length], 0, this.incorporatedBounds.length); System.arraycopy(this.unincorporatedBounds, 0, copy.unincorporatedBounds = new TypeBound[this.unincorporatedBounds.length], 0, this.unincorporatedBounds.length); copy.unincorporatedBoundsCount = this.unincorporatedBoundsCount; return copy; } public void addBound(TypeBound bound, LookupEnvironment environment) { if (bound.relation == ReductionResult.SUBTYPE && bound.right.id == TypeIds.T_JavaLangObject) return; if (bound.left == bound.right) //$IDENTITY-COMPARISON$ return; for (int recent = 0; recent < 4; recent++) { if (bound.equals(this.mostRecentBounds[recent])) { if (environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) { TypeBound existing = this.mostRecentBounds[recent]; long boundNullBits = bound.right.tagBits & TagBits.AnnotationNullMASK; long existingNullBits = existing.right.tagBits & TagBits.AnnotationNullMASK; if (boundNullBits != existingNullBits) { if (existingNullBits == 0) existing.right = bound.right; else if (boundNullBits != 0) // combine bits from both sources, even if this creates a contradiction existing.right = environment.createAnnotatedType(existing.right, environment.nullAnnotationsFromTagBits(boundNullBits)); } } return; } } this.mostRecentBounds[3] = this.mostRecentBounds[2]; this.mostRecentBounds[2] = this.mostRecentBounds[1]; this.mostRecentBounds[1] = this.mostRecentBounds[0]; this.mostRecentBounds[0] = bound; InferenceVariable variable = bound.left.prototype(); ThreeSets three = this.boundsPerVariable.get(variable); if (three == null) this.boundsPerVariable.put(variable, (three = new ThreeSets())); if (three.addBound(bound)) { int unincorporatedBoundsLength = this.unincorporatedBounds.length; if (this.unincorporatedBoundsCount >= unincorporatedBoundsLength) System.arraycopy(this.unincorporatedBounds, 0, this.unincorporatedBounds = new TypeBound[unincorporatedBoundsLength * 2], 0, unincorporatedBoundsLength); this.unincorporatedBounds[this.unincorporatedBoundsCount ++] = bound; // check if this makes the inference variable instantiated: TypeBinding typeBinding = bound.right; if (bound.relation == ReductionResult.SAME && typeBinding.isProperType(true)) three.setInstantiation(typeBinding, variable, environment); if (bound.right instanceof InferenceVariable) { // for a dependency between two IVs make a note about the inverse bound. // this should be needed to determine IV dependencies independent of direction. // TODO: so far no test could be identified which actually needs it ... InferenceVariable rightIV = (InferenceVariable) bound.right.prototype(); three = this.boundsPerVariable.get(rightIV); if (three == null) this.boundsPerVariable.put(rightIV, (three = new ThreeSets())); if (three.inverseBounds == null) three.inverseBounds = new HashMap<>(); three.inverseBounds.put(rightIV, bound); } } } private boolean addBounds(TypeBound[] newBounds, LookupEnvironment environment) { boolean hasProperBound = false; for (int i = 0; i < newBounds.length; i++) { addBound(newBounds[i], environment); hasProperBound |= newBounds[i].isBound(); } return hasProperBound; } public void addBounds(BoundSet that, LookupEnvironment environment) { if (that == null || environment == null) return; addBounds(that.flatten(), environment); } public boolean isInstantiated(InferenceVariable inferenceVariable) { ThreeSets three = this.boundsPerVariable.get(inferenceVariable.prototype()); if (three != null) return three.instantiation != null; return false; } public TypeBinding getInstantiation(InferenceVariable inferenceVariable, LookupEnvironment environment) { ThreeSets three = this.boundsPerVariable.get(inferenceVariable.prototype()); if (three != null) { TypeBinding instantiation = three.instantiation; if (environment != null && environment.globalOptions.isAnnotationBasedNullAnalysisEnabled && instantiation != null && (instantiation.tagBits & TagBits.AnnotationNullMASK) == 0) return three.combineAndUseNullHints(instantiation, inferenceVariable.nullHints, environment); return instantiation; } return null; } public int numUninstantiatedVariables(InferenceVariable[] variables) { int num = 0; for (int i = 0; i < variables.length; i++) { if (!isInstantiated(variables[i])) num++; } return num; } // Driver for the real workhorse - Implements generational incorporation a la generational garbage collector. boolean incorporate(InferenceContext18 context) throws InferenceFailureException { if (this.unincorporatedBoundsCount == 0 && this.captures.size() == 0) return true; do { TypeBound [] freshBounds; System.arraycopy(this.unincorporatedBounds, 0, freshBounds = new TypeBound[this.unincorporatedBoundsCount], 0, this.unincorporatedBoundsCount); this.unincorporatedBoundsCount = 0; // Pairwise bidirectional compare all bounds from previous generation with the fresh set. if (!incorporate(context, this.incorporatedBounds, freshBounds)) return false; // Pairwise bidirectional compare all fresh bounds. if (!incorporate(context, freshBounds, freshBounds)) return false; // Merge the bounds into one incorporated generation. final int incorporatedLength = this.incorporatedBounds.length; final int unincorporatedLength = freshBounds.length; TypeBound [] aggregate = new TypeBound[incorporatedLength + unincorporatedLength]; System.arraycopy(this.incorporatedBounds, 0, aggregate, 0, incorporatedLength); System.arraycopy(freshBounds, 0, aggregate, incorporatedLength, unincorporatedLength); this.incorporatedBounds = aggregate; } while (this.unincorporatedBoundsCount > 0); return true; } /** * <b>JLS 18.3:</b> Try to infer new constraints from pairs of existing type bounds. * Each new constraint is first reduced and checked for TRUE or FALSE, which will * abort the processing. * @param context the context that manages our inference variables * @return false if any constraint resolved to false, true otherwise * @throws InferenceFailureException a compile error has been detected during inference */ boolean incorporate(InferenceContext18 context, TypeBound [] first, TypeBound [] next) throws InferenceFailureException { boolean analyzeNull = context.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled; ConstraintTypeFormula [] mostRecentFormulas = new ConstraintTypeFormula[4]; // poor man's cache to toss out duplicates, in pathological cases there are a good quarter million of them. // check each pair, in each way. for (int i = 0, iLength = first.length; i < iLength; i++) { TypeBound boundI = first[i]; for (int j = 0, jLength = next.length; j < jLength; j++) { TypeBound boundJ = next[j]; if (boundI == boundJ) continue; int iteration = 1; do { ConstraintTypeFormula newConstraint = null; boolean deriveTypeArgumentConstraints = false; if (iteration == 2) { TypeBound boundX = boundI; boundI = boundJ; boundJ = boundX; } switch (boundI.relation) { case ReductionResult.SAME: switch (boundJ.relation) { case ReductionResult.SAME: newConstraint = combineSameSame(boundI, boundJ); break; case ReductionResult.SUBTYPE: case ReductionResult.SUPERTYPE: newConstraint = combineSameSubSuper(boundI, boundJ); break; } break; case ReductionResult.SUBTYPE: switch (boundJ.relation) { case ReductionResult.SAME: newConstraint = combineSameSubSuper(boundJ, boundI); break; case ReductionResult.SUPERTYPE: newConstraint = combineSuperAndSub(boundJ, boundI); break; case ReductionResult.SUBTYPE: newConstraint = combineEqualSupers(boundI, boundJ); deriveTypeArgumentConstraints = TypeBinding.equalsEquals(boundI.left, boundJ.left); break; } break; case ReductionResult.SUPERTYPE: switch (boundJ.relation) { case ReductionResult.SAME: newConstraint = combineSameSubSuper(boundJ, boundI); break; case ReductionResult.SUBTYPE: newConstraint = combineSuperAndSub(boundI, boundJ); break; case ReductionResult.SUPERTYPE: newConstraint = combineEqualSupers(boundI, boundJ); break; } } if (newConstraint != null) { if (newConstraint.left == newConstraint.right) { //$IDENTITY-COMPARISON$ newConstraint = null; } else if (newConstraint.equalsEquals(mostRecentFormulas[0]) || newConstraint.equalsEquals(mostRecentFormulas[1]) || newConstraint.equalsEquals(mostRecentFormulas[2]) || newConstraint.equalsEquals(mostRecentFormulas[3])) { newConstraint = null; } } if (newConstraint != null) { // bubble formulas around the cache. mostRecentFormulas[3] = mostRecentFormulas[2]; mostRecentFormulas[2] = mostRecentFormulas[1]; mostRecentFormulas[1] = mostRecentFormulas[0]; mostRecentFormulas[0] = newConstraint; if (!reduceOneConstraint(context, newConstraint)) return false; if (analyzeNull) { // not per JLS: if the new constraint relates types where at least one has a null annotations, // record all null tagBits as hints for the final inference solution. long nullHints = (newConstraint.left.tagBits | newConstraint.right.tagBits) & TagBits.AnnotationNullMASK; if (nullHints != 0) { if (TypeBinding.equalsEquals(boundI.left, boundJ.left) || (boundI.relation == ReductionResult.SAME && TypeBinding.equalsEquals(boundI.right, boundJ.left)) || (boundJ.relation == ReductionResult.SAME && TypeBinding.equalsEquals(boundI.left, boundJ.right))) { boundI.nullHints |= nullHints; boundJ.nullHints |= nullHints; } } } } ConstraintFormula[] typeArgumentConstraints = deriveTypeArgumentConstraints ? deriveTypeArgumentConstraints(boundI, boundJ) : null; if (typeArgumentConstraints != null) { for (int k = 0, length = typeArgumentConstraints.length; k < length; k++) { if (!reduceOneConstraint(context, typeArgumentConstraints[k])) return false; } } if (iteration == 2) { TypeBound boundX = boundI; boundI = boundJ; boundJ = boundX; } } while (first != next && ++iteration <= 2); } } /* TODO: are we sure this will always terminate? Cf. e.g. (Discussion in 18.3): * * "The assertion that incorporation reaches a fixed point oversimplifies the matter slightly. ..." */ Iterator<Entry<ParameterizedTypeBinding, ParameterizedTypeBinding>> captIter = this.captures.entrySet().iterator(); while (captIter.hasNext()) { Entry<ParameterizedTypeBinding, ParameterizedTypeBinding> capt = captIter.next(); ParameterizedTypeBinding gAlpha = capt.getKey(); ParameterizedTypeBinding gA = capt.getValue(); ReferenceBinding g = (ReferenceBinding) gA.original(); final TypeVariableBinding[] parameters = g.typeVariables(); // construct theta = [P1:=alpha1,...] final InferenceVariable[] alphas = new InferenceVariable[gAlpha.arguments.length]; System.arraycopy(gAlpha.arguments, 0, alphas, 0, alphas.length); InferenceSubstitution theta = new InferenceSubstitution(context.environment, alphas, context.currentInvocation) { @Override protected TypeBinding getP(int i) { return parameters[i]; } }; for (int i = 0, length = parameters.length; i < length; i++) { // A set of bounds on α1, ..., αn, constructed from the declared bounds of P1, ..., Pn as described in 18.1.3, is immediately implied. TypeVariableBinding pi = parameters[i]; InferenceVariable alpha = (InferenceVariable) gAlpha.arguments[i]; addBounds(pi.getTypeBounds(alpha, theta), context.environment); TypeBinding ai = gA.arguments[i]; if (ai instanceof WildcardBinding) { WildcardBinding wildcardBinding = (WildcardBinding)ai; TypeBinding t = wildcardBinding.bound; ThreeSets three = this.boundsPerVariable.get(alpha.prototype()); if (three != null) { Iterator<TypeBound> it; if (three.sameBounds != null) { // α = R implies false it = three.sameBounds.iterator(); while (it.hasNext()) { TypeBound bound = it.next(); if (InferenceContext18.SHOULD_WORKAROUND_BUG_JDK_8054721) { if (bound.right instanceof CaptureBinding && bound.right.isProperType(true)) continue; } if (!(bound.right instanceof InferenceVariable)) return false; } } if (three.subBounds != null && pi.firstBound != null) { // If Bi is Object, α <: R implies ⟨T <: R⟩ (extends wildcard) // α <: R implies ⟨θ Bi <: R⟩ (else) it = three.subBounds.iterator(); while (it.hasNext()) { TypeBound bound = it.next(); if (!(bound.right instanceof InferenceVariable)) { TypeBinding r = bound.right; TypeBinding bi1 = pi.firstBound; ReferenceBinding[] otherBounds = pi.superInterfaces; TypeBinding bi; if (otherBounds == Binding.NO_SUPERINTERFACES) { bi = bi1; } else { int n = otherBounds.length+1; ReferenceBinding[] allBounds = new ReferenceBinding[n]; allBounds[0] = (ReferenceBinding) bi1; // TODO is this safe? System.arraycopy(otherBounds, 0, allBounds, 1, n-1); bi = context.environment.createIntersectionType18(allBounds); } addTypeBoundsFromWildcardBound(context, theta, wildcardBinding.boundKind, t, r, bi); // if (otherBounds != null) { // for (int j = 0; j < otherBounds.length; j++) { // TypeBinding tj = otherBounds[j]; // if (TypeBinding.notEquals(tj, t)) // addTypeBoundsFromWildcardBound(context, wildcardBinding, tj, r, bij); // } // } } } } if (three.superBounds != null) { // R <: α implies ⟨R <: T⟩ (super wildcard) // R <: α implies false (else) it = three.superBounds.iterator(); while (it.hasNext()) { TypeBound bound = it.next(); if (!(bound.right instanceof InferenceVariable)) { if (wildcardBinding.boundKind == Wildcard.SUPER) reduceOneConstraint(context, ConstraintTypeFormula.create(bound.right, t, ReductionResult.SUBTYPE)); else return false; } } } } } else { addBound(new TypeBound(alpha, ai, ReductionResult.SAME), context.environment); } } } this.captures.clear(); return true; } void addTypeBoundsFromWildcardBound(InferenceContext18 context, InferenceSubstitution theta, int boundKind, TypeBinding t, TypeBinding r, TypeBinding bi) throws InferenceFailureException { ConstraintFormula formula = null; if (boundKind == Wildcard.EXTENDS) { if (bi.id == TypeIds.T_JavaLangObject) formula = ConstraintTypeFormula.create(t, r, ReductionResult.SUBTYPE); if (t.id == TypeIds.T_JavaLangObject) formula = ConstraintTypeFormula.create(theta.substitute(theta, bi), r, ReductionResult.SUBTYPE); } else { formula = ConstraintTypeFormula.create(theta.substitute(theta, bi), r, ReductionResult.SUBTYPE); } if (formula != null) reduceOneConstraint(context, formula); } private ConstraintTypeFormula combineSameSame(TypeBound boundS, TypeBound boundT) { // α = S and α = T imply ⟨S = T⟩ if (TypeBinding.equalsEquals(boundS.left, boundT.left)) return ConstraintTypeFormula.create(boundS.right, boundT.right, ReductionResult.SAME, boundS.isSoft||boundT.isSoft); // match against more shapes: ConstraintTypeFormula newConstraint; newConstraint = combineSameSameWithProperType(boundS, boundT); if (newConstraint != null) return newConstraint; newConstraint = combineSameSameWithProperType(boundT, boundS); if (newConstraint != null) return newConstraint; return null; } // pre: boundLeft.left != boundRight.left private ConstraintTypeFormula combineSameSameWithProperType(TypeBound boundLeft, TypeBound boundRight) { // α = U and S = T imply ⟨S[α:=U] = T[α:=U]⟩ TypeBinding u = boundLeft.right; if (u.isProperType(true)) { InferenceVariable alpha = boundLeft.left; TypeBinding left = boundRight.left; // no substitution since S inference variable and (S != α) per precondition TypeBinding right = boundRight.right.substituteInferenceVariable(alpha, u); return ConstraintTypeFormula.create(left, right, ReductionResult.SAME, boundLeft.isSoft||boundRight.isSoft); } return null; } private ConstraintTypeFormula combineSameSubSuper(TypeBound boundS, TypeBound boundT) { // α = S and α <: T imply ⟨S <: T⟩ // α = S and T <: α imply ⟨T <: S⟩ InferenceVariable alpha = boundS.left; TypeBinding s = boundS.right; if (TypeBinding.equalsEquals(alpha, boundT.left)) { TypeBinding t = boundT.right; return ConstraintTypeFormula.create(s, t, boundT.relation, boundT.isSoft||boundS.isSoft); } if (TypeBinding.equalsEquals(alpha, boundT.right)) { TypeBinding t = boundT.left; return ConstraintTypeFormula.create(t, s, boundT.relation, boundT.isSoft||boundS.isSoft); } if (boundS.right instanceof InferenceVariable) { // reverse: alpha = (InferenceVariable) boundS.right; s = boundS.left; if (TypeBinding.equalsEquals(alpha, boundT.left)) { TypeBinding t = boundT.right; return ConstraintTypeFormula.create(s, t, boundT.relation, boundT.isSoft||boundS.isSoft); } if (TypeBinding.equalsEquals(alpha, boundT.right)) { TypeBinding t = boundT.left; return ConstraintTypeFormula.create(t, s, boundT.relation, boundT.isSoft||boundS.isSoft); } } // α = U and S <: T imply ⟨S[α:=U] <: T[α:=U]⟩ TypeBinding u = boundS.right; if (u.isProperType(true)) { boolean substitute = TypeBinding.equalsEquals(alpha, boundT.left); TypeBinding left = substitute ? u : boundT.left; TypeBinding right = boundT.right.substituteInferenceVariable(alpha, u); substitute |= TypeBinding.notEquals(right, boundT.right); if (substitute) // avoid redundant constraint return ConstraintTypeFormula.create(left, right, boundT.relation, boundT.isSoft||boundS.isSoft); } return null; } private ConstraintTypeFormula combineSuperAndSub(TypeBound boundS, TypeBound boundT) { // permutations of: S <: α and α <: T imply ⟨S <: T⟩ InferenceVariable alpha = boundS.left; if (TypeBinding.equalsEquals(alpha, boundT.left)) // α >: S and α <: T imply ⟨S <: T⟩ return ConstraintTypeFormula.create(boundS.right, boundT.right, ReductionResult.SUBTYPE, boundT.isSoft||boundS.isSoft); if (boundS.right instanceof InferenceVariable) { // try reverse: alpha = (InferenceVariable) boundS.right; if (TypeBinding.equalsEquals(alpha, boundT.right)) // S :> α and T <: α imply ⟨S :> T⟩ return ConstraintTypeFormula.create(boundS.left, boundT.left, ReductionResult.SUPERTYPE, boundT.isSoft||boundS.isSoft); } return null; } private ConstraintTypeFormula combineEqualSupers(TypeBound boundS, TypeBound boundT) { // more permutations of: S <: α and α <: T imply ⟨S <: T⟩ if (TypeBinding.equalsEquals(boundS.left, boundT.right)) // came in as: α REL S and T REL α imply ⟨T REL S⟩ return ConstraintTypeFormula.create(boundT.left, boundS.right, boundS.relation, boundT.isSoft||boundS.isSoft); if (TypeBinding.equalsEquals(boundS.right, boundT.left)) // came in as: S REL α and α REL T imply ⟨S REL T⟩ return ConstraintTypeFormula.create(boundS.left, boundT.right, boundS.relation, boundT.isSoft||boundS.isSoft); return null; } private ConstraintTypeFormula[] deriveTypeArgumentConstraints(TypeBound boundS, TypeBound boundT) { /* From 18.4: * If two bounds have the form α <: S and α <: T, and if for some generic class or interface, G, * there exists a supertype (4.10) of S of the form G<S1, ..., Sn> and a supertype of T of the form G<T1, ..., Tn>, * then for all i, 1 ≤ i ≤ n, if Si and Ti are types (not wildcards), the constraint ⟨Si = Ti⟩ is implied. */ // callers must ensure both relations are <: and both lefts are equal TypeBinding[] supers = superTypesWithCommonGenericType(boundS.right, boundT.right); if (supers != null) return typeArgumentEqualityConstraints(supers[0], supers[1], boundS.isSoft || boundT.isSoft); return null; } private ConstraintTypeFormula[] typeArgumentEqualityConstraints(TypeBinding s, TypeBinding t, boolean isSoft) { if (s == null || s.kind() != Binding.PARAMETERIZED_TYPE || t == null || t.kind() != Binding.PARAMETERIZED_TYPE) return null; if (TypeBinding.equalsEquals(s, t)) // don't create useless constraints return null; TypeBinding[] sis = s.typeArguments(); TypeBinding[] tis = t.typeArguments(); if (sis == null || tis == null || sis.length != tis.length) return null; List<ConstraintTypeFormula> result = new ArrayList<>(); for (int i = 0; i < sis.length; i++) { TypeBinding si = sis[i]; TypeBinding ti = tis[i]; if (si.isWildcard() || ti.isWildcard() || TypeBinding.equalsEquals(si, ti)) continue; result.add(ConstraintTypeFormula.create(si, ti, ReductionResult.SAME, isSoft)); } if (result.size() > 0) return result.toArray(new ConstraintTypeFormula[result.size()]); return null; } /** * Try to reduce the one given constraint. * If a constraint produces further constraints reduce those recursively. * @throws InferenceFailureException a compile error has been detected during inference */ public boolean reduceOneConstraint(InferenceContext18 context, ConstraintFormula currentConstraint) throws InferenceFailureException { Object result = currentConstraint.reduce(context); if (result == ReductionResult.FALSE) return false; if (result == ReductionResult.TRUE) return true; if (result == currentConstraint) { // not reduceable throw new IllegalStateException("Failed to reduce constraint formula"); //$NON-NLS-1$ } if (result != null) { if (result instanceof ConstraintFormula) { if (!reduceOneConstraint(context, (ConstraintFormula) result)) return false; } else if (result instanceof ConstraintFormula[]) { ConstraintFormula[] resultArray = (ConstraintFormula[]) result; for (int i = 0; i < resultArray.length; i++) if (!reduceOneConstraint(context, resultArray[i])) return false; } else { addBound((TypeBound)result, context.environment); } } return true; // no FALSE encountered } /** * Helper for resolution (18.4): * Does this bound set define a direct dependency between the two given inference variables? */ public boolean dependsOnResolutionOf(InferenceVariable alpha, InferenceVariable beta) { alpha = alpha.prototype(); beta = beta.prototype(); if (TypeBinding.equalsEquals(alpha, beta)) return true; // An inference variable α depends on the resolution of itself. Iterator<Map.Entry<ParameterizedTypeBinding, ParameterizedTypeBinding>> captureIter = this.captures.entrySet().iterator(); boolean betaIsInCaptureLhs = false; while (captureIter.hasNext()) { // TODO: optimization: consider separate index structure (by IV) Entry<ParameterizedTypeBinding, ParameterizedTypeBinding> entry = captureIter.next(); ParameterizedTypeBinding g = entry.getKey(); for (int i = 0; i < g.arguments.length; i++) { if (TypeBinding.equalsEquals(g.arguments[i], alpha)) { // An inference variable α appearing on the left-hand side of a bound of the form G<..., α, ...> = capture(G<...>) // depends on the resolution of every other inference variable mentioned in this bound (on both sides of the = sign). ParameterizedTypeBinding captured = entry.getValue(); if (captured.mentionsAny(new TypeBinding[]{beta}, -1/*don't care about index*/)) return true; if (g.mentionsAny(new TypeBinding[]{beta}, i)) // exclude itself return true; } else if (TypeBinding.equalsEquals(g.arguments[i], beta)) { betaIsInCaptureLhs = true; } } } if (betaIsInCaptureLhs) { // swap α and β in the rule text to cover "then β depends on the resolution of α" ThreeSets sets = this.boundsPerVariable.get(beta); if (sets != null && sets.hasDependency(alpha)) return true; } else { ThreeSets sets = this.boundsPerVariable.get(alpha); if (sets != null && sets.hasDependency(beta)) return true; } return false; } List<Set<InferenceVariable>> computeConnectedComponents(InferenceVariable[] inferenceVariables) { // create all dependency edges (as bi-directional): Map<InferenceVariable, Set<InferenceVariable>> allEdges = new HashMap<>(); for (int i = 0; i < inferenceVariables.length; i++) { InferenceVariable iv1 = inferenceVariables[i]; HashSet<InferenceVariable> targetSet = new HashSet<InferenceVariable>(); allEdges.put(iv1, targetSet); // eventually ensures: forall iv in inferenceVariables : allEdges.get(iv) != null for (int j = 0; j < i; j++) { InferenceVariable iv2 = inferenceVariables[j]; if (dependsOnResolutionOf(iv1, iv2) || dependsOnResolutionOf(iv2, iv1)) { targetSet.add(iv2); allEdges.get(iv2).add(iv1); } } } // collect all connected IVs into one component: Set<InferenceVariable> visited = new HashSet<>(); List<Set<InferenceVariable>> allComponents = new ArrayList<>(); for (InferenceVariable inferenceVariable : inferenceVariables) { Set<InferenceVariable> component = new HashSet<>(); addConnected(component, inferenceVariable, allEdges, visited); if (!component.isEmpty()) allComponents.add(component); } return allComponents; } private void addConnected(Set<InferenceVariable> component, InferenceVariable seed, Map<InferenceVariable, Set<InferenceVariable>> allEdges, Set<InferenceVariable> visited) { if (visited.add(seed)) { // add all IVs starting from seed and reachable via any in allEdges: component.add(seed); for (InferenceVariable next : allEdges.get(seed)) addConnected(component, next, allEdges, visited); } } // helper for 18.4 public boolean hasCaptureBound(Set<InferenceVariable> variableSet) { Iterator<ParameterizedTypeBinding> captureIter = this.captures.keySet().iterator(); while (captureIter.hasNext()) { ParameterizedTypeBinding g = captureIter.next(); for (int i = 0; i < g.arguments.length; i++) if (variableSet.contains(g.arguments[i])) return true; } return false; } // helper for 18.4 public boolean hasOnlyTrivialExceptionBounds(InferenceVariable variable, TypeBinding[] upperBounds) { if (upperBounds != null) { for (int i = 0; i < upperBounds.length; i++) { switch (upperBounds[i].id) { case TypeIds.T_JavaLangException: case TypeIds.T_JavaLangThrowable: case TypeIds.T_JavaLangObject: continue; } return false; } } return true; } /** * JLS 18.1.3: * Answer all upper bounds for the given inference variable as defined by any bounds in this set. */ public TypeBinding[] upperBounds(InferenceVariable variable, boolean onlyProper) { ThreeSets three = this.boundsPerVariable.get(variable.prototype()); if (three == null || three.subBounds == null) return Binding.NO_TYPES; return three.upperBounds(onlyProper, variable); // TODO: if !onlyProper: should we also consider ThreeSets.inverseBounds, // or is it safe to rely on incorporation to produce the required bounds? } /** * JLS 18.1.3: * Answer all lower bounds for the given inference variable as defined by any bounds in this set. */ TypeBinding[] lowerBounds(InferenceVariable variable, boolean onlyProper) { ThreeSets three = this.boundsPerVariable.get(variable.prototype()); if (three == null || three.superBounds == null) return Binding.NO_TYPES; return three.lowerBounds(onlyProper, variable); // bounds where 'variable' appears at the RHS are not relevant because // we're only interested in bounds with a proper type, but if 'variable' // appears as RHS the bound is by construction an inference variable,too. } // debugging: public String toString() { StringBuffer buf = new StringBuffer("Type Bounds:\n"); //$NON-NLS-1$ TypeBound[] flattened = flatten(); for (int i = 0; i < flattened.length; i++) { buf.append('\t').append(flattened[i].toString()).append('\n'); } buf.append("Capture Bounds:\n"); //$NON-NLS-1$ Iterator<Map.Entry<ParameterizedTypeBinding,ParameterizedTypeBinding>> captIter = this.captures.entrySet().iterator(); while (captIter.hasNext()) { Entry<ParameterizedTypeBinding, ParameterizedTypeBinding> capt = captIter.next(); String lhs = String.valueOf(((TypeBinding)capt.getKey()).shortReadableName()); String rhs = String.valueOf(((TypeBinding)capt.getValue()).shortReadableName()); buf.append('\t').append(lhs).append(" = capt(").append(rhs).append(")\n"); //$NON-NLS-1$ //$NON-NLS-2$ } return buf.toString(); } public TypeBinding findWrapperTypeBound(InferenceVariable variable) { ThreeSets three = this.boundsPerVariable.get(variable.prototype()); if (three == null) return null; return three.findSingleWrapperType(); } // this condition is just way too complex to check it in-line: public boolean condition18_5_2_bullet_3_3_1(InferenceVariable alpha, TypeBinding targetType) { // T is a reference type, but is not a wildcard-parameterized type, and either // i) B2 contains a bound of one of the forms α = S or S <: α, where S is a wildcard-parameterized type, or ... if (targetType.isBaseType()) return false; if (InferenceContext18.parameterizedWithWildcard(targetType) != null) return false; ThreeSets ts = this.boundsPerVariable.get(alpha.prototype()); if (ts == null) return false; if (ts.sameBounds != null) { Iterator<TypeBound> bounds = ts.sameBounds.iterator(); while (bounds.hasNext()) { TypeBound bound = bounds.next(); if (InferenceContext18.parameterizedWithWildcard(bound.right) != null) return true; } } if (ts.superBounds != null) { Iterator<TypeBound> bounds = ts.superBounds.iterator(); while (bounds.hasNext()) { TypeBound bound = bounds.next(); if (InferenceContext18.parameterizedWithWildcard(bound.right) != null) return true; } } // ii) B2 contains two bounds of the forms S1 <: α and S2 <: α, where // S1 and S2 have supertypes (4.10) that are two different parameterizations of the same generic class or interface. if (ts.superBounds != null) { ArrayList<TypeBound> superBounds = new ArrayList<>(ts.superBounds); int len = superBounds.size(); for (int i=0; i<len; i++) { TypeBinding s1 = superBounds.get(i).right; for (int j=i+1; j<len; j++) { TypeBinding s2 = superBounds.get(j).right; TypeBinding[] supers = superTypesWithCommonGenericType(s1, s2); if (supers != null) { /* HashMap<K#8,V#9> and HashMap<K#8,ArrayList<T>> with an instantiation for V9 = ArrayList<T> already in the bound set should not be seen as two different parameterizations of the same generic class or interface. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=432626 for a test that triggers this condition. See https://bugs.openjdk.java.net/browse/JDK-8056092: recommendation is to check for proper types. */ if (supers[0].isProperType(true) && supers[1].isProperType(true) && !TypeBinding.equalsEquals(supers[0], supers[1])) return true; } } } } return false; } public boolean condition18_5_2_bullet_3_3_2(InferenceVariable alpha, TypeBinding targetType, InferenceContext18 ctx18) { // T is a parameterization of a generic class or interface, G, and // B2 contains a bound of one of the forms α = S or S <: α, // where there exists no type of the form G<...> that is a supertype of S, but the raw type G is a supertype of S. if (!targetType.isParameterizedType()) return false; TypeBinding g = targetType.original(); ThreeSets ts = this.boundsPerVariable.get(alpha.prototype()); if (ts == null) return false; Iterator<TypeBound> boundIterator; if (ts.sameBounds != null) { boundIterator = ts.sameBounds.iterator(); while (boundIterator.hasNext()) { TypeBound b = boundIterator.next(); if (superOnlyRaw(g, b.right, ctx18.environment)) return true; } } if (ts.superBounds != null) { boundIterator = ts.superBounds.iterator(); while (boundIterator.hasNext()) { TypeBound b = boundIterator.next(); if (superOnlyRaw(g, b.right, ctx18.environment)) return true; } } return false; } private boolean superOnlyRaw(TypeBinding g, TypeBinding s, LookupEnvironment env) { if (s instanceof InferenceVariable) return false; // inference has no super types final TypeBinding superType = s.findSuperTypeOriginatingFrom(g); if (superType != null && !superType.isParameterizedType()) return s.isCompatibleWith(env.convertToRawType(g, false)); return false; } protected TypeBinding[] superTypesWithCommonGenericType(TypeBinding s, TypeBinding t) { if (s == null || s.id == TypeIds.T_JavaLangObject || t == null || t.id == TypeIds.T_JavaLangObject) return null; if (TypeBinding.equalsEquals(s.original(), t.original())) { return new TypeBinding[] { s, t }; } TypeBinding tSuper = t.findSuperTypeOriginatingFrom(s); if (tSuper != null) { return new TypeBinding[] {s, tSuper}; } TypeBinding[] result = superTypesWithCommonGenericType(s.superclass(), t); if (result != null) return result; ReferenceBinding[] superInterfaces = s.superInterfaces(); if (superInterfaces != null) { for (int i = 0; i < superInterfaces.length; i++) { result = superTypesWithCommonGenericType(superInterfaces[i], t); if (result != null) return result; } } return null; } public TypeBinding getEquivalentOuterVariable(InferenceVariable variable, InferenceVariable[] outerVariables) { ThreeSets three = this.boundsPerVariable.get(variable); if (three != null) { for (TypeBound bound : three.sameBounds) { for (InferenceVariable iv : outerVariables) if (TypeBinding.equalsEquals(bound.right, iv)) return iv; } } for (InferenceVariable iv : outerVariables) { three = this.boundsPerVariable.get(outerVariables); if (three != null) { for (TypeBound bound : three.sameBounds) if (TypeBinding.equalsEquals(bound.right, variable)) return iv; } } return null; } }