RawObject prepareKeywordCall()

in runtime/trampolines.cpp [298:448]


RawObject prepareKeywordCall(Thread* thread, word nargs,
                             RawFunction function_raw) {
  HandleScope scope(thread);
  Function function(&scope, function_raw);
  // Pop the tuple of kwarg names
  Tuple keywords(&scope, thread->stackPop());
  Code code(&scope, function.code());
  word expected_args = function.argcount() + code.kwonlyargcount();
  word num_keyword_args = keywords.length();
  word num_positional_args = nargs - num_keyword_args;
  Tuple varnames(&scope, code.varnames());
  Object tmp_varargs(&scope, NoneType::object());
  Object tmp_dict(&scope, NoneType::object());

  // We expect use of keyword argument calls to be uncommon, but when used
  // we anticipate mostly use of simple forms.  General scheme here is to
  // normalize the odd forms into standard form and then handle them all
  // in the same place.
  if (function.hasVarargsOrVarkeyargs()) {
    Runtime* runtime = thread->runtime();
    if (function.hasVarargs()) {
      // If we have more positional than expected, add the remainder to a tuple,
      // remove from the stack and close up the hole.
      word excess = num_positional_args - function.argcount();
      if (excess > 0) {
        MutableTuple varargs(&scope, runtime->newMutableTuple(excess));
        // Point to the leftmost excess argument
        RawObject* p = (thread->stackPointer() + num_keyword_args + excess) - 1;
        // Copy the excess to the * tuple
        for (word i = 0; i < excess; i++) {
          varargs.atPut(i, *(p - i));
        }
        // Fill in the hole
        for (word i = 0; i < num_keyword_args; i++) {
          *p = *(p - excess);
          p--;
        }
        // Adjust the counts
        thread->stackDrop(excess);
        nargs -= excess;
        num_positional_args -= excess;
        tmp_varargs = varargs.becomeImmutable();
      } else {
        tmp_varargs = runtime->emptyTuple();
      }
    }
    if (function.hasVarkeyargs()) {
      // Too many positional args passed?
      if (num_positional_args > function.argcount()) {
        thread->stackDrop(nargs + 1);
        return thread->raiseWithFmt(LayoutId::kTypeError,
                                    "Too many positional arguments");
      }
      // If we have keyword arguments that don't appear in the formal parameter
      // list, add them to a keyword dict.
      Dict dict(&scope, runtime->newDict());
      List saved_keyword_list(&scope, runtime->newList());
      List saved_values(&scope, runtime->newList());
      DCHECK(varnames.length() >= expected_args,
             "varnames must be greater than or equal to positional args");
      RawObject* p = thread->stackPointer() + (num_keyword_args - 1);
      word posonlyargcount = code.posonlyargcount();
      for (word i = 0; i < num_keyword_args; i++) {
        Object key(&scope, keywords.at(i));
        Object value(&scope, *(p - i));
        word result = findName(thread, posonlyargcount, key, varnames);
        if (result < 0) {
          thread->stackDrop(nargs + 1);
          return Error::exception();
        }
        if (result < expected_args) {
          // Got a match, stash pair for future restoration on the stack
          runtime->listAdd(thread, saved_keyword_list, key);
          runtime->listAdd(thread, saved_values, value);
        } else {
          // New, add it and associated value to the varkeyargs dict
          Object hash_obj(&scope, Interpreter::hash(thread, key));
          if (hash_obj.isErrorException()) {
            thread->stackDrop(nargs + 1);
            return *hash_obj;
          }
          word hash = SmallInt::cast(*hash_obj).value();
          Object dict_result(&scope, dictAtPut(thread, dict, key, hash, value));
          if (dict_result.isErrorException()) {
            thread->stackDrop(nargs + 1);
            return *dict_result;
          }
          nargs--;
        }
      }
      // Now, restore the stashed values to the stack and build a new
      // keywords name list.
      thread->stackDrop(num_keyword_args);  // Pop all of the old keyword values
      num_keyword_args = saved_keyword_list.numItems();
      // Replace the old keywords list with a new one.
      if (num_keyword_args > 0) {
        MutableTuple new_keywords(&scope,
                                  runtime->newMutableTuple(num_keyword_args));
        for (word i = 0; i < num_keyword_args; i++) {
          thread->stackPush(saved_values.at(i));
          new_keywords.atPut(i, saved_keyword_list.at(i));
        }
        keywords = new_keywords.becomeImmutable();
      } else {
        keywords = runtime->emptyTuple();
      }
      tmp_dict = *dict;
    }
  }
  // At this point, all vararg forms have been normalized
  RawObject* kw_arg_base = (thread->stackPointer() + num_keyword_args) -
                           1;  // pointer to first non-positional arg
  if (UNLIKELY(nargs > expected_args)) {
    thread->stackDrop(nargs + 1);
    return thread->raiseWithFmt(LayoutId::kTypeError, "Too many arguments");
  }
  if (UNLIKELY(nargs < expected_args)) {
    // Too few args passed.  Can we supply default args to make it work?
    // First, normalize & pad keywords and stack arguments
    word name_tuple_size = expected_args - num_positional_args;
    MutableTuple padded_keywords(
        &scope, thread->runtime()->newMutableTuple(name_tuple_size));
    padded_keywords.replaceFromWith(0, *keywords, num_keyword_args);
    // Fill in missing spots w/ Error code
    for (word i = num_keyword_args; i < name_tuple_size; i++) {
      thread->stackPush(Error::error());
      nargs++;
      padded_keywords.atPut(i, Error::error());
    }
    keywords = padded_keywords.becomeImmutable();
  }
  // Now we've got the right number.  Do they match up?
  RawObject res = checkArgs(thread, function, kw_arg_base, keywords, varnames,
                            num_positional_args);
  if (res.isErrorException()) {
    thread->stackDrop(nargs + 1);
    return res;  // TypeError created by checkArgs.
  }
  CHECK(res.isNoneType(), "checkArgs should return an Error or None");
  // If we're a vararg form, need to push the tuple/dict.
  if (function.hasVarargs()) {
    thread->stackPush(*tmp_varargs);
    nargs++;
  }
  if (function.hasVarkeyargs()) {
    thread->stackPush(*tmp_dict);
    nargs++;
  }
  DCHECK(function.totalArgs() == nargs, "argument count mismatch");
  return *function;
}