/**
   * 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;
  }
  /** 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;
  }