blob: 5971114719462aeedd7f2e1eab05aaff382b7fb8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2019 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
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.ITeamAnchor;
/**
* Capture-like type variable introduced during 1.8 type inference.
*/
public class CaptureBinding18 extends CaptureBinding {
TypeBinding[] upperBounds;
private char[] originalName;
private CaptureBinding18 prototype;
public CaptureBinding18(ReferenceBinding contextType, char[] sourceName, char[] originalName, int start, int end, int captureID, LookupEnvironment environment) {
super(contextType, sourceName, start, end, captureID, environment);
this.originalName = originalName;
this.prototype = this;
}
private CaptureBinding18(CaptureBinding18 prototype) {
super(prototype);
this.sourceName = CharOperation.append(prototype.sourceName, '\'');
this.originalName = prototype.originalName;
this.upperBounds = prototype.upperBounds;
this.prototype = prototype.prototype;
}
public boolean setUpperBounds(TypeBinding[] upperBounds, ReferenceBinding javaLangObject) {
this.upperBounds = upperBounds;
if (upperBounds.length > 0)
this.firstBound = upperBounds[0];
int numReferenceInterfaces = 0;
if (!isConsistentIntersection(upperBounds))
return false;
for (int i = 0; i < upperBounds.length; i++) {
TypeBinding aBound = upperBounds[i];
if (aBound instanceof ReferenceBinding) {
if (this.superclass == null && aBound.isClass())
this.superclass = (ReferenceBinding) aBound;
else if (aBound.isInterface())
numReferenceInterfaces++;
} else if (TypeBinding.equalsEquals(aBound.leafComponentType(), this)) {
return false; // cycle detected
}
}
this.superInterfaces = new ReferenceBinding[numReferenceInterfaces];
int idx = 0;
for (int i = 0; i < upperBounds.length; i++) {
TypeBinding aBound = upperBounds[i];
if (aBound.isInterface())
this.superInterfaces[idx++] = (ReferenceBinding) aBound;
}
if (this.superclass == null)
this.superclass = javaLangObject;
return true;
}
@Override
public void initializeBounds(Scope scope, ParameterizedTypeBinding capturedParameterizedType) {
// nothing to initialize here (and cannot use super methods which requires wildcard to be set).
}
@Override
public TypeBinding clone(TypeBinding enclosingType) {
return new CaptureBinding18(this);
}
@Override
public MethodBinding[] getMethods(char[] selector) {
if (this.upperBounds.length == 1 && this.upperBounds[0] instanceof ReferenceBinding)
return ((ReferenceBinding)this.upperBounds[0]).getMethods(selector);
return super.getMethods(selector);
}
@Override
public TypeBinding erasure() {
if (this.upperBounds != null && this.upperBounds.length > 1) {
ReferenceBinding[] erasures = new ReferenceBinding[this.upperBounds.length];
boolean multipleErasures = false;
for (int i = 0; i < this.upperBounds.length; i++) {
erasures[i] = (ReferenceBinding) this.upperBounds[i].erasure(); // FIXME cast?
if (i > 0) {
if (TypeBinding.notEquals(erasures[0], erasures[i]))
multipleErasures = true;
}
}
if (!multipleErasures)
return erasures[0];
return this.environment.createIntersectionType18(erasures);
}
if (this.superclass == null)
return this.environment.getType(TypeConstants.JAVA_LANG_OBJECT);
return super.erasure();
}
/**
* @see TypeBinding#isEquivalentTo(TypeBinding)
*/
@Override
public boolean isEquivalentTo(TypeBinding otherType) {
// from CaptureBinding:
if (equalsEquals(this, otherType)) return true;
if (otherType == null) return false;
if (this.upperBounds != null) {
// from CaptureBinding:
for (int i = 0; i < this.upperBounds.length; i++) {
TypeBinding aBound = this.upperBounds[i];
// capture of ? extends X[]
if (aBound != null && aBound.isArrayType()) {
if (!aBound.isCompatibleWith(otherType))
return false;
} else switch (otherType.kind()) {
case Binding.WILDCARD_TYPE :
case Binding.INTERSECTION_TYPE :
if (!((WildcardBinding) otherType).boundCheck(aBound))
return false;
}
}
return true;
}
return false;
}
@Override
public boolean isCompatibleWith(TypeBinding otherType, Scope captureScope) {
if (TypeBinding.equalsEquals(this, otherType))
return true;
if (this.inRecursiveFunction)
return true;
this.inRecursiveFunction = true;
try {
if (this.upperBounds != null) {
int length = this.upperBounds.length;
// need to compare two intersection types? (borrowed from IntersectionType18)
int rightKind = otherType.kind();
TypeBinding[] rightIntersectingTypes = null;
if (rightKind == INTERSECTION_TYPE && otherType.boundKind() == Wildcard.EXTENDS) {
TypeBinding allRightBounds = ((WildcardBinding) otherType).allBounds();
if (allRightBounds instanceof IntersectionTypeBinding18)
rightIntersectingTypes = ((IntersectionTypeBinding18) allRightBounds).intersectingTypes;
} else if (rightKind == INTERSECTION_TYPE18) {
rightIntersectingTypes = ((IntersectionTypeBinding18) otherType).intersectingTypes;
}
if (rightIntersectingTypes != null) {
nextRequired:
for (TypeBinding required : rightIntersectingTypes) {
for (TypeBinding provided : this.upperBounds) {
if (provided.isCompatibleWith(required, captureScope))
continue nextRequired;
}
return false;
}
return true;
}
for (int i = 0; i < length; i++) {
if (this.upperBounds[i].isCompatibleWith(otherType, captureScope))
return true;
}
}
return false;
} finally {
this.inRecursiveFunction = false;
}
}
@Override
public TypeBinding findSuperTypeOriginatingFrom(TypeBinding otherType) {
if (this.upperBounds != null && this.upperBounds.length > 1) {
for (int i = 0; i < this.upperBounds.length; i++) {
TypeBinding candidate = this.upperBounds[i].findSuperTypeOriginatingFrom(otherType);
if (candidate != null)
return candidate;
// TODO: maybe we should double check about multiple candidates here,
// but upper bounds should be consistent so hopefully the first non-null candidate is good enough.
}
}
return super.findSuperTypeOriginatingFrom(otherType);
}
//{ObjectTeams: cross the OT package, make protected:
@Override
protected
// SH}
TypeBinding substituteInferenceVariable(InferenceVariable var, TypeBinding substituteType) {
if (this.inRecursiveFunction) return this;
this.inRecursiveFunction = true;
try {
boolean haveSubstitution = false;
ReferenceBinding currentSuperclass = this.superclass;
if (currentSuperclass != null) {
currentSuperclass = (ReferenceBinding) currentSuperclass.substituteInferenceVariable(var, substituteType);
haveSubstitution |= TypeBinding.notEquals(currentSuperclass, this.superclass);
}
ReferenceBinding[] currentSuperInterfaces = null;
if (this.superInterfaces != null) {
int length = this.superInterfaces.length;
if (haveSubstitution)
System.arraycopy(this.superInterfaces, 0, currentSuperInterfaces=new ReferenceBinding[length], 0, length);
for (int i = 0; i < length; i++) {
ReferenceBinding currentSuperInterface = this.superInterfaces[i];
if (currentSuperInterface != null) {
currentSuperInterface = (ReferenceBinding) currentSuperInterface.substituteInferenceVariable(var, substituteType);
if (TypeBinding.notEquals(currentSuperInterface, this.superInterfaces[i])) {
if (currentSuperInterfaces == null)
System.arraycopy(this.superInterfaces, 0, currentSuperInterfaces=new ReferenceBinding[length], 0, length);
currentSuperInterfaces[i] = currentSuperInterface;
haveSubstitution = true;
}
}
}
}
TypeBinding[] currentUpperBounds = null;
if (this.upperBounds != null) {
int length = this.upperBounds.length;
if (haveSubstitution)
System.arraycopy(this.upperBounds, 0, currentUpperBounds=new TypeBinding[length], 0, length);
for (int i = 0; i < length; i++) {
TypeBinding currentBound = this.upperBounds[i];
if (currentBound != null) {
currentBound = currentBound.substituteInferenceVariable(var, substituteType);
if (TypeBinding.notEquals(currentBound, this.upperBounds[i])) {
if (currentUpperBounds == null)
System.arraycopy(this.upperBounds, 0, currentUpperBounds=new TypeBinding[length], 0, length);
currentUpperBounds[i] = currentBound;
haveSubstitution = true;
}
}
}
}
TypeBinding currentFirstBound = null;
if (this.firstBound != null) {
currentFirstBound = this.firstBound.substituteInferenceVariable(var, substituteType);
haveSubstitution |= TypeBinding.notEquals(this.firstBound, currentFirstBound);
}
if (haveSubstitution) {
final CaptureBinding18 newCapture = (CaptureBinding18) clone(enclosingType());
newCapture.tagBits = this.tagBits;
Substitution substitution = new Substitution() {
@Override
public TypeBinding substitute(TypeVariableBinding typeVariable) {
return (typeVariable == CaptureBinding18.this) ? newCapture : typeVariable; //$IDENTITY-COMPARISON$
}
@Override
public boolean isRawSubstitution() {
return false;
}
@Override
public LookupEnvironment environment() {
return CaptureBinding18.this.environment;
}
//{ObjectTeams:
@Override
public ITeamAnchor substituteAnchor(ITeamAnchor anchor, int rank2) {
return anchor;
}
// SH}
};
if (currentFirstBound != null)
newCapture.firstBound = Scope.substitute(substitution, currentFirstBound);
newCapture.superclass = (ReferenceBinding) Scope.substitute(substitution, currentSuperclass);
newCapture.superInterfaces = Scope.substitute(substitution, currentSuperInterfaces);
newCapture.upperBounds = Scope.substitute(substitution, currentUpperBounds);
return newCapture;
}
return this;
} finally {
this.inRecursiveFunction = false;
}
}
@Override
public boolean isProperType(boolean admitCapture18) {
if (!admitCapture18)
return false;
if (this.inRecursiveFunction)
return true;
this.inRecursiveFunction = true;
try {
if (this.lowerBound != null && !this.lowerBound.isProperType(admitCapture18))
return false;
if (this.upperBounds != null) {
for (int i = 0; i < this.upperBounds.length; i++) {
if (!this.upperBounds[i].isProperType(admitCapture18))
return false;
}
}
} finally {
this.inRecursiveFunction = false;
}
return true;
}
int recursionLevel = 0; // used to give a hint at recursive types without going into infinity
@Override
public char[] readableName() {
if (this.lowerBound == null && this.firstBound != null) {
if (this.prototype.recursionLevel < 2) {
try {
this.prototype.recursionLevel ++;
if (this.upperBounds != null && this.upperBounds.length > 1) {
StringBuffer sb = new StringBuffer();
sb.append(this.upperBounds[0].readableName());
for (int i = 1; i < this.upperBounds.length; i++)
sb.append('&').append(this.upperBounds[i].readableName());
int len = sb.length();
char[] name = new char[len];
sb.getChars(0, len, name, 0);
return name;
}
return this.firstBound.readableName();
} finally {
this.prototype.recursionLevel--;
}
} else {
return this.originalName;
}
}
return super.readableName();
}
@Override
public char[] shortReadableName() {
if (this.lowerBound == null && this.firstBound != null) {
if (this.prototype.recursionLevel < 2) {
try {
this.prototype.recursionLevel++;
if (this.upperBounds != null && this.upperBounds.length > 1) {
StringBuffer sb = new StringBuffer();
sb.append(this.upperBounds[0].shortReadableName());
for (int i = 1; i < this.upperBounds.length; i++)
sb.append('&').append(this.upperBounds[i].shortReadableName());
int len = sb.length();
char[] name = new char[len];
sb.getChars(0, len, name, 0);
return name;
}
return this.firstBound.shortReadableName();
} finally {
this.prototype.recursionLevel--;
}
} else {
return this.originalName;
}
}
return super.shortReadableName();
}
@Override
public TypeBinding uncapture(Scope scope) {
return this;
}
@Override
public char[] computeUniqueKey(boolean isLeaf) {
StringBuffer buffer = new StringBuffer();
buffer.append(TypeConstants.CAPTURE18);
buffer.append('{').append(this.end).append('#').append(this.captureID).append('}');
buffer.append(';');
int length = buffer.length();
char[] uniqueKey = new char[length];
buffer.getChars(0, length, uniqueKey, 0);
return uniqueKey;
}
}