| /******************************************************************************* |
| * 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; |
| } |
| } |