org.eclipse.jdt.internal.compiler.lookup.CaptureBinding 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) 2000, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Stephan Herrmann - Contribution for
* Bug 400874 - [1.8][compiler] Inference infrastructure should evolve to meet JLS8 18.x (Part G of JSR335 spec)
* Bug 429384 - [1.8][null] implement conformance rules for null-annotated lower / upper type bounds
* Bug 441797 - [1.8] synchronize type annotations on capture and its wildcard
* Bug 456497 - [1.8][null] during inference nullness from target type is lost against weaker hint from applicability analysis
* Bug 456924 - StackOverflowError during compilation
* Bug 462790 - [null] NPE in Expression.computeConversion()
* Jesper S Møller - Contributions for bug 381345 : [1.8] Take care of the Java 8 major version
* Bug 527554 - [18.3] Compiler support for JEP 286 Local-Variable Type
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
public class CaptureBinding extends TypeVariableBinding {
public TypeBinding lowerBound;
public WildcardBinding wildcard;
public int captureID;
/* information to compute unique binding key */
public ReferenceBinding sourceType;
public int start;
public int end;
public ASTNode cud; // to facilitate recaptures.
TypeBinding pendingSubstitute; // for substitution of recursive captures, see https://bugs.eclipse.org/456924
public CaptureBinding(WildcardBinding wildcard, ReferenceBinding sourceType, int start, int end, ASTNode cud, int captureID) {
super(TypeConstants.WILDCARD_CAPTURE_NAME_PREFIX, wildcard.environment);
this.wildcard = wildcard;
this.modifiers = ClassFileConstants.AccPublic | ExtraCompilerModifiers.AccGenericSignature; // treat capture as public
this.fPackage = wildcard.fPackage;
this.sourceType = sourceType;
this.start = start;
this.end = end;
this.captureID = captureID;
this.tagBits |= TagBits.HasCapturedWildcard;
this.cud = cud;
if (wildcard.hasTypeAnnotations()) {
// register an unannoted version before adding the annotated wildcard:
CaptureBinding unannotated = (CaptureBinding) clone(null);
unannotated.wildcard = (WildcardBinding) this.wildcard.unannotated();
this.environment.getUnannotatedType(unannotated);
this.id = unannotated.id; // transfer fresh id
// now register this annotated type:
this.environment.typeSystem.cacheDerivedType(this, unannotated, this);
// propagate from wildcard to capture - use super version, because our own method propagates type annotations in the opposite direction:
super.setTypeAnnotations(wildcard.getTypeAnnotations(), wildcard.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled);
if (wildcard.hasNullTypeAnnotations())
this.tagBits |= TagBits.HasNullTypeAnnotation;
} else {
computeId(this.environment);
if(wildcard.hasNullTypeAnnotations()) {
this.tagBits |= (wildcard.tagBits & TagBits.AnnotationNullMASK) | TagBits.HasNullTypeAnnotation;
}
}
}
// for subclass CaptureBinding18
protected CaptureBinding(ReferenceBinding sourceType, char[] sourceName, int start, int end, int captureID, LookupEnvironment environment) {
super(sourceName, null, 0, environment);
this.modifiers = ClassFileConstants.AccPublic | ExtraCompilerModifiers.AccGenericSignature; // treat capture as public
this.sourceType = sourceType;
this.start = start;
this.end = end;
this.captureID = captureID;
}
public CaptureBinding(CaptureBinding prototype) {
super(prototype);
this.wildcard = prototype.wildcard;
this.sourceType = prototype.sourceType;
this.start = prototype.start;
this.end = prototype.end;
this.captureID = prototype.captureID;
this.lowerBound = prototype.lowerBound;
this.tagBits |= (prototype.tagBits & TagBits.HasCapturedWildcard);
this.cud = prototype.cud;
}
// Captures may get cloned and annotated during type inference.
@Override
public TypeBinding clone(TypeBinding enclosingType) {
return new CaptureBinding(this);
}
/*
* sourceTypeKey ! wildcardKey position semi-colon
* p.X { capture of ? } --> !*123; (Lp/X; in declaring type except if leaf)
* p.X { capture of ? extends p.Y } --> !+Lp/Y;123; (Lp/X; in declaring type except if leaf)
*/
@Override
public char[] computeUniqueKey(boolean isLeaf) {
StringBuffer buffer = new StringBuffer();
if (isLeaf) {
buffer.append(this.sourceType.computeUniqueKey(false/*not a leaf*/));
buffer.append('&');
}
buffer.append(TypeConstants.WILDCARD_CAPTURE);
buffer.append(this.wildcard.computeUniqueKey(false/*not a leaf*/));
buffer.append(this.end);
buffer.append(';');
int length = buffer.length();
char[] uniqueKey = new char[length];
buffer.getChars(0, length, uniqueKey, 0);
return uniqueKey;
}
@Override
public String debugName() {
if (this.wildcard != null) {
StringBuffer buffer = new StringBuffer(10);
AnnotationBinding [] annotations = getTypeAnnotations();
for (int i = 0, length = annotations == null ? 0 : annotations.length; i < length; i++) {
buffer.append(annotations[i]);
buffer.append(' ');
}
buffer
.append(TypeConstants.WILDCARD_CAPTURE_NAME_PREFIX)
.append(this.captureID)
.append(TypeConstants.WILDCARD_CAPTURE_NAME_SUFFIX)
.append(this.wildcard.debugName());
return buffer.toString();
}
return super.debugName();
}
@Override
public char[] genericTypeSignature() {
// captures have no signature per JVMS 4.7.9.1, approximate one by erasure:
if (this.inRecursiveFunction) {
// catch "capture#1 of X":
// prefer answering "Ljava.lang.Object;" instead of throwing StackOverflowError:
return CharOperation.concat(new char[] {'L'}, CharOperation.concatWith(TypeConstants.JAVA_LANG_OBJECT, '.'), new char[] {';'});
}
this.inRecursiveFunction = true;
try {
return erasure().genericTypeSignature();
} finally {
this.inRecursiveFunction = false;
}
}
/**
* Initialize capture bounds using substituted supertypes
* e.g. given X>, capture(X) = X, where capture extends X
*/
public void initializeBounds(Scope scope, ParameterizedTypeBinding capturedParameterizedType) {
boolean is18plus = scope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_8;
TypeVariableBinding wildcardVariable = this.wildcard.typeVariable();
if (wildcardVariable == null) {
// error resilience when capturing Zork>
// no substitution for wildcard bound (only formal bounds from type variables are to be substituted: 104082)
TypeBinding originalWildcardBound = this.wildcard.bound;
switch (this.wildcard.boundKind) {
case Wildcard.EXTENDS :
// still need to capture bound supertype as well so as not to expose wildcards to the outside (111208)
TypeBinding capturedWildcardBound = is18plus
? originalWildcardBound // as spec'd
: originalWildcardBound.capture(scope, this.start, this.end); // for compatibility with old behavior at 1.7-
if (originalWildcardBound.isInterface()) {
this.setSuperClass(scope.getJavaLangObject());
this.setSuperInterfaces(new ReferenceBinding[] { (ReferenceBinding) capturedWildcardBound });
} else {
// the wildcard bound should be a subtype of variable superclass
// it may occur that the bound is less specific, then consider glb (202404)
if (capturedWildcardBound.isArrayType() || TypeBinding.equalsEquals(capturedWildcardBound, this)) {
this.setSuperClass(scope.getJavaLangObject());
} else {
this.setSuperClass((ReferenceBinding) capturedWildcardBound);
}
this.setSuperInterfaces(Binding.NO_SUPERINTERFACES);
}
this.setFirstBound(capturedWildcardBound);
if ((capturedWildcardBound.tagBits & TagBits.HasTypeVariable) == 0)
this.tagBits &= ~TagBits.HasTypeVariable;
break;
case Wildcard.UNBOUND :
this.setSuperClass(scope.getJavaLangObject());
this.setSuperInterfaces(Binding.NO_SUPERINTERFACES);
this.tagBits &= ~TagBits.HasTypeVariable;
break;
case Wildcard.SUPER :
this.setSuperClass(scope.getJavaLangObject());
this.setSuperInterfaces(Binding.NO_SUPERINTERFACES);
this.lowerBound = this.wildcard.bound;
if ((originalWildcardBound.tagBits & TagBits.HasTypeVariable) == 0)
this.tagBits &= ~TagBits.HasTypeVariable;
break;
}
return;
}
ReferenceBinding originalVariableSuperclass = wildcardVariable.superclass;
ReferenceBinding substitutedVariableSuperclass = (ReferenceBinding) Scope.substitute(capturedParameterizedType, originalVariableSuperclass);
// prevent cyclic capture: given X, capture(X extends T> could yield a circular type
if (TypeBinding.equalsEquals(substitutedVariableSuperclass, this)) substitutedVariableSuperclass = originalVariableSuperclass;
ReferenceBinding[] originalVariableInterfaces = wildcardVariable.superInterfaces();
ReferenceBinding[] substitutedVariableInterfaces = Scope.substitute(capturedParameterizedType, originalVariableInterfaces);
if (substitutedVariableInterfaces != originalVariableInterfaces) {
// prevent cyclic capture: given X, capture(X extends T> could yield a circular type
for (int i = 0, length = substitutedVariableInterfaces.length; i < length; i++) {
if (TypeBinding.equalsEquals(substitutedVariableInterfaces[i], this)) substitutedVariableInterfaces[i] = originalVariableInterfaces[i];
}
}
// no substitution for wildcard bound (only formal bounds from type variables are to be substituted: 104082)
TypeBinding originalWildcardBound = this.wildcard.bound;
switch (this.wildcard.boundKind) {
case Wildcard.EXTENDS :
// still need to capture bound supertype as well so as not to expose wildcards to the outside (111208)
TypeBinding capturedWildcardBound = is18plus
? originalWildcardBound // as spec'd
: originalWildcardBound.capture(scope, this.start, this.end); // for compatibility with old behavior at 1.7-
if (originalWildcardBound.isInterface()) {
this.setSuperClass(substitutedVariableSuperclass);
// merge wildcard bound into variable superinterfaces using glb
if (substitutedVariableInterfaces == Binding.NO_SUPERINTERFACES) {
this.setSuperInterfaces(new ReferenceBinding[] { (ReferenceBinding) capturedWildcardBound });
} else {
int length = substitutedVariableInterfaces.length;
System.arraycopy(substitutedVariableInterfaces, 0, substitutedVariableInterfaces = new ReferenceBinding[length+1], 1, length);
substitutedVariableInterfaces[0] = (ReferenceBinding) capturedWildcardBound;
this.setSuperInterfaces(Scope.greaterLowerBound(substitutedVariableInterfaces));
}
} else {
// the wildcard bound should be a subtype of variable superclass
// it may occur that the bound is less specific, then consider glb (202404)
if (capturedWildcardBound.isArrayType() || TypeBinding.equalsEquals(capturedWildcardBound, this)) {
this.setSuperClass(substitutedVariableSuperclass);
} else {
this.setSuperClass((ReferenceBinding) capturedWildcardBound);
if (this.superclass.isSuperclassOf(substitutedVariableSuperclass)) {
this.setSuperClass(substitutedVariableSuperclass);
}
// TODO: there are cases were we need to compute glb(capturedWildcardBound, substitutedVariableSuperclass)
// but then when glb (perhaps triggered inside setFirstBound()) fails, how to report the error??
}
this.setSuperInterfaces(substitutedVariableInterfaces);
}
this.setFirstBound(capturedWildcardBound);
if ((capturedWildcardBound.tagBits & TagBits.HasTypeVariable) == 0)
this.tagBits &= ~TagBits.HasTypeVariable;
break;
case Wildcard.UNBOUND :
this.setSuperClass(substitutedVariableSuperclass);
this.setSuperInterfaces(substitutedVariableInterfaces);
this.tagBits &= ~TagBits.HasTypeVariable;
break;
case Wildcard.SUPER :
this.setSuperClass(substitutedVariableSuperclass);
if (TypeBinding.equalsEquals(wildcardVariable.firstBound, substitutedVariableSuperclass) || TypeBinding.equalsEquals(originalWildcardBound, substitutedVariableSuperclass)) {
this.setFirstBound(substitutedVariableSuperclass);
}
this.setSuperInterfaces(substitutedVariableInterfaces);
this.lowerBound = originalWildcardBound;
if ((originalWildcardBound.tagBits & TagBits.HasTypeVariable) == 0)
this.tagBits &= ~TagBits.HasTypeVariable;
break;
}
if(scope.environment().usesNullTypeAnnotations()) {
evaluateNullAnnotations(scope, null);
}
}
@Override
public ReferenceBinding upwardsProjection(Scope scope, TypeBinding[] mentionedTypeVariables) {
if (enterRecursiveProjectionFunction()) {
try {
for (int i = 0; i < mentionedTypeVariables.length; ++i) {
if (TypeBinding.equalsEquals(this, mentionedTypeVariables[i])) {
TypeBinding upperBoundForProjection = this.upperBoundForProjection();
return ((ReferenceBinding)upperBoundForProjection).upwardsProjection(scope, mentionedTypeVariables);
}
}
return this;
} finally {
exitRecursiveProjectionFunction();
}
} else {
return scope.getJavaLangObject();
}
}
public TypeBinding upperBoundForProjection() {
TypeBinding upperBound = null;
if (this.wildcard != null) {
ReferenceBinding[] supers = this.superInterfaces();
if (this.wildcard.boundKind == Wildcard.EXTENDS) {
if (supers.length > 0) {
ReferenceBinding[] allBounds = new ReferenceBinding[supers.length + 1];
System.arraycopy(supers, 0, allBounds, 1, supers.length);
allBounds[0] = this.superclass();
ReferenceBinding[] glbs = Scope.greaterLowerBound(allBounds);
if (glbs == null) {
upperBound = new ProblemReferenceBinding(null, null, ProblemReasons.ParameterBoundMismatch);
} else if (glbs.length == 1) {
upperBound = glbs[0];
} else {
upperBound = this.environment.createIntersectionType18(glbs);
}
} else {
upperBound = this.superclass;
}
} else {
// ITB18.isCompatibleWith does not handle the presence of j.l.Object among intersecting types,
// so it returns false when checking (I&J).isCompatibleWith(Object&I&J)
// TODO see if this can be handled in ITB18.isCompatibleWith() itself
boolean superClassIsObject = TypeBinding.equalsEquals(this.superclass(), this.environment.getResolvedJavaBaseType(TypeConstants.JAVA_LANG_OBJECT, null));
if (supers.length == 0) {
upperBound = this.superclass();
} else if (supers.length == 1) {
upperBound = superClassIsObject ? supers[0] : this.environment.createIntersectionType18(new ReferenceBinding[] {this.superclass(), supers[0]});
} else {
if (superClassIsObject) {
upperBound = this.environment.createIntersectionType18(supers);
} else {
ReferenceBinding[] allBounds = new ReferenceBinding[supers.length + 1];
System.arraycopy(supers, 0, allBounds, 1, supers.length);
allBounds[0] = this.superclass();
upperBound = this.environment.createIntersectionType18(allBounds);
}
}
}
} else {
upperBound = super.upperBound();
}
return upperBound;
}
/**
* @see org.eclipse.jdt.internal.compiler.lookup.TypeBinding#isCapture()
*/
@Override
public boolean isCapture() {
return true;
}
/**
* @see TypeBinding#isEquivalentTo(TypeBinding)
*/
@Override
public boolean isEquivalentTo(TypeBinding otherType) {
if (equalsEquals(this, otherType)) return true;
if (otherType == null) return false;
// capture of ? extends X[]
if (this.firstBound != null && this.firstBound.isArrayType()) {
if (this.firstBound.isCompatibleWith(otherType))
return true;
}
switch (otherType.kind()) {
case Binding.WILDCARD_TYPE :
case Binding.INTERSECTION_TYPE :
return ((WildcardBinding) otherType).boundCheck(this);
}
return false;
}
@Override
public boolean isProperType(boolean admitCapture18) {
if (this.lowerBound != null && !this.lowerBound.isProperType(admitCapture18))
return false;
if (this.wildcard != null && !this.wildcard.isProperType(admitCapture18))
return false;
return super.isProperType(admitCapture18);
}
@Override
public char[] readableName() {
if (this.wildcard != null) {
StringBuffer buffer = new StringBuffer(10);
buffer
.append(TypeConstants.WILDCARD_CAPTURE_NAME_PREFIX)
.append(this.captureID)
.append(TypeConstants.WILDCARD_CAPTURE_NAME_SUFFIX)
.append(this.wildcard.readableName());
int length = buffer.length();
char[] name = new char[length];
buffer.getChars(0, length, name, 0);
return name;
}
return super.readableName();
}
@Override
public char[] signableName() {
if (this.wildcard != null) {
StringBuffer buffer = new StringBuffer(10);
buffer
.append(TypeConstants.WILDCARD_CAPTURE_SIGNABLE_NAME_SUFFIX)
.append(this.wildcard.readableName());
int length = buffer.length();
char[] name = new char[length];
buffer.getChars(0, length, name, 0);
return name;
}
return super.readableName();
}
@Override
public char[] shortReadableName() {
if (this.wildcard != null) {
StringBuffer buffer = new StringBuffer(10);
buffer
.append(TypeConstants.WILDCARD_CAPTURE_NAME_PREFIX)
.append(this.captureID)
.append(TypeConstants.WILDCARD_CAPTURE_NAME_SUFFIX)
.append(this.wildcard.shortReadableName());
int length = buffer.length();
char[] name = new char[length];
buffer.getChars(0, length, name, 0);
return name;
}
return super.shortReadableName();
}
@Override
public char[] nullAnnotatedReadableName(CompilerOptions options, boolean shortNames) {
StringBuffer nameBuffer = new StringBuffer(10);
appendNullAnnotation(nameBuffer, options);
nameBuffer.append(this.sourceName());
if (!this.inRecursiveFunction) { // CaptureBinding18 can be recursive indeed
this.inRecursiveFunction = true;
try {
if (this.wildcard != null) {
nameBuffer.append("of "); //$NON-NLS-1$
nameBuffer.append(this.wildcard.withoutToplevelNullAnnotation().nullAnnotatedReadableName(options, shortNames));
} else if (this.lowerBound != null) {
nameBuffer.append(" super "); //$NON-NLS-1$
nameBuffer.append(this.lowerBound.nullAnnotatedReadableName(options, shortNames));
} else if (this.firstBound != null) {
nameBuffer.append(" extends "); //$NON-NLS-1$
nameBuffer.append(this.firstBound.nullAnnotatedReadableName(options, shortNames));
TypeBinding[] otherUpperBounds = this.otherUpperBounds();
if (otherUpperBounds != NO_TYPES)
nameBuffer.append(" & ..."); //$NON-NLS-1$ // only hint at more bounds, we currently don't evaluate null annotations on otherUpperBounds
}
} finally {
this.inRecursiveFunction = false;
}
}
int nameLength = nameBuffer.length();
char[] readableName = new char[nameLength];
nameBuffer.getChars(0, nameLength, readableName, 0);
return readableName;
}
@Override
public TypeBinding withoutToplevelNullAnnotation() {
if (!hasNullTypeAnnotations())
return this;
if (this.wildcard != null && this.wildcard.hasNullTypeAnnotations()) {
WildcardBinding newWildcard = (WildcardBinding) this.wildcard.withoutToplevelNullAnnotation();
if (newWildcard != this.wildcard) { //$IDENTITY-COMPARISON$
CaptureBinding newCapture = (CaptureBinding) this.environment.getUnannotatedType(this).clone(null);
if (newWildcard.hasTypeAnnotations())
newCapture.tagBits |= TagBits.HasTypeAnnotations;
newCapture.wildcard = newWildcard;
// manually transfer the following two, because we are not in a context where we can call initializeBounds():
newCapture.superclass = this.superclass;
newCapture.superInterfaces = this.superInterfaces;
AnnotationBinding[] newAnnotations = this.environment.filterNullTypeAnnotations(this.typeAnnotations);
return this.environment.createAnnotatedType(newCapture, newAnnotations);
}
}
return super.withoutToplevelNullAnnotation();
}
@Override
TypeBinding substituteInferenceVariable(InferenceVariable var, TypeBinding substituteType) {
if (this.pendingSubstitute != null)
return this.pendingSubstitute;
try {
TypeBinding substitutedWildcard = this.wildcard.substituteInferenceVariable(var, substituteType);
if (substitutedWildcard != this.wildcard) { //$IDENTITY-COMPARISON$
CaptureBinding substitute = (CaptureBinding) clone(enclosingType());
substitute.wildcard = (WildcardBinding) substitutedWildcard;
this.pendingSubstitute = substitute;
if (this.lowerBound != null)
substitute.lowerBound = this.lowerBound.substituteInferenceVariable(var, substituteType);
if (this.firstBound != null)
substitute.firstBound = this.firstBound.substituteInferenceVariable(var, substituteType);
if (this.superclass != null)
substitute.superclass = (ReferenceBinding) this.superclass.substituteInferenceVariable(var, substituteType);
if (this.superInterfaces != null) {
int length = this.superInterfaces.length;
substitute.superInterfaces = new ReferenceBinding[length];
for (int i = 0; i < length; i++)
substitute.superInterfaces[i] = (ReferenceBinding) this.superInterfaces[i].substituteInferenceVariable(var, substituteType);
}
return substitute;
}
return this;
} finally {
this.pendingSubstitute = null;
}
}
@Override
public void setTypeAnnotations(AnnotationBinding[] annotations, boolean evalNullAnnotations) {
super.setTypeAnnotations(annotations, evalNullAnnotations);
if (annotations != Binding.NO_ANNOTATIONS && this.wildcard != null) {
// keep annotations in sync, propagate from capture to its wildcard:
this.wildcard = (WildcardBinding) this.wildcard.environment.createAnnotatedType(this.wildcard, annotations);
}
}
@Override
public TypeBinding uncapture(Scope scope) {
return this.wildcard;
}
@Override
public ReferenceBinding downwardsProjection(Scope scope, TypeBinding[] mentionedTypeVariables) {
ReferenceBinding result = null;
if (enterRecursiveProjectionFunction()) {
for (int i = 0; i < mentionedTypeVariables.length; ++i) {
if (TypeBinding.equalsEquals(this, mentionedTypeVariables[i])) {
if (this.lowerBound != null) {
result = (ReferenceBinding) this.lowerBound.downwardsProjection(scope, mentionedTypeVariables);
}
break;
}
}
exitRecursiveProjectionFunction();
}
return result;
}
/*
* CaptureBinding needs even more propagation, because we are creating a naked type
* (during CaptureBinding(WildcardBinding,ReferenceBinding,int,int,ASTNode,int)
* that has no firstBound / superclass / superInterfaces set.
*/
@Override
protected TypeBinding[] getDerivedTypesForDeferredInitialization() {
TypeBinding[] derived = this.environment.typeSystem.getDerivedTypes(this);
if (derived.length > 0) {
int count = 0;
for (int i = 0; i < derived.length; i++) {
if (derived[i] != null && derived[i].id == this.id)
derived[count++] = derived[i];
}
if (count < derived.length)
System.arraycopy(derived, 0, derived = new TypeBinding[count], 0, count);
}
return derived;
}
@Override
public String toString() {
if (this.wildcard != null) {
StringBuffer buffer = new StringBuffer(10);
AnnotationBinding [] annotations = getTypeAnnotations();
for (int i = 0, length = annotations == null ? 0 : annotations.length; i < length; i++) {
buffer.append(annotations[i]);
buffer.append(' ');
}
buffer
.append(TypeConstants.WILDCARD_CAPTURE_NAME_PREFIX)
.append(this.captureID)
.append(TypeConstants.WILDCARD_CAPTURE_NAME_SUFFIX)
.append(this.wildcard);
return buffer.toString();
}
return super.toString();
}
}