org.eclipse.jdt.internal.compiler.lookup.BoundSet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ecj Show documentation
Show all versions of ecj Show documentation
Eclipse Compiler for Java(TM)
/*******************************************************************************
* 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 - 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 superBounds;
Set sameBounds;
Set subBounds;
TypeBinding instantiation;
Map inverseBounds; // from right inference variable to bound
Set 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 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 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 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.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 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 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 it = this.sameBounds.iterator();
while(it.hasNext())
nullHints |= it.next().nullHints;
}
if (this.superBounds != null) {
Iterator it = this.superBounds.iterator();
while(it.hasNext())
nullHints |= it.next().nullHints;
}
if (this.subBounds != null) {
Iterator 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 boundsPerVariable = new HashMap<>();
/**
* 18.1.3 bullet 4: G<α1, ..., αn> = capture(G)
* On both sides we only enter types with nonnull arguments.
*/
HashMap captures = new HashMap<>();
/** 18.1.3 bullet 5: throws α */
Set 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 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> setsIterator = this.boundsPerVariable.entrySet().iterator();
while (setsIterator.hasNext()) {
Entry 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;
}
/**
* JLS 18.3: 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> captIter = this.captures.entrySet().iterator();
while (captIter.hasNext()) {
Entry 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 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 and a supertype of T of the form G,
* 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 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> captureIter = this.captures.entrySet().iterator();
boolean betaIsInCaptureLhs = false;
while (captureIter.hasNext()) { // TODO: optimization: consider separate index structure (by IV)
Entry 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> computeConnectedComponents(InferenceVariable[] inferenceVariables) {
// create all dependency edges (as bi-directional):
Map> allEdges = new HashMap<>();
for (int i = 0; i < inferenceVariables.length; i++) {
InferenceVariable iv1 = inferenceVariables[i];
HashSet targetSet = new HashSet();
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 visited = new HashSet<>();
List> allComponents = new ArrayList<>();
for (InferenceVariable inferenceVariable : inferenceVariables) {
Set component = new HashSet<>();
addConnected(component, inferenceVariable, allEdges, visited);
if (!component.isEmpty())
allComponents.add(component);
}
return allComponents;
}
private void addConnected(Set component, InferenceVariable seed,
Map> allEdges, Set 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 variableSet) {
Iterator 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> captIter = this.captures.entrySet().iterator();
while (captIter.hasNext()) {
Entry 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 bounds = ts.sameBounds.iterator();
while (bounds.hasNext()) {
TypeBound bound = bounds.next();
if (InferenceContext18.parameterizedWithWildcard(bound.right) != null)
return true;
}
}
if (ts.superBounds != null) {
Iterator 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 superBounds = new ArrayList<>(ts.superBounds);
int len = superBounds.size();
for (int i=0; i and HashMap> with an instantiation for V9 = ArrayList 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 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;
}
}