/******************************************************************************* | |
* Copyright (c) 2000, 2004 IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Common Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/cpl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.wst.jsdt.internal.compiler.lookup; | |
import java.util.HashMap; | |
import java.util.Map; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.MessageSend; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.Wildcard; | |
/** | |
* Binding denoting a generic method after type parameter substitutions got performed. | |
* On parameterized type bindings, all methods got substituted, regardless whether | |
* their signature did involve generics or not, so as to get the proper declaringClass for | |
* these methods. | |
*/ | |
public class ParameterizedGenericMethodBinding extends ParameterizedMethodBinding implements Substitution { | |
public TypeBinding[] typeArguments; | |
private LookupEnvironment environment; | |
public boolean inferredReturnType; | |
/** | |
* Create method of parameterized type, substituting original parameters with type arguments. | |
*/ | |
public ParameterizedGenericMethodBinding(MethodBinding originalMethod, TypeBinding[] typeArguments, LookupEnvironment environment) { | |
this.environment = environment; | |
this.modifiers = originalMethod.modifiers; | |
this.selector = originalMethod.selector; | |
this.declaringClass = originalMethod.declaringClass; | |
this.typeVariables = NoTypeVariables; | |
this.typeArguments = typeArguments; | |
this.originalMethod = originalMethod; | |
this.parameters = Scope.substitute(this, originalMethod.parameters); | |
this.thrownExceptions = Scope.substitute(this, originalMethod.thrownExceptions); | |
this.returnType = this.substitute(originalMethod.returnType); | |
} | |
/** | |
* Create raw generic method for raw type (double substitution from type vars with raw type arguments, and erasure of method variables) | |
*/ | |
public ParameterizedGenericMethodBinding(MethodBinding originalMethod, RawTypeBinding rawType, LookupEnvironment environment) { | |
TypeVariableBinding[] originalVariables = originalMethod.typeVariables; | |
int length = originalVariables.length; | |
TypeBinding[] rawArguments = new TypeBinding[length]; | |
for (int i = 0; i < length; i++) { | |
rawArguments[i] = originalVariables[i].erasure(); | |
} | |
this.environment = environment; | |
this.modifiers = originalMethod.modifiers; | |
this.selector = originalMethod.selector; | |
this.declaringClass = rawType; | |
this.typeVariables = NoTypeVariables; | |
this.typeArguments = rawArguments; | |
this.originalMethod = originalMethod; | |
boolean isStatic = originalMethod.isStatic(); | |
this.parameters = Scope.substitute(this, | |
isStatic ? originalMethod.parameters : Scope.substitute(rawType, originalMethod.parameters)); | |
this.thrownExceptions = Scope.substitute(this, | |
isStatic ? originalMethod.thrownExceptions : Scope.substitute(rawType, originalMethod.thrownExceptions)); | |
this.returnType = this.substitute(isStatic ? originalMethod.returnType : rawType.substitute(originalMethod.returnType)); | |
} | |
/** | |
* Perform inference of generic method type parameters and/or expected type | |
*/ | |
public static MethodBinding computeCompatibleMethod(MethodBinding originalMethod, TypeBinding[] arguments, Scope scope, InvocationSite invocationSite) { | |
ParameterizedGenericMethodBinding methodSubstitute; | |
TypeVariableBinding[] typeVariables = originalMethod.typeVariables; | |
TypeBinding[] substitutes = invocationSite.genericTypeArguments(); | |
if (substitutes != null) { | |
if (substitutes.length != typeVariables.length) { | |
// incompatible due to wrong arity | |
return new ProblemMethodBinding(originalMethod, originalMethod.selector, substitutes, TypeParameterArityMismatch); | |
} | |
methodSubstitute = new ParameterizedGenericMethodBinding(originalMethod, substitutes, scope.environment()); | |
} else { | |
// perform type inference based on argument types and expected type | |
// collect substitutes by pattern matching parameters and arguments | |
int argLength = arguments.length; | |
TypeBinding[] parameters = originalMethod.parameters; | |
int varLength = typeVariables.length; | |
HashMap collectedSubstitutes = new HashMap(varLength); | |
for (int i = 0; i < varLength; i++) | |
collectedSubstitutes.put(typeVariables[i], new TypeBinding[1]); | |
for (int i = 0; i < argLength; i++) | |
parameters[i].collectSubstitutes(arguments[i], collectedSubstitutes); | |
substitutes = new TypeBinding[varLength]; | |
boolean needReturnTypeInference = false; | |
for (int i = 0; i < varLength; i++) { | |
TypeBinding[] variableSubstitutes = (TypeBinding[]) collectedSubstitutes.get(typeVariables[i]); | |
TypeBinding mostSpecificSubstitute = scope.lowerUpperBound(variableSubstitutes); | |
//TypeBinding mostSpecificSubstitute = scope.mostSpecificCommonType(variableSubstitutes); | |
if (mostSpecificSubstitute == null) | |
return null; // incompatible | |
if (mostSpecificSubstitute == VoidBinding) { | |
needReturnTypeInference = true; | |
mostSpecificSubstitute = typeVariables[i]; | |
} | |
substitutes[i] = mostSpecificSubstitute; | |
} | |
// apply inferred variable substitutions | |
methodSubstitute = new ParameterizedGenericMethodBinding(originalMethod, substitutes, scope.environment()); | |
if (needReturnTypeInference && invocationSite instanceof MessageSend) { | |
MessageSend message = (MessageSend) invocationSite; | |
TypeBinding expectedType = message.expectedType; | |
if (expectedType != null) | |
methodSubstitute.inferFromExpectedType(message.expectedType, scope); | |
} | |
} | |
// check bounds | |
for (int i = 0, length = typeVariables.length; i < length; i++) { | |
TypeVariableBinding typeVariable = typeVariables[i]; | |
if (!typeVariable.boundCheck(methodSubstitute, substitutes[i])) | |
// incompatible due to bound check | |
return new ProblemMethodBinding(methodSubstitute, originalMethod.selector, new TypeBinding[]{substitutes[i], typeVariables[i] }, ParameterBoundMismatch); | |
} | |
return methodSubstitute; | |
} | |
public void inferFromExpectedType(TypeBinding expectedType, Scope scope) { | |
if (this.returnType == expectedType) | |
return; | |
if ((this.returnType.tagBits & TagBits.HasTypeVariable) == 0) | |
return; | |
Map substitutes = new HashMap(1); | |
int length = this.typeArguments.length; | |
TypeVariableBinding[] originalVariables = this.original().typeVariables; | |
boolean hasUnboundParameters = false; | |
for (int i = 0; i < length; i++) { | |
if (this.typeArguments[i] == originalVariables[i]) { | |
hasUnboundParameters = true; | |
substitutes.put(originalVariables[i], new TypeBinding[1]); | |
} else { | |
substitutes.put(originalVariables[i], new TypeBinding[] { this.typeArguments[i] }); | |
} | |
} | |
if (!hasUnboundParameters) | |
return; | |
returnType.collectSubstitutes(expectedType, substitutes); | |
for (int i = 0; i < length; i++) { | |
TypeBinding[] variableSubstitutes = (TypeBinding[]) substitutes.get(originalVariables[i]); | |
TypeBinding mostSpecificSubstitute = scope.lowerUpperBound(variableSubstitutes); | |
//TypeBinding mostSpecificSubstitute = scope.mostSpecificCommonType(variableSubstitutes); | |
if (mostSpecificSubstitute == null) { | |
return; // TODO (philippe) should report no way to infer type | |
} | |
if (mostSpecificSubstitute != VoidBinding) | |
this.typeArguments[i] = mostSpecificSubstitute; | |
} | |
TypeBinding oldReturnType = this.returnType; | |
this.returnType = this.substitute(this.returnType); | |
this.inferredReturnType = this.returnType != oldReturnType; | |
this.parameters = Scope.substitute(this, this.parameters); | |
this.thrownExceptions = Scope.substitute(this, this.thrownExceptions); | |
} | |
/** | |
* Returns a type, where original type was substituted using the receiver | |
* parameterized method. | |
*/ | |
public TypeBinding substitute(TypeBinding originalType) { | |
if ((originalType.tagBits & TagBits.HasTypeVariable) != 0) { | |
if (originalType.isTypeVariable()) { | |
TypeVariableBinding originalVariable = (TypeVariableBinding) originalType; | |
TypeVariableBinding[] variables = this.originalMethod.typeVariables; | |
int length = variables.length; | |
// check this variable can be substituted given parameterized type | |
if (originalVariable.rank < length && variables[originalVariable.rank] == originalVariable) { | |
return this.typeArguments[originalVariable.rank]; | |
} | |
} else if (originalType.isParameterizedType()) { | |
ParameterizedTypeBinding originalParameterizedType = (ParameterizedTypeBinding) originalType; | |
TypeBinding[] originalArguments = originalParameterizedType.arguments; | |
TypeBinding[] substitutedArguments = Scope.substitute(this, originalArguments); | |
if (substitutedArguments != originalArguments) { | |
identicalVariables: { // if substituted with original variables, then answer the generic type itself | |
TypeVariableBinding[] originalVariables = originalParameterizedType.type.typeVariables(); | |
for (int i = 0, length = originalVariables.length; i < length; i++) { | |
if (substitutedArguments[i] != originalVariables[i]) break identicalVariables; | |
} | |
return originalParameterizedType.type; | |
} | |
return this.environment.createParameterizedType( | |
originalParameterizedType.type, substitutedArguments, originalParameterizedType.enclosingType()); | |
} | |
} else if (originalType.isArrayType()) { | |
TypeBinding originalLeafComponentType = originalType.leafComponentType(); | |
TypeBinding substitute = substitute(originalLeafComponentType); // substitute could itself be array type | |
if (substitute != originalLeafComponentType) { | |
return this.environment.createArrayType(substitute.leafComponentType(), substitute.dimensions() + originalType.dimensions()); | |
} | |
} else if (originalType.isWildcard()) { | |
WildcardBinding wildcard = (WildcardBinding) originalType; | |
if (wildcard.kind != Wildcard.UNBOUND) { | |
TypeBinding originalBound = wildcard.bound; | |
TypeBinding substitutedBound = substitute(originalBound); | |
if (substitutedBound != originalBound) { | |
return this.environment.createWildcard(wildcard.genericType, wildcard.rank, substitutedBound, wildcard.kind); | |
} | |
} | |
} | |
} else if (originalType.isGenericType()) { | |
// treat as if parameterized with its type variables | |
ReferenceBinding originalGenericType = (ReferenceBinding) originalType; | |
TypeVariableBinding[] originalVariables = originalGenericType.typeVariables(); | |
int length = originalVariables.length; | |
TypeBinding[] originalArguments; | |
System.arraycopy(originalVariables, 0, originalArguments = new TypeBinding[length], 0, length); | |
TypeBinding[] substitutedArguments = Scope.substitute(this, originalArguments); | |
if (substitutedArguments != originalArguments) { | |
return this.environment.createParameterizedType( | |
originalGenericType, substitutedArguments, null); | |
} | |
} | |
return originalType; | |
} | |
} |