py/{compile,runtime}: Fix *args after kwarg.

This fixes a compiler/runtime bug where *args after a kwarg was not
handled correctly.

Prior to this change, if `*args` was encountered in a function call
after a keyword argument, the compiler would push a single object
and increment the positional arg count. However, two objects for the
keyword argument key and value had already been pushed. This caused
inconsistencies that the runtime could not resolve since it expects
all of the positional args first followed by key/value pairs for the
keyword args.

To fix it, we need to conditionally change what happens when `*args`
is encountered depending on if it is before or after the first keyword
argument. If it is before, everything is handled as before. If after,
instead of pushing a single object and incrementing the positional arg
count, we push two objects and increment the keyword arg count. This
makes it possible for the runtime to handle it with minimal changes.

In the runtime, we have to add some extra checks to handle the new
case of the possibility that one of the `n_kw` args is a `*arg`. We
already have a case where `**arg` is handled as a keyword argument
where the key is `MP_OBJ_NULL`. We now do the same for `*arg` as well.
The existing `star_args` flags is used to determine if the value
corresponding to a key of `MP_OBJ_NULL` is `*arg` or `**arg`.

A couple of test that failed before this fix are added.

Fixes: https://github.com/micropython/micropython/issues/11439
Signed-off-by: David Lechner <david@pybricks.com>
pull/11441/head
David Lechner 2023-05-07 16:19:33 -05:00
rodzic 1093dea709
commit 49cec95fdd
3 zmienionych plików z 55 dodań i 12 usunięć

Wyświetl plik

@ -2401,8 +2401,17 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar
}
star_flags |= MP_EMIT_STAR_FLAG_SINGLE;
star_args |= (mp_uint_t)1 << i;
if (n_keyword == 0) {
// star-args before kwargs encoded as positional arg
n_positional++;
} else {
// star-args after kwargs encoded as kw arg with key=NULL
EMIT(load_null);
n_keyword++;
}
compile_node(comp, pns_arg->nodes[0]);
n_positional++;
} else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_arglist_dbl_star) {
star_flags |= MP_EMIT_STAR_FLAG_DOUBLE;
// double-star args are stored as kw arg with key of None

Wyświetl plik

@ -735,17 +735,30 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
mp_obj_t *args2;
size_t args2_alloc;
size_t args2_len = 0;
size_t n_args_star_args = n_args;
// Try to get a hint for unpacked * args length
ssize_t list_len = 0;
if (star_args != 0) {
for (size_t i = 0; i < n_args; i++) {
if ((star_args >> i) & 1) {
mp_obj_t len = mp_obj_len_maybe(args[i]);
if (len != MP_OBJ_NULL) {
if (star_args) {
// kw can also contain star args.
n_args_star_args += n_kw;
for (size_t i = 0; i < n_args_star_args; i++) {
if (!((star_args >> i) & 1)) {
continue;
}
mp_obj_t arg = i >= n_args ? args[n_args + 2 * (i - n_args) + 1] : args[i];
mp_obj_t len = mp_obj_len_maybe(arg);
if (len != MP_OBJ_NULL) {
list_len += mp_obj_get_int(len);
if (i < n_args) {
// -1 accounts for 1 of n_args occupied by this arg
list_len += mp_obj_get_int(len) - 1;
list_len--;
}
}
}
@ -757,9 +770,20 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
for (size_t i = 0; i < n_kw; i++) {
mp_obj_t key = args[n_args + i * 2];
mp_obj_t value = args[n_args + i * 2 + 1];
if (key == MP_OBJ_NULL && value != MP_OBJ_NULL && mp_obj_is_type(value, &mp_type_dict)) {
if (key == MP_OBJ_NULL) {
// -1 accounts for 1 of n_kw occupied by this arg
kw_dict_len += mp_obj_dict_len(value) - 1;
kw_dict_len--;
if (((star_args >> (n_args + i)) & 1)) {
// star args were already handled above
continue;
}
// double-star args
if (mp_obj_is_type(value, &mp_type_dict)) {
kw_dict_len += mp_obj_dict_len(value);
}
}
}
@ -792,8 +816,9 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
args2[args2_len++] = self;
}
for (size_t i = 0; i < n_args; i++) {
mp_obj_t arg = args[i];
for (size_t i = 0; i < n_args_star_args; i++) {
mp_obj_t arg = i >= n_args ? args[n_args + 2 * (i - n_args) + 1] : args[i];
if ((star_args >> i) & 1) {
// star arg
if (mp_obj_is_type(arg, &mp_type_tuple) || mp_obj_is_type(arg, &mp_type_list)) {
@ -824,7 +849,7 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
args2[args2_len++] = item;
}
}
} else {
} else if (i < n_args) {
// normal argument
assert(args2_len < args2_alloc);
args2[args2_len++] = arg;
@ -848,6 +873,11 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
mp_obj_t kw_key = args[n_args + i * 2];
mp_obj_t kw_value = args[n_args + i * 2 + 1];
if (kw_key == MP_OBJ_NULL) {
if ((star_args >> (n_args + i)) & 1) {
// star args have already been handled above
continue;
}
// double-star args
if (mp_obj_is_type(kw_value, &mp_type_dict)) {
// dictionary

Wyświetl plik

@ -23,6 +23,10 @@ foo(*range(3))
# pos then iterator
foo(1, *range(2, 4))
# star after kw
foo(1, 2, c=3, *())
foo(b=2, *(1,), c=3)
# an iterator with many elements
def foo(*rest):
print(rest)