in runtime/trampolines.cpp [161:280]
static RawObject checkArgs(Thread* thread, const Function& function,
RawObject* kw_arg_base, const Tuple& actual_names,
const Tuple& formal_names, word start) {
word posonlyargcount = RawCode::cast(function.code()).posonlyargcount();
word num_actuals = actual_names.length();
// Helper function to swap actual arguments and names
auto swap = [&kw_arg_base](RawMutableTuple ordered_names, word arg_pos1,
word arg_pos2) -> void {
RawObject tmp = *(kw_arg_base - arg_pos1);
*(kw_arg_base - arg_pos1) = *(kw_arg_base - arg_pos2);
*(kw_arg_base - arg_pos2) = tmp;
tmp = ordered_names.at(arg_pos1);
ordered_names.atPut(arg_pos1, ordered_names.at(arg_pos2));
ordered_names.atPut(arg_pos2, tmp);
};
// Helper function to retrieve argument
auto arg_at = [&kw_arg_base](word idx) -> RawObject& {
return *(kw_arg_base - idx);
};
HandleScope scope(thread);
// In case the order of the parameters in the call does not match the
// declaration order, create a copy of `actual_names` to adjust it to match
// `formal_names`.
Tuple ordered_names(&scope, *actual_names);
Object formal_name(&scope, NoneType::object());
for (word arg_pos = 0; arg_pos < num_actuals; arg_pos++) {
word formal_pos = arg_pos + start;
formal_name = formal_names.at(formal_pos);
RawObject result =
Runtime::objectEquals(thread, ordered_names.at(arg_pos), *formal_name);
if (result.isErrorException()) return result;
if (result == Bool::trueObj()) {
if (formal_pos >= posonlyargcount) {
// We're good here: actual & formal arg names match. Check the next
// one.
continue;
}
// A matching keyword arg but for a positional-only parameter.
return Thread::current()->raiseWithFmt(
LayoutId::kTypeError,
"keyword argument specified for positional-only argument '%S'",
&formal_name);
}
// Mismatch. Try to fix it. Note: args grow down.
// TODO(T66307914): Avoid heap allocation here.
// In case `actual_names` needs to be adjusted, create a copy to avoid
// modifying `actual_names`.
if (ordered_names == actual_names) {
word actual_names_length = actual_names.length();
ordered_names = thread->runtime()->newMutableTuple(actual_names_length);
for (word i = 0; i < actual_names_length; ++i) {
ordered_names.atPut(i, actual_names.at(i));
}
}
DCHECK(ordered_names.isMutableTuple(), "MutableTuple is expected");
bool swapped = false;
// Look for expected Formal name in Actuals tuple.
for (word i = arg_pos + 1; i < num_actuals; i++) {
result = Runtime::objectEquals(thread, ordered_names.at(i), *formal_name);
if (result.isErrorException()) return result;
if (result == Bool::trueObj()) {
// Found it. Swap both the stack and the ordered_names tuple.
swap(MutableTuple::cast(*ordered_names), arg_pos, i);
swapped = true;
break;
}
}
if (swapped) {
// We managed to fix it. Check the next one.
continue;
}
// Can't find an Actual for this Formal.
// If we have a real actual in current slot, move it somewhere safe.
if (!arg_at(arg_pos).isError()) {
for (word i = arg_pos + 1; i < num_actuals; i++) {
if (arg_at(i).isError()) {
// Found an uninitialized slot. Use it to save current actual.
swap(MutableTuple::cast(*ordered_names), arg_pos, i);
break;
}
}
// If we were unable to find a slot to swap into, TypeError
if (!arg_at(arg_pos).isError()) {
Object param_name(&scope, swapped ? formal_names.at(arg_pos)
: ordered_names.at(arg_pos));
return thread->raiseWithFmt(
LayoutId::kTypeError,
"%F() got an unexpected keyword argument '%S'", &function,
¶m_name);
}
}
// Now, can we fill that slot with a default argument?
word absolute_pos = arg_pos + start;
word argcount = function.argcount();
if (absolute_pos < argcount) {
word defaults_size = function.hasDefaults()
? Tuple::cast(function.defaults()).length()
: 0;
word defaults_start = argcount - defaults_size;
if (absolute_pos >= (defaults_start)) {
// Set the default value
Tuple default_args(&scope, function.defaults());
*(kw_arg_base - arg_pos) =
default_args.at(absolute_pos - defaults_start);
continue; // Got it, move on to the next
}
} else if (!function.kwDefaults().isNoneType()) {
// How about a kwonly default?
Dict kw_defaults(&scope, function.kwDefaults());
Str name(&scope, formal_names.at(arg_pos + start));
RawObject val = dictAtByStr(thread, kw_defaults, name);
if (!val.isErrorNotFound()) {
*(kw_arg_base - arg_pos) = val;
continue; // Got it, move on to the next
}
}
return thread->raiseWithFmt(LayoutId::kTypeError, "missing argument");
}
return NoneType::object();
}