/**
   * Collect the substitutes into a map for certain type variables inside the receiver type e.g.
   * Collection<T>.collectSubstitutes(Collection<List<X>>, Map), will populate Map with: T -->
   * List<X> Constraints: A << F corresponds to: F.collectSubstitutes(..., A, ...,
   * CONSTRAINT_EXTENDS (1)) A = F corresponds to: F.collectSubstitutes(..., A, ...,
   * CONSTRAINT_EQUAL (0)) A >> F corresponds to: F.collectSubstitutes(..., A, ..., CONSTRAINT_SUPER
   * (2))
   */
  public void collectSubstitutes(
      Scope scope, TypeBinding actualType, InferenceContext inferenceContext, int constraint) {

    //	only infer for type params of the generic method
    if (this.declaringElement != inferenceContext.genericMethod) return;

    // cannot infer anything from a null type
    switch (actualType.kind()) {
      case Binding.BASE_TYPE:
        if (actualType == TypeBinding.NULL) return;
        TypeBinding boxedType = scope.environment().computeBoxingType(actualType);
        if (boxedType == actualType) return; // $IDENTITY-COMPARISON$
        actualType = boxedType;
        break;
      case Binding.POLY_TYPE: // cannot steer inference, only learn from it.
      case Binding.WILDCARD_TYPE:
        return; // wildcards are not true type expressions (JLS 15.12.2.7, p.453 2nd discussion)
    }

    // reverse constraint, to reflect variable on rhs:   A << T --> T >: A
    int variableConstraint;
    switch (constraint) {
      case TypeConstants.CONSTRAINT_EQUAL:
        variableConstraint = TypeConstants.CONSTRAINT_EQUAL;
        break;
      case TypeConstants.CONSTRAINT_EXTENDS:
        variableConstraint = TypeConstants.CONSTRAINT_SUPER;
        break;
      default:
        // case CONSTRAINT_SUPER :
        variableConstraint = TypeConstants.CONSTRAINT_EXTENDS;
        break;
    }
    inferenceContext.recordSubstitute(this, actualType, variableConstraint);
  }
 @Override
 public GroovyResolveResult[] resolveByShape() {
   final InferenceContext context = TypeInferenceHelper.getCurrentContext();
   return context.getCachedValue(
       this,
       new Computable<GroovyResolveResult[]>() {
         @Override
         public GroovyResolveResult[] compute() {
           Pair<GrReferenceExpressionImpl, InferenceContext> key =
               Pair.create(GrReferenceExpressionImpl.this, context);
           GroovyResolveResult[] value =
               RecursionManager.doPreventingRecursion(
                   key,
                   true,
                   new Computable<GroovyResolveResult[]>() {
                     @Override
                     public GroovyResolveResult[] compute() {
                       return doPolyResolve(false, false);
                     }
                   });
           return value == null ? GroovyResolveResult.EMPTY_ARRAY : value;
         }
       });
 }
  /** 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;
  }
 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;
 }
  /** 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();
    InferenceContext inferenceContext = null;
    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 = 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 = invocationSite.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: https://bugs.eclipse.org/bugs/show_bug.cgi?id=242159, Inferred types may contain self reference
       in formal bounds. If "T extends I<T>" is a original type variable and T was inferred to be I<T> due possibly
       to under constraints and resultant glb application per 15.12.2.8, using this.typeArguments to drive the bounds
       check against itself is doomed to fail. For, the variable T would after substitution be I<I<T>> and would fail
       bounds check against I<T>. Use the inferred types from the context directly - see that there is one round of
       extra substitution that has taken place to properly substitute a remaining unresolved variable which also appears
       in a formal bound  (So we really have a bounds mismatch between I<I<T>> and I<I<I<T>>>, in the absence of a fix.)
    */
    Substitution substitution = null;
    if (inferenceContext != null) {
      substitution =
          new LingeringTypeVariableEliminator(typeVariables, inferenceContext.substitutes, scope);
    } else {
      substitution = methodSubstitute;
    }
    for (int i = 0, length = typeVariables.length; i < length; i++) {
      TypeVariableBinding typeVariable = typeVariables[i];
      TypeBinding substitute = methodSubstitute.typeArguments[i]; // retain for diagnostics
      /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=375394, To avoid spurious bounds check failures due to circularity in formal bounds,
        we should eliminate only the lingering embedded type variable references after substitution, not alien type variable references
        that constitute the inference per se.
      */
      TypeBinding substituteForChecks;
      if (substitute instanceof TypeVariableBinding) {
        substituteForChecks = substitute;
      } else {
        substituteForChecks =
            Scope.substitute(
                new LingeringTypeVariableEliminator(typeVariables, null, scope),
                substitute); // while using this for bounds check
      }

      if (uncheckedArguments != null && uncheckedArguments[i] == null)
        continue; // only bound check if inferred through 15.12.2.6
      switch (typeVariable.boundCheck(substitution, substituteForChecks, scope)) {
        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;
  }
 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, scope, scope.environment());
       TypeBinding mostSpecificSubstitute = null;
       // https://bugs.eclipse.org/bugs/show_bug.cgi?id=341795 - Per 15.12.2.8, we should fully
       // apply glb
       if (glb != null) {
         if (glb.length == 1) {
           mostSpecificSubstitute = glb[0];
         } else {
           TypeBinding[] otherBounds = new TypeBinding[glb.length - 1];
           System.arraycopy(glb, 1, otherBounds, 0, glb.length - 1);
           mostSpecificSubstitute =
               scope.environment().createWildcard(null, 0, glb[0], otherBounds, Wildcard.EXTENDS);
         }
       }
       if (mostSpecificSubstitute != null) {
         substitutes[i] = mostSpecificSubstitute;
       }
     }
   }
   return true;
 }