static int vGetArgsKeywordsFastImpl()

in ext/Python/getargs.cpp [1422:1602]


static int vGetArgsKeywordsFastImpl(PyObject* const* args, Py_ssize_t nargs,
                                    PyObject* keywords, PyObject* kwnames,
                                    struct _PyArg_Parser* parser, va_list* p_va,
                                    int flags) {
  freelistentry_t static_entries[STATIC_FREELIST_ENTRIES];
  freelist_t freelist;
  freelist.entries = static_entries;
  freelist.first_available = 0;
  freelist.entries_malloced = 0;

  DCHECK(keywords == nullptr || PyDict_Check(keywords),
         "keywords must be null or a dict");
  DCHECK(kwnames == nullptr || PyTuple_Check(kwnames),
         "kwnames must be null or a tuple");
  DCHECK(keywords == nullptr || kwnames == nullptr,
         "both keywords and kwnames cannot be non-null");
  DCHECK(parser != nullptr, "parser must not be null");
  DCHECK(p_va != nullptr, "p_va must not be null");

  int keyword_count = 0;
  if (!parserInit(parser, &keyword_count)) {
    return false;
  }

  int pos = parser->pos;
  int len = pos + keyword_count;

  if (len > STATIC_FREELIST_ENTRIES) {
    freelist.entries = PyMem_NEW(freelistentry_t, len);
    if (freelist.entries == nullptr) {
      PyErr_NoMemory();
      return 0;
    }
    freelist.entries_malloced = 1;
  }

  Py_ssize_t num_keywords = 0;
  PyObject* const* kwstack = nullptr;
  if (keywords != nullptr) {
    num_keywords = PyDict_Size(keywords);
  } else if (kwnames != nullptr) {
    num_keywords = PyTuple_GET_SIZE(kwnames);
    kwstack = args + nargs;
  }

  if (nargs + num_keywords > len) {
    PyErr_Format(PyExc_TypeError,
                 "%s%s takes at most %d argument%s (%zd given)",
                 (parser->fname == nullptr) ? "function" : parser->fname,
                 (parser->fname == nullptr) ? "" : "()", len,
                 (len == 1) ? "" : "s", nargs + num_keywords);
    return cleanreturn(0, &freelist);
  }
  if (parser->max < nargs) {
    PyErr_Format(
        PyExc_TypeError, "Function takes %s %d positional arguments (%d given)",
        (parser->min != INT_MAX) ? "at most" : "exactly", parser->max, nargs);
    return cleanreturn(0, &freelist);
  }

  // convert tuple args and keyword args in same loop, using kwtuple to drive
  // process
  const char* format = parser->format;
  for (int i = 0; i < len; i++) {
    const char* keyword = i >= pos ? parser->keywords[i] : nullptr;
    if (*format == '|') {
      format++;
    }
    if (*format == '$') {
      format++;
    }
    DCHECK(!IS_END_OF_FORMAT(*format), "end of format was reached");

    PyObject* current_arg = nullptr;
    if (num_keywords && i >= pos) {
      if (keywords != nullptr) {
        current_arg = PyDict_GetItemString(keywords, keyword);
        if (current_arg == nullptr && PyErr_Occurred()) {
          return cleanreturn(0, &freelist);
        }
      } else {
        current_arg = findKeyword(kwnames, kwstack, keyword);
      }
    }
    if (current_arg != nullptr) {
      --num_keywords;
      if (i < nargs) {
        // arg present in tuple and in dict
        PyErr_Format(PyExc_TypeError,
                     "Argument given by name ('%s') "
                     "and position (%d)",
                     keyword, i + 1);
        return cleanreturn(0, &freelist);
      }
    } else if (i < nargs) {
      current_arg = args[i];
    }

    if (current_arg != nullptr) {
      char msgbuf[512];
      int levels[32];
      const char* msg = convertitem(current_arg, &format, p_va, flags, levels,
                                    msgbuf, sizeof(msgbuf), &freelist);
      if (msg) {
        seterror(i + 1, msg, levels, parser->fname, parser->custom_msg);
        return cleanreturn(0, &freelist);
      }
      continue;
    }

    if (i < parser->min) {
      // Less arguments than required
      if (i < pos) {
        PyErr_Format(
            PyExc_TypeError,
            "Function takes %s %d positional arguments"
            " (%d given)",
            (Py_MIN(pos, parser->min) < parser->max) ? "at least" : "exactly",
            Py_MIN(pos, parser->min), nargs);
      } else {
        PyErr_Format(PyExc_TypeError,
                     "Required argument "
                     "'%s' (pos %d) not found",
                     keyword, i + 1);
      }
      return cleanreturn(0, &freelist);
    }
    // current code reports success when all required args fulfilled and no
    // keyword args left, with no further validation.
    if (!num_keywords) {
      return cleanreturn(1, &freelist);
    }

    // We are into optional args, skip through to any remaining keyword args
    const char* message = skipitem(&format, p_va, flags);
    DCHECK(message == nullptr, "message was not null");
  }

  DCHECK(IS_END_OF_FORMAT(*format) || (*format == '|') || (*format == '$'),
         "expected end of format, '|', '$'");

  // make sure there are no extraneous keyword arguments
  if (num_keywords > 0) {
    if (keywords != nullptr) {
      PyObject *key, *value;
      Py_ssize_t pos1 = 0;
      while (PyDict_Next(keywords, &pos1, &key, &value)) {
        if (!PyUnicode_Check(key)) {
          Thread::current()->raiseWithFmt(LayoutId::kTypeError,
                                          "keywords must be strings");
          return cleanreturn(0, &freelist);
        }
        if (!isValidKeyword(parser, keyword_count, key)) {
          PyErr_Format(PyExc_TypeError,
                       "'%s' is an invalid keyword "
                       "argument for this function",
                       key);
          return cleanreturn(0, &freelist);
        }
      }
    } else {
      Py_ssize_t num_kwargs = PyTuple_GET_SIZE(kwnames);
      for (Py_ssize_t j = 0; j < num_kwargs; j++) {
        PyObject* key = PyTuple_GET_ITEM(kwnames, j);
        if (!PyUnicode_Check(key)) {
          Thread::current()->raiseWithFmt(LayoutId::kTypeError,
                                          "keywords must be strings");
          return cleanreturn(0, &freelist);
        }
        if (!isValidKeyword(parser, keyword_count, key)) {
          PyErr_Format(PyExc_TypeError,
                       "'%U' is an invalid keyword "
                       "argument for this function",
                       key);
          return cleanreturn(0, &freelist);
        }
      }
    }
  }
  return cleanreturn(1, &freelist);
}