import eclipse 3.1 M4 compiler
[org.ibex.tool.git] / src / org / eclipse / jdt / internal / compiler / lookup / ParameterizedGenericMethodBinding.java
diff --git a/src/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java b/src/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java
new file mode 100644 (file)
index 0000000..bf283be
--- /dev/null
@@ -0,0 +1,379 @@
+/*******************************************************************************
+ * 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;
+       }       
+}