Skip to content

Commit 1faf1cc

Browse files
committed
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: #11439 Signed-off-by: David Lechner <david@pybricks.com>
1 parent 1093dea commit 1faf1cc

File tree

3 files changed

+58
-12
lines changed

3 files changed

+58
-12
lines changed

py/compile.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2401,8 +2401,17 @@ STATIC void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar
24012401
}
24022402
star_flags |= MP_EMIT_STAR_FLAG_SINGLE;
24032403
star_args |= (mp_uint_t)1 << i;
2404+
2405+
if (n_keyword == 0) {
2406+
// star-args before kwargs encoded as positional arg
2407+
n_positional++;
2408+
} else {
2409+
// star-args after kwargs encoded as kw arg with key=NULL
2410+
EMIT(load_null);
2411+
n_keyword++;
2412+
}
2413+
24042414
compile_node(comp, pns_arg->nodes[0]);
2405-
n_positional++;
24062415
} else if (MP_PARSE_NODE_STRUCT_KIND(pns_arg) == PN_arglist_dbl_star) {
24072416
star_flags |= MP_EMIT_STAR_FLAG_DOUBLE;
24082417
// double-star args are stored as kw arg with key of None

py/runtime.c

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -736,16 +736,32 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
736736
size_t args2_alloc;
737737
size_t args2_len = 0;
738738

739+
size_t n_args_star_args = n_args;
740+
741+
// kw can also contain star args.
742+
if (star_args) {
743+
n_args_star_args += n_kw;
744+
}
745+
739746
// Try to get a hint for unpacked * args length
740747
ssize_t list_len = 0;
741748

742-
if (star_args != 0) {
743-
for (size_t i = 0; i < n_args; i++) {
744-
if ((star_args >> i) & 1) {
745-
mp_obj_t len = mp_obj_len_maybe(args[i]);
746-
if (len != MP_OBJ_NULL) {
749+
if (star_args) {
750+
for (size_t i = 0; i < n_args_star_args; i++) {
751+
if (!((star_args >> i) & 1)) {
752+
continue;
753+
}
754+
755+
mp_obj_t arg = i >= n_args ? args[n_args + 2 * (i - n_args) + 1] : args[i];
756+
757+
mp_obj_t len = mp_obj_len_maybe(arg);
758+
759+
if (len != MP_OBJ_NULL) {
760+
list_len += mp_obj_get_int(len);
761+
762+
if (i < n_args) {
747763
// -1 accounts for 1 of n_args occupied by this arg
748-
list_len += mp_obj_get_int(len) - 1;
764+
list_len--;
749765
}
750766
}
751767
}
@@ -757,9 +773,20 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
757773
for (size_t i = 0; i < n_kw; i++) {
758774
mp_obj_t key = args[n_args + i * 2];
759775
mp_obj_t value = args[n_args + i * 2 + 1];
760-
if (key == MP_OBJ_NULL && value != MP_OBJ_NULL && mp_obj_is_type(value, &mp_type_dict)) {
776+
777+
if (key == MP_OBJ_NULL) {
761778
// -1 accounts for 1 of n_kw occupied by this arg
762-
kw_dict_len += mp_obj_dict_len(value) - 1;
779+
kw_dict_len--;
780+
781+
if (((star_args >> (n_args + i)) & 1)) {
782+
// star args were already handled above
783+
continue;
784+
}
785+
786+
// double-star args
787+
if (mp_obj_is_type(value, &mp_type_dict)) {
788+
kw_dict_len += mp_obj_dict_len(value);
789+
}
763790
}
764791
}
765792

@@ -792,8 +819,9 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
792819
args2[args2_len++] = self;
793820
}
794821

795-
for (size_t i = 0; i < n_args; i++) {
796-
mp_obj_t arg = args[i];
822+
for (size_t i = 0; i < n_args_star_args; i++) {
823+
mp_obj_t arg = i >= n_args ? args[n_args + 2 * (i - n_args) + 1] : args[i];
824+
797825
if ((star_args >> i) & 1) {
798826
// star arg
799827
if (mp_obj_is_type(arg, &mp_type_tuple) || mp_obj_is_type(arg, &mp_type_list)) {
@@ -824,7 +852,7 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
824852
args2[args2_len++] = item;
825853
}
826854
}
827-
} else {
855+
} else if (i < n_args) {
828856
// normal argument
829857
assert(args2_len < args2_alloc);
830858
args2[args2_len++] = arg;
@@ -848,6 +876,11 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
848876
mp_obj_t kw_key = args[n_args + i * 2];
849877
mp_obj_t kw_value = args[n_args + i * 2 + 1];
850878
if (kw_key == MP_OBJ_NULL) {
879+
if ((star_args >> (n_args + i)) & 1) {
880+
// star args have already been handled above
881+
continue;
882+
}
883+
851884
// double-star args
852885
if (mp_obj_is_type(kw_value, &mp_type_dict)) {
853886
// dictionary

tests/basics/fun_callstar.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ def foo(a, b, c):
2323
# pos then iterator
2424
foo(1, *range(2, 4))
2525

26+
# star after kw
27+
foo(1, 2, c=3, *())
28+
foo(b=2, *(1,), c=3)
29+
2630
# an iterator with many elements
2731
def foo(*rest):
2832
print(rest)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy