| /******************************************************************************* |
| * Copyright (c) 2000, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-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; |
| |
| /** |
| * 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; |
| public boolean isUnchecked; // indicates whether inferred arguments used unchecked conversion during bound check or was raw |
| |
| /** |
| * 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(); |
| |
| computeSubstitutes: { |
| if (substitutes != null) { |
| // explicit type arguments got supplied |
| 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()); |
| break computeSubstitutes; |
| } |
| |
| // perform type argument inference (15.12.2.7) |
| |
| // initializes the map of substitutes (var --> type[][]{ equal, extends, super} |
| TypeBinding[] parameters = originalMethod.parameters; |
| int varLength = typeVariables.length; |
| Map collectedSubstitutes = new HashMap(varLength); |
| for (int i = 0; i < varLength; i++) |
| collectedSubstitutes.put(typeVariables[i], new TypeBinding[3][]); |
| |
| substitutes = new TypeBinding[varLength]; |
| methodSubstitute = inferFromArgumentTypes(scope, originalMethod, arguments, parameters, collectedSubstitutes, substitutes); |
| if (methodSubstitute == null) |
| return null; |
| // substitutes may hold null to denote unresolved vars, but null arguments got replaced with respective original variable in param method |
| |
| // 15.12.2.8 - inferring unresolved type arguments |
| if (hasUnresolvedTypeArgument(substitutes)) { |
| TypeBinding expectedType = null; |
| // if message invocation has expected type |
| if (invocationSite instanceof MessageSend) { |
| MessageSend message = (MessageSend) invocationSite; |
| expectedType = message.expectedType; |
| } |
| TypeBinding upperBound = null; |
| if (methodSubstitute.returnType.isTypeVariable()) { |
| // should be: if no expected type, then assume Object |
| // actually it rather seems to handle the returned variable case by expecting its erasure instead |
| upperBound = methodSubstitute.returnType.erasure(); |
| } else { |
| if (methodSubstitute.returnType.id != TypeIds.T_void) |
| upperBound = scope.getJavaLangObject(); |
| } |
| // Object o = foo(); // where <T extends Serializable> T foo(); |
| if (expectedType == null || upperBound.isCompatibleWith(expectedType)) { |
| expectedType = upperBound; |
| } |
| methodSubstitute = methodSubstitute.inferFromExpectedType(scope, expectedType, collectedSubstitutes, substitutes); |
| if (methodSubstitute == null) |
| return null; |
| } |
| } |
| |
| // bounds check |
| if (!methodSubstitute.isRaw) { |
| for (int i = 0, length = typeVariables.length; i < length; i++) { |
| TypeVariableBinding typeVariable = typeVariables[i]; |
| TypeBinding substitute = methodSubstitute.typeArguments[i]; |
| switch (typeVariable.boundCheck(methodSubstitute, substitute)) { |
| case TypeConstants.MISMATCH : |
| // incompatible due to bound check |
| return new ProblemMethodBinding(methodSubstitute, originalMethod.selector, new TypeBinding[]{substitute, typeVariables[i] }, ParameterBoundMismatch); |
| case TypeConstants.UNCHECKED : |
| // tolerate unchecked bounds |
| methodSubstitute.isUnchecked = true; |
| break; |
| } |
| } |
| } |
| |
| return methodSubstitute; |
| } |
| |
| /** |
| * Returns true if any unresolved variable is detected, i.e. any variable is substituted with itself |
| */ |
| private static boolean hasUnresolvedTypeArgument(TypeBinding[] substitutes) { |
| for (int i = 0, varLength = substitutes.length; i <varLength; i++) { |
| if (substitutes[i] == null) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Collect argument type mapping, handling varargs |
| */ |
| private static ParameterizedGenericMethodBinding inferFromArgumentTypes(Scope scope, MethodBinding originalMethod, TypeBinding[] arguments, TypeBinding[] parameters, Map collectedSubstitutes, TypeBinding[] substitutes) { |
| |
| 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(scope, arguments[i], collectedSubstitutes, CONSTRAINT_EXTENDS); |
| } |
| // process optional arguments |
| if (minArgLength < argLength) { |
| TypeBinding varargType = parameters[minArgLength]; // last arg type - as is ? |
| TypeBinding lastArgument = arguments[minArgLength]; |
| if (paramLength != argLength // argument is passed as is ? |
| || (lastArgument != NullBinding |
| && (lastArgument.dimensions() == 0 || lastArgument.leafComponentType().isBaseType() != varargType.leafComponentType().isBaseType()))) { |
| varargType = ((ArrayBinding)varargType).elementsType(); // eliminate one array dimension |
| } |
| for (int i = minArgLength; i < argLength; i++) { |
| varargType.collectSubstitutes(scope, arguments[i], collectedSubstitutes, CONSTRAINT_EXTENDS); |
| } |
| } |
| } else { |
| int paramLength = parameters.length; |
| for (int i = 0; i < paramLength; i++) { |
| parameters[i].collectSubstitutes(scope, arguments[i], collectedSubstitutes, CONSTRAINT_EXTENDS); |
| } |
| } |
| TypeVariableBinding[] originalVariables = originalMethod.typeVariables; |
| int varLength = originalVariables.length; |
| substitutes = resolveSubstituteConstraints(scope, originalVariables , substitutes, false/*ignore Ti<:Uk*/, collectedSubstitutes); |
| if (substitutes == null) |
| return null; // incompatible |
| if (substitutes.length == 0) { |
| // raw generic method inferred |
| return new ParameterizedGenericMethodBinding(originalMethod, (RawTypeBinding)null, scope.environment()); |
| } |
| // apply inferred variable substitutions - replacing unresolved variable with original ones in param method |
| TypeBinding[] resolvedSubstitutes = substitutes; |
| for (int i = 0; i < varLength; i++) { |
| if (substitutes[i] == null) { |
| if (resolvedSubstitutes == substitutes) { |
| System.arraycopy(substitutes, 0, resolvedSubstitutes = new TypeBinding[varLength], 0, i); // clone to replace null with original variable in param method |
| } |
| resolvedSubstitutes[i] = originalVariables[i]; |
| } else if (resolvedSubstitutes != substitutes) { |
| resolvedSubstitutes[i] = substitutes[i]; |
| } |
| } |
| return new ParameterizedGenericMethodBinding(originalMethod, resolvedSubstitutes, scope.environment()); |
| } |
| |
| private static TypeBinding[] resolveSubstituteConstraints(Scope scope, TypeVariableBinding[] typeVariables, TypeBinding[] substitutes, boolean considerEXTENDSConstraints, Map collectedSubstitutes) { |
| if (collectedSubstitutes.isEmpty()) { |
| // raw generic method inferred |
| return NoTypes; // empty array |
| } |
| int varLength = typeVariables.length; |
| |
| // check Tj=U constraints |
| nextTypeParameter: |
| for (int i = 0; i < varLength; i++) { |
| TypeVariableBinding current = typeVariables[i]; |
| TypeBinding substitute = substitutes[i]; |
| if (substitute != null) continue nextTypeParameter; // already inferred previously |
| TypeBinding[][] variableSubstitutes = (TypeBinding[][]) collectedSubstitutes.get(current); |
| TypeBinding [] equalSubstitutes = variableSubstitutes[CONSTRAINT_EQUAL]; |
| if (equalSubstitutes != null) { |
| nextConstraint: |
| for (int j = 0, equalLength = equalSubstitutes.length; j < equalLength; j++) { |
| TypeBinding equalSubstitute = equalSubstitutes[j]; |
| if (equalSubstitute == null) continue nextConstraint; |
| // if (equalSubstitute == current) continue nextConstraint; |
| // if (equalSubstitute.isTypeVariable()) { |
| // TypeVariableBinding variable = (TypeVariableBinding) equalSubstitute; |
| // // substituted by a variable of the same method, ignore |
| // if (variable.rank < varLength && typeVariables[variable.rank] == variable) { |
| // // TODO (philippe) rewrite all other constraints to use current instead. |
| // continue nextConstraint; |
| // } |
| // } |
| substitutes[i] = equalSubstitute; |
| continue nextTypeParameter; // pick first match, applicability check will rule out invalid scenario where others were present |
| } |
| } |
| } |
| if (hasUnresolvedTypeArgument(substitutes)) { |
| // check Tj>:U constraints |
| nextTypeParameter: |
| for (int i = 0; i < varLength; i++) { |
| TypeVariableBinding current = typeVariables[i]; |
| TypeBinding substitute = substitutes[i]; |
| if (substitute != null) continue nextTypeParameter; // already inferred previously |
| TypeBinding[][] variableSubstitutes = (TypeBinding[][]) collectedSubstitutes.get(current); |
| TypeBinding [] bounds = variableSubstitutes[CONSTRAINT_SUPER]; |
| if (bounds == null) continue nextTypeParameter; |
| TypeBinding mostSpecificSubstitute = scope.lowerUpperBound(bounds); |
| if (mostSpecificSubstitute == null) |
| return null; // incompatible |
| if (mostSpecificSubstitute != VoidBinding) { |
| substitutes[i] = mostSpecificSubstitute; |
| } |
| } |
| } |
| if (considerEXTENDSConstraints && hasUnresolvedTypeArgument(substitutes)) { |
| // check Tj<:U constraints |
| nextTypeParameter: |
| for (int i = 0; i < varLength; i++) { |
| TypeVariableBinding current = typeVariables[i]; |
| TypeBinding substitute = substitutes[i]; |
| if (substitute != null) continue nextTypeParameter; // already inferred previously |
| TypeBinding[][] variableSubstitutes = (TypeBinding[][]) collectedSubstitutes.get(current); |
| TypeBinding [] bounds = variableSubstitutes[CONSTRAINT_EXTENDS]; |
| if (bounds == null) continue nextTypeParameter; |
| TypeBinding[] glb = Scope.greaterLowerBound(bounds); |
| TypeBinding mostSpecificSubstitute = null; |
| if (glb != null) mostSpecificSubstitute = glb[0]; // TODO (philippe) need to improve |
| //TypeBinding mostSpecificSubstitute = scope.greaterLowerBound(bounds); |
| if (mostSpecificSubstitute != null) { |
| substitutes[i] = mostSpecificSubstitute; |
| } |
| } |
| } |
| return substitutes; |
| } |
| |
| /** |
| * 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.isUnchecked = false; |
| 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 = Scope.substitute(this, ignoreRawTypeSubstitution |
| ? originalMethod.returnType // no substitution if original was static |
| : Scope.substitute(rawType, originalMethod.returnType)); |
| this.wasInferred = false; // not resulting from method invocation inferrence |
| } |
| |
| /** |
| * 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.isUnchecked = false; |
| this.originalMethod = originalMethod; |
| this.parameters = Scope.substitute(this, originalMethod.parameters); |
| this.thrownExceptions = Scope.substitute(this, originalMethod.thrownExceptions); |
| this.returnType = Scope.substitute(this, originalMethod.returnType); |
| this.wasInferred = true;// resulting from method invocation inferrence |
| } |
| |
| /* |
| * 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^123%<Lp/X;> |
| */ |
| public char[] computeUniqueKey(boolean withAccessFlags) { |
| if (this.isRaw) |
| return super.computeUniqueKey(withAccessFlags); |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append(super.computeUniqueKey(withAccessFlags)); |
| 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(false/*without access flags*/)); |
| } |
| buffer.append('>'); |
| int resultLength = buffer.length(); |
| char[] result = new char[resultLength]; |
| buffer.getChars(0, resultLength, result, 0); |
| return result; |
| |
| } |
| |
| /** |
| * @see org.eclipse.jdt.internal.compiler.lookup.Substitution#environment() |
| */ |
| public LookupEnvironment environment() { |
| return this.environment; |
| } |
| /** |
| * 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.inferredReturnType) |
| return this.originalMethod.hasSubstitutedReturnType(); |
| return super.hasSubstitutedReturnType(); |
| } |
| /** |
| * Given some type expectation, and type variable bounds, perform some inference. |
| * Returns true if still had unresolved type variable at the end of the operation |
| */ |
| private ParameterizedGenericMethodBinding inferFromExpectedType(Scope scope, TypeBinding expectedType, Map collectedSubstitutes, TypeBinding[] substitutes) { |
| TypeVariableBinding[] originalVariables = this.originalMethod.typeVariables; // immediate parent (could be a parameterized method) |
| int varLength = originalVariables.length; |
| |
| computeSubstitutes: { |
| // infer from expected return type |
| if (expectedType != null) { |
| returnType.collectSubstitutes(scope, expectedType, collectedSubstitutes, CONSTRAINT_SUPER); |
| } |
| // infer from bounds of type parameters |
| for (int i = 0; i < varLength; i++) { |
| TypeVariableBinding originalVariable = originalVariables[i]; |
| TypeBinding argument = this.typeArguments[i]; |
| if (originalVariable.firstBound == originalVariable.superclass) { |
| Scope.substitute(this, originalVariable.firstBound) // substitue original bound with resolved variables |
| .collectSubstitutes(scope, argument, collectedSubstitutes, CONSTRAINT_EXTENDS); |
| } |
| for (int j = 0, max = originalVariable.superInterfaces.length; j < max; j++) { |
| Scope.substitute(this, originalVariable.superInterfaces[j]) // substitue original bound with resolved variables |
| .collectSubstitutes(scope, argument, collectedSubstitutes, CONSTRAINT_EXTENDS); |
| } |
| } |
| substitutes = resolveSubstituteConstraints(scope, originalVariables, substitutes, true/*consider Ti<:Uk*/, collectedSubstitutes); |
| if (substitutes == null) |
| return null; // incompatible |
| if (substitutes.length == 0) { |
| // raw generic method inferred |
| this.isRaw = true; |
| this.isUnchecked = false; |
| for (int i = 0; i < varLength; i++) { |
| this.typeArguments[i] = originalVariables[i].erasure(); |
| } |
| break computeSubstitutes; |
| } |
| // this.typeArguments = substitutes; - no op since side effects got performed during #resolveSubstituteConstraints |
| for (int i = 0; i < varLength; i++) { |
| TypeBinding substitute = substitutes[i]; |
| if (substitute != null) { |
| this.typeArguments[i] = substitutes[i]; |
| } else { |
| // remaining unresolved variable are considered to be Object (or their bound actually) |
| this.typeArguments[i] = originalVariables[i].erasure(); |
| } |
| } |
| } |
| // adjust method types to reflect latest inference |
| TypeBinding oldReturnType = this.returnType; |
| this.returnType = Scope.substitute(this, this.returnType); |
| this.inferredReturnType = this.returnType != oldReturnType; |
| this.parameters = Scope.substitute(this, this.parameters); |
| this.thrownExceptions = Scope.substitute(this, this.thrownExceptions); |
| return this; |
| } |
| |
| /** |
| * @see org.eclipse.jdt.internal.compiler.lookup.Substitution#isRawSubstitution() |
| */ |
| public boolean isRawSubstitution() { |
| return this.isRaw; |
| } |
| |
| /** |
| * @see org.eclipse.jdt.internal.compiler.lookup.Substitution#substitute(org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding) |
| */ |
| public TypeBinding substitute(TypeVariableBinding originalVariable) { |
| 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.isStatic() && this.declaringClass instanceof Substitution) { |
| return ((Substitution)this.declaringClass).substitute(originalVariable); |
| } |
| return originalVariable; |
| } |
| /** |
| * 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; |
| } |
| } |