/**
   * Infer the parameter and return types of a function from the parameter and return types of the
   * function it is overriding.
   *
   * @param oldType The function being overridden. Does nothing if this is null.
   * @param paramsParent The LP node of the function that we're assigning to. If null, that just
   *     means we're not initializing this to a function literal.
   */
  FunctionTypeBuilder inferFromOverriddenFunction(
      @Nullable FunctionType oldType, @Nullable Node paramsParent) {
    if (oldType == null) {
      return this;
    }

    returnType = oldType.getReturnType();
    returnTypeInferred = oldType.isReturnTypeInferred();
    if (paramsParent == null) {
      // Not a function literal.
      parametersNode = oldType.getParametersNode();
      if (parametersNode == null) {
        parametersNode = new FunctionParamBuilder(typeRegistry).build();
      }
    } else {
      // We're overriding with a function literal. Apply type information
      // to each parameter of the literal.
      FunctionParamBuilder paramBuilder = new FunctionParamBuilder(typeRegistry);
      Iterator<Node> oldParams = oldType.getParameters().iterator();
      boolean warnedAboutArgList = false;
      boolean oldParamsListHitOptArgs = false;
      for (Node currentParam = paramsParent.getFirstChild();
          currentParam != null;
          currentParam = currentParam.getNext()) {
        if (oldParams.hasNext()) {
          Node oldParam = oldParams.next();
          Node newParam = paramBuilder.newParameterFromNode(oldParam);

          oldParamsListHitOptArgs =
              oldParamsListHitOptArgs || oldParam.isVarArgs() || oldParam.isOptionalArg();

          // The subclass method might right its var_args as individual
          // arguments.
          if (currentParam.getNext() != null && newParam.isVarArgs()) {
            newParam.setVarArgs(false);
            newParam.setOptionalArg(true);
          }
        } else {
          warnedAboutArgList |=
              addParameter(
                  paramBuilder,
                  typeRegistry.getNativeType(UNKNOWN_TYPE),
                  warnedAboutArgList,
                  codingConvention.isOptionalParameter(currentParam) || oldParamsListHitOptArgs,
                  codingConvention.isVarArgsParameter(currentParam));
        }
      }
      parametersNode = paramBuilder.build();
    }
    return this;
  }
  /**
   * Notice that "call" and "bind" have the same argument signature, except that all the arguments
   * of "bind" (except the first) are optional.
   */
  private FunctionType getCallOrBindSignature(boolean isCall) {
    boolean isBind = !isCall;
    FunctionBuilder builder =
        new FunctionBuilder(registry)
            .withReturnType(isCall ? getReturnType() : getBindReturnType(-1))
            .withTemplateKeys(getTemplateTypeMap().getTemplateKeys());

    Node origParams = getParametersNode();
    if (origParams != null) {
      Node params = origParams.cloneTree();

      Node thisTypeNode = Node.newString(Token.NAME, "thisType");
      thisTypeNode.setJSType(registry.createOptionalNullableType(getTypeOfThis()));
      params.addChildToFront(thisTypeNode);

      if (isBind) {
        // The arguments of bind() are unique in that they are all
        // optional but not undefinable.
        for (Node current = thisTypeNode.getNext(); current != null; current = current.getNext()) {
          current.setOptionalArg(true);
        }
      } else if (isCall) {
        // The first argument of call() is optional iff all the arguments
        // are optional. It's sufficient to check the first argument.
        Node firstArg = thisTypeNode.getNext();
        if (firstArg == null || firstArg.isOptionalArg() || firstArg.isVarArgs()) {
          thisTypeNode.setOptionalArg(true);
        }
      }

      builder.withParamsNode(params);
    }

    return builder.build();
  }
 private void appendArgString(StringBuilder b, Node p, boolean forAnnotations) {
   if (p.isVarArgs()) {
     appendVarArgsString(b, p.getJSType(), forAnnotations);
   } else if (p.isOptionalArg()) {
     appendOptionalArgString(b, p.getJSType(), forAnnotations);
   } else {
     b.append(p.getJSType().toStringHelper(forAnnotations));
   }
 }
  /**
   * Gets the maximum number of arguments that this function requires, or Integer.MAX_VALUE if this
   * is a variable argument function.
   */
  public int getMaxArguments() {
    Node params = getParametersNode();
    if (params != null) {
      Node lastParam = params.getLastChild();
      if (lastParam == null || !lastParam.isVarArgs()) {
        return params.getChildCount();
      }
    }

    return Integer.MAX_VALUE;
  }
 /** Gets the minimum number of arguments that this function requires. */
 public int getMinArguments() {
   // NOTE(nicksantos): There are some native functions that have optional
   // parameters before required parameters. This algorithm finds the position
   // of the last required parameter.
   int i = 0;
   int min = 0;
   for (Node n : getParameters()) {
     i++;
     if (!n.isOptionalArg() && !n.isVarArgs()) {
       min = i;
     }
   }
   return min;
 }
  /**
   * Creates a JSDoc-suitable String representation the type of a parameter.
   *
   * @param parameterNode The parameter node.
   */
  private String getParameterNodeJSDocType(Node parameterNode) {
    JSType parameterType = parameterNode.getJSType();
    String typeString;

    // Emit unknown types as '*' (AllType) since '?' (UnknownType) is not
    // a valid JSDoc type.
    if (parameterType.isUnknownType()) {
      typeString = "*";
    } else {
      // Fix-up optional and vararg parameters to match JSDoc type language
      if (parameterNode.isOptionalArg()) {
        typeString = parameterType.restrictByNotNullOrUndefined().toAnnotationString() + "=";
      } else if (parameterNode.isVarArgs()) {
        typeString = "..." + parameterType.restrictByNotNullOrUndefined().toAnnotationString();
      } else {
        typeString = parameterType.toAnnotationString();
      }
    }

    return typeString;
  }
  /** Infer the parameter types from the list of argument names and the doc info. */
  FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent, @Nullable JSDocInfo info) {
    if (argsParent == null) {
      if (info == null) {
        return this;
      } else {
        return inferParameterTypes(info);
      }
    }

    // arguments
    Node oldParameterType = null;
    if (parametersNode != null) {
      oldParameterType = parametersNode.getFirstChild();
    }

    FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry);
    boolean warnedAboutArgList = false;
    Set<String> allJsDocParams =
        (info == null) ? Sets.<String>newHashSet() : Sets.newHashSet(info.getParameterNames());
    boolean foundTemplateType = false;
    for (Node arg : argsParent.children()) {
      String argumentName = arg.getString();
      allJsDocParams.remove(argumentName);

      // type from JSDocInfo
      JSType parameterType = null;
      boolean isOptionalParam = isOptionalParameter(arg, info);
      boolean isVarArgs = isVarArgsParameter(arg, info);
      if (info != null && info.hasParameterType(argumentName)) {
        parameterType = info.getParameterType(argumentName).evaluate(scope, typeRegistry);
      } else if (oldParameterType != null && oldParameterType.getJSType() != null) {
        parameterType = oldParameterType.getJSType();
        isOptionalParam = oldParameterType.isOptionalArg();
        isVarArgs = oldParameterType.isVarArgs();
      } else {
        parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE);
      }

      if (templateTypeName != null
          && parameterType.restrictByNotNullOrUndefined().isTemplateType()) {
        if (foundTemplateType) {
          reportError(TEMPLATE_TYPE_DUPLICATED, fnName);
        }
        foundTemplateType = true;
      }
      warnedAboutArgList |=
          addParameter(builder, parameterType, warnedAboutArgList, isOptionalParam, isVarArgs);

      if (oldParameterType != null) {
        oldParameterType = oldParameterType.getNext();
      }
    }

    if (templateTypeName != null && !foundTemplateType) {
      reportError(TEMPLATE_TYPE_EXPECTED, fnName);
    }

    for (String inexistentName : allJsDocParams) {
      reportWarning(INEXISTANT_PARAM, inexistentName, fnName);
    }

    parametersNode = builder.build();
    return this;
  }