--- /dev/null
+/*******************************************************************************
+ * 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.jdt.internal.compiler.lookup;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.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;
+ public boolean wasInferred; // only set to true for instances resulting from method invocation inferrence
+ public boolean isRaw; // set to true for method behaving as raw for substitution purpose
+ public MethodBinding tiebreakMethod;
+
+ /**
+ * 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.isRaw = false;
+ this.originalMethod = originalMethod;
+ this.parameters = Scope.substitute(this, originalMethod.parameters);
+ this.thrownExceptions = Scope.substitute(this, originalMethod.thrownExceptions);
+ this.returnType = this.substitute(originalMethod.returnType);
+ this.wasInferred = true;// resulting from method invocation inferrence
+ }
+
+ /**
+ * Create raw generic method for raw type (double substitution from type vars with raw type arguments, and erasure of method variables)
+ * Only invoked for non-static generic methods of raw type
+ */
+ 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.isRaw = true;
+ this.environment = environment;
+ this.modifiers = originalMethod.modifiers;
+ this.selector = originalMethod.selector;
+ this.declaringClass = rawType == null ? originalMethod.declaringClass : rawType;
+ this.typeVariables = NoTypeVariables;
+ this.typeArguments = rawArguments;
+ this.originalMethod = originalMethod;
+ boolean ignoreRawTypeSubstitution = rawType == null || originalMethod.isStatic();
+ this.parameters = Scope.substitute(this, ignoreRawTypeSubstitution
+ ? originalMethod.parameters // no substitution if original was static
+ : Scope.substitute(rawType, originalMethod.parameters));
+ this.thrownExceptions = Scope.substitute(this, ignoreRawTypeSubstitution
+ ? originalMethod.thrownExceptions // no substitution if original was static
+ : Scope.substitute(rawType, originalMethod.thrownExceptions));
+ this.returnType = this.substitute(ignoreRawTypeSubstitution
+ ? originalMethod.returnType // no substitution if original was static
+ : rawType.substitute(originalMethod.returnType));
+ this.wasInferred = false; // not resulting from method invocation inferrence
+ }
+
+ /**
+ * 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
+ 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]);
+
+ // collect argument type mapping, handling varargs
+ if (originalMethod.isVarargs()) {
+ int paramLength = parameters.length;
+ int minArgLength = paramLength - 1;
+ int argLength = arguments.length;
+ // process mandatory arguments
+ for (int i = 0; i < minArgLength; i++)
+ parameters[i].collectSubstitutes(arguments[i], collectedSubstitutes);
+ // process optional arguments
+ if (minArgLength < argLength) {
+ TypeBinding varargType = parameters[minArgLength]; // last arg type - as is ?
+ if (paramLength != argLength // argument is passed as is ?
+ || (arguments[minArgLength] != NullBinding
+ && (arguments[minArgLength].dimensions() != varargType.dimensions()))) {
+ varargType = ((ArrayBinding)varargType).elementsType(); // eliminate one array dimension
+ }
+ for (int i = minArgLength; i < argLength; i++)
+ varargType.collectSubstitutes(arguments[i], collectedSubstitutes);
+ }
+ } else {
+ int paramLength = parameters.length;
+ for (int i = 0; i < paramLength; i++)
+ parameters[i].collectSubstitutes(arguments[i], collectedSubstitutes);
+ }
+ boolean needReturnTypeInference = false;
+ if (collectedSubstitutes.isEmpty()) {
+ // raw generic method inferred
+ methodSubstitute = new ParameterizedGenericMethodBinding(originalMethod, (RawTypeBinding)null, scope.environment());
+ } else {
+ substitutes = new TypeBinding[varLength];
+ for (int i = 0; i < varLength; i++) {
+ TypeBinding[] variableSubstitutes = (TypeBinding[]) collectedSubstitutes.get(typeVariables[i]);
+ TypeBinding mostSpecificSubstitute = scope.lowerUpperBound(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) {
+ // 15.12.2.8 - if no expected type, then assume Object
+ // actually it rather seems to handle the returned variable case by expecting its erasure instead
+ if (methodSubstitute.returnType.isTypeVariable()) {
+ expectedType = methodSubstitute.returnType.erasure();
+ } else {
+ expectedType =scope.getJavaLangObject();
+ }
+ }
+ methodSubstitute.inferFromExpectedType(expectedType, scope);
+ }
+ }
+ // check bounds
+ if (!methodSubstitute.isRaw) {
+ for (int i = 0, length = typeVariables.length; i < length; i++) {
+ TypeVariableBinding typeVariable = typeVariables[i];
+ TypeBinding substitute = substitutes[i];
+ if (!typeVariable.boundCheck(methodSubstitute, substitute))
+ // incompatible due to bound check
+ return new ProblemMethodBinding(methodSubstitute, originalMethod.selector, new TypeBinding[]{substitutes[i], typeVariables[i] }, ParameterBoundMismatch);
+ }
+ }
+
+ return methodSubstitute;
+ }
+
+ /*
+ * parameterizedDeclaringUniqueKey dot selector originalMethodGenericSignature percent typeArguments
+ * p.X<U> { <T> void bar(T t, U u) { new X<String>().bar(this, "") } } --> Lp/X<Ljava/lang/String;>;.bar<T:Ljava/lang/Object;>(TT;TU;)V%<Lp/X;>
+ */
+ public char[] computeUniqueKey() {
+ if (this.isRaw)
+ return super.computeUniqueKey();
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(super.computeUniqueKey());
+ buffer.append('%');
+ buffer.append('<');
+ int length = this.typeArguments.length;
+ for (int i = 0; i < length; i++) {
+ TypeBinding typeArgument = this.typeArguments[i];
+ buffer.append(typeArgument.computeUniqueKey());
+ }
+ buffer.append('>');
+ int resultLength = buffer.length();
+ char[] result = new char[resultLength];
+ buffer.getChars(0, resultLength, result, 0);
+ return result;
+
+ }
+
+ /**
+ * Returns true if some parameters got substituted.
+ * NOTE: generic method invocation delegates to its declaring method (could be a parameterized one)
+ */
+ public boolean hasSubstitutedParameters() {
+ // generic parameterized method can represent either an invocation or a raw generic method
+ if (this.wasInferred)
+ return this.originalMethod.hasSubstitutedParameters();
+ return super.hasSubstitutedParameters();
+ }
+ /**
+ * Returns true if the return type got substituted.
+ * NOTE: generic method invocation delegates to its declaring method (could be a parameterized one)
+ */
+ public boolean hasSubstitutedReturnType() {
+ if (this.wasInferred)
+ return this.originalMethod.hasSubstitutedReturnType();
+ return super.hasSubstitutedReturnType();
+ }
+
+ 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);
+ if (substitutes.isEmpty()) {
+ // raw generic method inferred
+ this.isRaw = true;
+ for (int i = 0; i < length; i++) {
+ this.typeArguments[i] = originalVariables[i].erasure();
+ }
+ } else {
+ for (int i = 0; i < length; i++) {
+ TypeBinding[] variableSubstitutes = (TypeBinding[]) substitutes.get(originalVariables[i]);
+ TypeBinding mostSpecificSubstitute = scope.lowerUpperBound(variableSubstitutes);
+ if (mostSpecificSubstitute == null) {
+ return; // TODO (philippe) should report no way to infer type
+ }
+ if (mostSpecificSubstitute == VoidBinding) {
+ // 15.12.2.8 - any remaining variable is assumed to be its erasure
+ mostSpecificSubstitute = originalVariables[i].erasure();
+ }
+ 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) {
+
+ switch (originalType.kind()) {
+
+ case Binding.TYPE_PARAMETER:
+ 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];
+ }
+ if (this.declaringClass instanceof Substitution) {
+ return ((Substitution)this.declaringClass).substitute(originalType);
+ }
+ break;
+
+ case Binding.PARAMETERIZED_TYPE:
+ ParameterizedTypeBinding originalParameterizedType = (ParameterizedTypeBinding) originalType;
+ ReferenceBinding originalEnclosing = originalType.enclosingType();
+ ReferenceBinding substitutedEnclosing = originalEnclosing;
+ if (originalEnclosing != null) {
+ substitutedEnclosing = (ReferenceBinding) this.substitute(originalEnclosing);
+ }
+ if (this.isRaw) {
+ return this.environment.createRawType(originalParameterizedType.type, substitutedEnclosing);
+ }
+ TypeBinding[] originalArguments = originalParameterizedType.arguments;
+ TypeBinding[] substitutedArguments = originalArguments;
+ if (originalArguments != null) {
+ substitutedArguments = Scope.substitute(this, originalArguments);
+ }
+ if (substitutedArguments != originalArguments || substitutedEnclosing != originalEnclosing) {
+ identicalVariables: { // if substituted with original variables, then answer the generic type itself
+ if (substitutedEnclosing != originalEnclosing) break identicalVariables;
+ TypeVariableBinding[] originalVariables = originalParameterizedType.type.typeVariables();
+ length = originalVariables.length;
+ for (int i = 0; i < length; i++) {
+ if (substitutedArguments[i] != originalVariables[i]) break identicalVariables;
+ }
+ return originalParameterizedType.type;
+ }
+ return this.environment.createParameterizedType(
+ originalParameterizedType.type, substitutedArguments, substitutedEnclosing);
+ }
+ break;
+
+ case Binding.ARRAY_TYPE:
+ 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());
+ }
+ break;
+
+ case Binding.WILDCARD_TYPE:
+ 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);
+ }
+ }
+ break;
+
+
+ case Binding.GENERIC_TYPE:
+ // treat as if parameterized with its type variables
+ ReferenceBinding originalGenericType = (ReferenceBinding) originalType;
+ originalEnclosing = originalType.enclosingType();
+ substitutedEnclosing = originalEnclosing;
+ if (originalEnclosing != null) {
+ substitutedEnclosing = (ReferenceBinding) this.substitute(originalEnclosing);
+ }
+ if (this.isRaw) {
+ return this.environment.createRawType(originalGenericType, substitutedEnclosing);
+ }
+ TypeVariableBinding[] originalVariables = originalGenericType.typeVariables();
+ length = originalVariables.length;
+ System.arraycopy(originalVariables, 0, originalArguments = new TypeBinding[length], 0, length);
+ substitutedArguments = Scope.substitute(this, originalArguments);
+ if (substitutedArguments != originalArguments || substitutedEnclosing != originalEnclosing) {
+ return this.environment.createParameterizedType(
+ originalGenericType, substitutedArguments, substitutedEnclosing);
+ }
+ break;
+ }
+ return originalType;
+ }
+ /**
+ * Returns the method to use during tiebreak (usually the method itself).
+ * For generic method invocations, tiebreak needs to use generic method with erasure substitutes.
+ */
+ public MethodBinding tiebreakMethod() {
+ if (this.tiebreakMethod == null) {
+ this.tiebreakMethod = new ParameterizedGenericMethodBinding(this.originalMethod, (RawTypeBinding)null, this.environment);
+ }
+ return this.tiebreakMethod;
+ }
+}