/******************************************************************************* * Copyright (c) 2000, 2009 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 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 private MethodBinding tiebreakMethod; /** * 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(); TypeBinding[] uncheckedArguments= null; 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, ProblemReasons.TypeParameterArityMismatch); } methodSubstitute= scope.environment().createParameterizedGenericMethod(originalMethod, substitutes); break computeSubstitutes; } // perform type argument inference (15.12.2.7) // initializes the map of substitutes (var --> type[][]{ equal, extends, super} TypeBinding[] parameters= originalMethod.parameters; InferenceContext inferenceContext= new InferenceContext(originalMethod); methodSubstitute= inferFromArgumentTypes(scope, originalMethod, arguments, parameters, inferenceContext); 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 (inferenceContext.hasUnresolvedTypeArgument()) { if (inferenceContext.isUnchecked) { // only remember unchecked status post 15.12.2.7 int length= inferenceContext.substitutes.length; System.arraycopy(inferenceContext.substitutes, 0, uncheckedArguments= new TypeBinding[length], 0, length); } if (methodSubstitute.returnType != TypeBinding.VOID) { TypeBinding expectedType= null; // if message invocation has expected type if (invocationSite instanceof MessageSend) { MessageSend message= (MessageSend)invocationSite; expectedType= message.expectedType; } if (expectedType != null) { // record it was explicit from context, as opposed to assumed by default (see below) inferenceContext.hasExplicitExpectedType= true; } else { expectedType= scope.getJavaLangObject(); // assume Object by default } inferenceContext.expectedType= expectedType; } methodSubstitute= methodSubstitute.inferFromExpectedType(scope, inferenceContext); if (methodSubstitute == null) return null; } } // bounds check for (int i= 0, length= typeVariables.length; i < length; i++) { TypeVariableBinding typeVariable= typeVariables[i]; TypeBinding substitute= methodSubstitute.typeArguments[i]; if (uncheckedArguments != null && uncheckedArguments[i] == null) continue; // only bound check if inferred through 15.12.2.6 switch (typeVariable.boundCheck(methodSubstitute, substitute)) { case TypeConstants.MISMATCH: // incompatible due to bound check int argLength= arguments.length; TypeBinding[] augmentedArguments= new TypeBinding[argLength + 2]; // append offending substitute and typeVariable System.arraycopy(arguments, 0, augmentedArguments, 0, argLength); augmentedArguments[argLength]= substitute; augmentedArguments[argLength + 1]= typeVariable; return new ProblemMethodBinding(methodSubstitute, originalMethod.selector, augmentedArguments, ProblemReasons.ParameterBoundMismatch); case TypeConstants.UNCHECKED: // tolerate unchecked bounds methodSubstitute.tagBits|= TagBits.HasUncheckedTypeArgumentForBoundCheck; break; } } // check presence of unchecked argument conversion a posteriori (15.12.2.6) return methodSubstitute; } /** * Collect argument type mapping, handling varargs */ private static ParameterizedGenericMethodBinding inferFromArgumentTypes(Scope scope, MethodBinding originalMethod, TypeBinding[] arguments, TypeBinding[] parameters, InferenceContext inferenceContext) { 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], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS); if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution } // process optional arguments if (minArgLength < argLength) { TypeBinding varargType= parameters[minArgLength]; // last arg type - as is ? TypeBinding lastArgument= arguments[minArgLength]; checkVarargDimension: { if (paramLength == argLength) { if (lastArgument == TypeBinding.NULL) break checkVarargDimension; switch (lastArgument.dimensions()) { case 0: break; // will remove one dim case 1: if (!lastArgument.leafComponentType().isBaseType()) break checkVarargDimension; break; // will remove one dim default: break checkVarargDimension; } } // eliminate one array dimension varargType= ((ArrayBinding)varargType).elementsType(); } for (int i= minArgLength; i < argLength; i++) { varargType.collectSubstitutes(scope, arguments[i], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS); if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution } } } else { int paramLength= parameters.length; for (int i= 0; i < paramLength; i++) { parameters[i].collectSubstitutes(scope, arguments[i], inferenceContext, TypeConstants.CONSTRAINT_EXTENDS); if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution } } TypeVariableBinding[] originalVariables= originalMethod.typeVariables; if (!resolveSubstituteConstraints(scope, originalVariables, inferenceContext, false/*ignore Ti<:Uk*/)) return null; // impossible substitution // apply inferred variable substitutions - replacing unresolved variable with original ones in param method TypeBinding[] inferredSustitutes= inferenceContext.substitutes; TypeBinding[] actualSubstitutes= inferredSustitutes; for (int i= 0, varLength= originalVariables.length; i < varLength; i++) { if (inferredSustitutes[i] == null) { if (actualSubstitutes == inferredSustitutes) { System.arraycopy(inferredSustitutes, 0, actualSubstitutes= new TypeBinding[varLength], 0, i); // clone to replace null with original variable in param method } actualSubstitutes[i]= originalVariables[i]; } else if (actualSubstitutes != inferredSustitutes) { actualSubstitutes[i]= inferredSustitutes[i]; } } ParameterizedGenericMethodBinding paramMethod= scope.environment().createParameterizedGenericMethod(originalMethod, actualSubstitutes); return paramMethod; } private static boolean resolveSubstituteConstraints(Scope scope, TypeVariableBinding[] typeVariables, InferenceContext inferenceContext, boolean considerEXTENDSConstraints) { TypeBinding[] substitutes= inferenceContext.substitutes; 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[] equalSubstitutes= inferenceContext.getSubstitutes(current, TypeConstants.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) { // try to find a better different match if any in subsequent equal candidates for (int k= j + 1; k < equalLength; k++) { equalSubstitute= equalSubstitutes[k]; if (equalSubstitute != current && equalSubstitute != null) { substitutes[i]= equalSubstitute; continue nextTypeParameter; } } substitutes[i]= current; continue nextTypeParameter; } // 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 (inferenceContext.hasUnresolvedTypeArgument()) { // 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[] bounds= inferenceContext.getSubstitutes(current, TypeConstants.CONSTRAINT_SUPER); if (bounds == null) continue nextTypeParameter; TypeBinding mostSpecificSubstitute= scope.lowerUpperBound(bounds); if (mostSpecificSubstitute == null) { return false; // incompatible } if (mostSpecificSubstitute != TypeBinding.VOID) { substitutes[i]= mostSpecificSubstitute; } } } if (considerEXTENDSConstraints && inferenceContext.hasUnresolvedTypeArgument()) { // 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[] bounds= inferenceContext.getSubstitutes(current, TypeConstants.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 true; } /** * 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]= environment.convertToRawType(originalVariables[i].erasure(), false /*do not force conversion of enclosing types*/); } this.isRaw= true; this.tagBits= originalMethod.tagBits; this.environment= environment; this.modifiers= originalMethod.modifiers; this.selector= originalMethod.selector; this.declaringClass= rawType == null ? originalMethod.declaringClass : rawType; this.typeVariables= Binding.NO_TYPE_VARIABLES; 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)); // error case where exception type variable would have been substituted by a non-reference type (207573) if (this.thrownExceptions == null) this.thrownExceptions= Binding.NO_EXCEPTIONS; 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= Binding.NO_TYPE_VARIABLES; this.typeArguments= typeArguments; this.isRaw= false; this.tagBits= originalMethod.tagBits; this.originalMethod= originalMethod; this.parameters= Scope.substitute(this, originalMethod.parameters); // error case where exception type variable would have been substituted by a non-reference type (207573) this.returnType= Scope.substitute(this, originalMethod.returnType); this.thrownExceptions= Scope.substitute(this, originalMethod.thrownExceptions); if (this.thrownExceptions == null) this.thrownExceptions= Binding.NO_EXCEPTIONS; checkMissingType: { if ((this.tagBits & TagBits.HasMissingType) != 0) break checkMissingType; if ((this.returnType.tagBits & TagBits.HasMissingType) != 0) { this.tagBits|= TagBits.HasMissingType; break checkMissingType; } for (int i= 0, max= this.parameters.length; i < max; i++) { if ((this.parameters[i].tagBits & TagBits.HasMissingType) != 0) { this.tagBits|= TagBits.HasMissingType; break checkMissingType; } } for (int i= 0, max= this.thrownExceptions.length; i < max; i++) { if ((this.thrownExceptions[i].tagBits & TagBits.HasMissingType) != 0) { this.tagBits|= TagBits.HasMissingType; break checkMissingType; } } } 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;Ljava/lang/String;)V%<Lp/X;> */ public char[] computeUniqueKey(boolean isLeaf) { StringBuffer buffer= new StringBuffer(); buffer.append(this.originalMethod.computeUniqueKey(false/*not a leaf*/)); buffer.append('%'); buffer.append('<'); if (!this.isRaw) { int length= this.typeArguments.length; for (int i= 0; i < length; i++) { TypeBinding typeArgument= this.typeArguments[i]; buffer.append(typeArgument.computeUniqueKey(false/*not a leaf*/)); } } 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, InferenceContext inferenceContext) { TypeVariableBinding[] originalVariables= this.originalMethod.typeVariables; // immediate parent (could be a parameterized method) int varLength= originalVariables.length; // infer from expected return type if (inferenceContext.expectedType != null) { this.returnType.collectSubstitutes(scope, inferenceContext.expectedType, inferenceContext, TypeConstants.CONSTRAINT_SUPER); if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution } // infer from bounds of type parameters for (int i= 0; i < varLength; i++) { TypeVariableBinding originalVariable= originalVariables[i]; TypeBinding argument= this.typeArguments[i]; boolean argAlreadyInferred= argument != originalVariable; if (originalVariable.firstBound == originalVariable.superclass) { TypeBinding substitutedBound= Scope.substitute(this, originalVariable.superclass); argument.collectSubstitutes(scope, substitutedBound, inferenceContext, TypeConstants.CONSTRAINT_SUPER); if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution // JLS 15.12.2.8 claims reverse inference shouldn't occur, however it improves inference // e.g. given: <E extends Object, S extends Collection<E>> S test1(S param) // invocation: test1(new Vector<String>()) will infer: S=Vector<String> and with code below: E=String if (argAlreadyInferred) { substitutedBound.collectSubstitutes(scope, argument, inferenceContext, TypeConstants.CONSTRAINT_EXTENDS); if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution } } for (int j= 0, max= originalVariable.superInterfaces.length; j < max; j++) { TypeBinding substitutedBound= Scope.substitute(this, originalVariable.superInterfaces[j]); argument.collectSubstitutes(scope, substitutedBound, inferenceContext, TypeConstants.CONSTRAINT_SUPER); if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution // JLS 15.12.2.8 claims reverse inference shouldn't occur, however it improves inference if (argAlreadyInferred) { substitutedBound.collectSubstitutes(scope, argument, inferenceContext, TypeConstants.CONSTRAINT_EXTENDS); if (inferenceContext.status == InferenceContext.FAILED) return null; // impossible substitution } } } if (!resolveSubstituteConstraints(scope, originalVariables, inferenceContext, true/*consider Ti<:Uk*/)) return null; // incompatible // this.typeArguments = substitutes; - no op since side effects got performed during #resolveSubstituteConstraints for (int i= 0; i < varLength; i++) { TypeBinding substitute= inferenceContext.substitutes[i]; if (substitute != null) { this.typeArguments[i]= inferenceContext.substitutes[i]; } else { // remaining unresolved variable are considered to be Object (or their bound actually) this.typeArguments[i]= originalVariables[i].upperBound(); } } // may still need an extra substitution at the end (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=121369) // to properly substitute a remaining unresolved variable which also appear in a formal bound this.typeArguments= Scope.substitute(this, this.typeArguments); // adjust method types to reflect latest inference TypeBinding oldReturnType= this.returnType; this.returnType= Scope.substitute(this, this.returnType); this.inferredReturnType= inferenceContext.hasExplicitExpectedType && this.returnType != oldReturnType; this.parameters= Scope.substitute(this, this.parameters); this.thrownExceptions= Scope.substitute(this, this.thrownExceptions); // error case where exception type variable would have been substituted by a non-reference type (207573) if (this.thrownExceptions == null) this.thrownExceptions= Binding.NO_EXCEPTIONS; checkMissingType: { if ((this.tagBits & TagBits.HasMissingType) != 0) break checkMissingType; if ((this.returnType.tagBits & TagBits.HasMissingType) != 0) { this.tagBits|= TagBits.HasMissingType; break checkMissingType; } for (int i= 0, max= this.parameters.length; i < max; i++) { if ((this.parameters[i].tagBits & TagBits.HasMissingType) != 0) { this.tagBits|= TagBits.HasMissingType; break checkMissingType; } } for (int i= 0, max= this.thrownExceptions.length; i < max; i++) { if ((this.thrownExceptions[i].tagBits & TagBits.HasMissingType) != 0) { this.tagBits|= TagBits.HasMissingType; break checkMissingType; } } } 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]; } return originalVariable; } /** * @see org.eclipse.jdt.internal.compiler.lookup.MethodBinding#tiebreakMethod() */ public MethodBinding tiebreakMethod() { if (this.tiebreakMethod == null) this.tiebreakMethod= this.originalMethod.asRawMethod(this.environment); return this.tiebreakMethod; } }