| /******************************************************************************* |
| * Copyright (c) 2013, 2017 GK Software AG. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Stephan Herrmann - initial API and implementation |
| * 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; |
| |
| /** |
| * 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 |
| Set<InferenceVariable> dependencies; |
| public ThreeSets() { |
| // empty, the sets are lazily initialized |
| } |
| /** Add a type bound to the appropriate set. */ |
| public boolean addBound(TypeBound bound) { |
| boolean result = addBound1(bound); |
| if(result) { |
| Set<InferenceVariable> set = (this.dependencies == null ? new HashSet<>() : this.dependencies); |
| bound.right.collectInferenceVariables(set); |
| if (this.dependencies == null && set.size() > 0) { |
| this.dependencies = set; |
| } |
| } |
| return result; |
| } |
| private boolean addBound1(TypeBound bound) { |
| switch (bound.relation) { |
| case ReductionResult.SUPERTYPE: |
| if (this.superBounds == null) this.superBounds = new HashSet<>(); |
| 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<>(); |
| 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) { |
| TypeBinding[] rights = new TypeBinding[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++] = 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 TypeBinding[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.dependencies != null && this.dependencies.contains(beta)) |
| return true; |
| if (this.inverseBounds != null) { |
| if (this.inverseBounds.containsKey(beta)) { |
| // TODO: not yet observed in tests |
| 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; |
| if (this.dependencies != null) { |
| copy.dependencies = new HashSet<>(this.dependencies); |
| } |
| 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 (!(bound.right instanceof InferenceVariable)) |
| return false; |
| } |
| } |
| if (three.subBounds != null) { |
| TypeBinding bi1 = pi.firstBound; |
| if (bi1 == null) { |
| bi1 = context.object; // implicit bound |
| } |
| // 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; |
| 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 (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: |
| @Override |
| 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(iv); |
| if (three != null && three.sameBounds != null) { |
| for (TypeBound bound : three.sameBounds) |
| if (TypeBinding.equalsEquals(bound.right, variable)) |
| return iv; |
| } |
| } |
| return null; |
| } |
| } |