#include "py/parsenum.h"
#include "py/compile.h"
@@ -699,12 +700,11 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
if (have_self) {
self = *args++; // may be MP_OBJ_NULL
}
- uint n_args = n_args_n_kw & 0xff;
- uint n_kw = (n_args_n_kw >> 8) & 0xff;
- mp_obj_t pos_seq = args[n_args + 2 * n_kw]; // may be MP_OBJ_NULL
- mp_obj_t kw_dict = args[n_args + 2 * n_kw + 1]; // may be MP_OBJ_NULL
+ size_t n_args = n_args_n_kw & 0xff;
+ size_t n_kw = (n_args_n_kw >> 8) & 0xff;
+ mp_uint_t star_args = mp_obj_get_int_truncated(args[n_args + 2 * n_kw]);
- DEBUG_OP_printf("call method var (fun=%p, self=%p, n_args=%u, n_kw=%u, args=%p, seq=%p, dict=%p)\n", fun, self, n_args, n_kw, args, pos_seq, kw_dict);
+ DEBUG_OP_printf("call method var (fun=%p, self=%p, n_args=%u, n_kw=%u, args=%p, map=%u)\n", fun, self, n_args, n_kw, args, star_args);
// We need to create the following array of objects:
// args[0 .. n_args] unpacked(pos_seq) args[n_args .. n_args + 2 * n_kw] unpacked(kw_dict)
@@ -712,19 +712,40 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
// The new args array
mp_obj_t *args2;
- uint args2_alloc;
- uint args2_len = 0;
+ size_t args2_alloc;
+ size_t args2_len = 0;
+
+ // 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 & (1 << i)) {
+ mp_obj_t len = mp_obj_len_maybe(args[i]);
+ if (len != MP_OBJ_NULL) {
+ // -1 accounts for 1 of n_args occupied by this arg
+ list_len += mp_obj_get_int(len) - 1;
+ }
+ }
+ }
+ }
// Try to get a hint for the size of the kw_dict
- uint kw_dict_len = 0;
- if (kw_dict != MP_OBJ_NULL && mp_obj_is_type(kw_dict, &mp_type_dict)) {
- kw_dict_len = mp_obj_dict_len(kw_dict);
+ ssize_t kw_dict_len = 0;
+
+ 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)) {
+ // -1 accounts for 1 of n_kw occupied by this arg
+ kw_dict_len += mp_obj_dict_len(value) - 1;
+ }
}
// Extract the pos_seq sequence to the new args array.
// Note that it can be arbitrary iterator.
- if (pos_seq == MP_OBJ_NULL) {
- // no sequence
+ if (star_args == 0) {
+ // no star args to unpack
// allocate memory for the new array of args
args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len);
@@ -738,33 +759,11 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
// copy the fixed pos args
mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t);
args2_len += n_args;
-
- } else if (mp_obj_is_type(pos_seq, &mp_type_tuple) || mp_obj_is_type(pos_seq, &mp_type_list)) {
- // optimise the case of a tuple and list
-
- // get the items
- size_t len;
- mp_obj_t *items;
- mp_obj_get_array(pos_seq, &len, &items);
-
- // allocate memory for the new array of args
- args2_alloc = 1 + n_args + len + 2 * (n_kw + kw_dict_len);
- args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t));
-
- // copy the self
- if (self != MP_OBJ_NULL) {
- args2[args2_len++] = self;
- }
-
- // copy the fixed and variable position args
- mp_seq_cat(args2 + args2_len, args, n_args, items, len, mp_obj_t);
- args2_len += n_args + len;
-
} else {
- // generic iterator
+ // at least one star arg to unpack
// allocate memory for the new array of args
- args2_alloc = 1 + n_args + 2 * (n_kw + kw_dict_len) + 3;
+ args2_alloc = 1 + n_args + list_len + 2 * (n_kw + kw_dict_len);
args2 = mp_nonlocal_alloc(args2_alloc * sizeof(mp_obj_t));
// copy the self
@@ -772,84 +771,120 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
args2[args2_len++] = self;
}
- // copy the fixed position args
- mp_seq_copy(args2 + args2_len, args, n_args, mp_obj_t);
- args2_len += n_args;
-
- // extract the variable position args from the iterator
- mp_obj_iter_buf_t iter_buf;
- mp_obj_t iterable = mp_getiter(pos_seq, &iter_buf);
- mp_obj_t item;
- while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
- if (args2_len >= args2_alloc) {
- args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), args2_alloc * 2 * sizeof(mp_obj_t));
- args2_alloc *= 2;
+ for (size_t i = 0; i < n_args; i++) {
+ mp_obj_t arg = args[i];
+ if (star_args & (1 << i)) {
+ // star arg
+ if (mp_obj_is_type(arg, &mp_type_tuple) || mp_obj_is_type(arg, &mp_type_list)) {
+ // optimise the case of a tuple and list
+
+ // get the items
+ size_t len;
+ mp_obj_t *items;
+ mp_obj_get_array(arg, &len, &items);
+
+ // copy the items
+ assert(args2_len + len <= args2_alloc);
+ mp_seq_copy(args2 + args2_len, items, len, mp_obj_t);
+ args2_len += len;
+ } else {
+ // generic iterator
+
+ // extract the variable position args from the iterator
+ mp_obj_iter_buf_t iter_buf;
+ mp_obj_t iterable = mp_getiter(arg, &iter_buf);
+ mp_obj_t item;
+ while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
+ if (args2_len >= args2_alloc) {
+ args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t),
+ args2_alloc * 2 * sizeof(mp_obj_t));
+ args2_alloc *= 2;
+ }
+ args2[args2_len++] = item;
+ }
+ }
+ } else {
+ // normal argument
+ assert(args2_len < args2_alloc);
+ args2[args2_len++] = arg;
}
- args2[args2_len++] = item;
}
}
// The size of the args2 array now is the number of positional args.
- uint pos_args_len = args2_len;
-
- // Copy the fixed kw args.
- mp_seq_copy(args2 + args2_len, args + n_args, 2 * n_kw, mp_obj_t);
- args2_len += 2 * n_kw;
+ size_t pos_args_len = args2_len;
+
+ // ensure there is still enough room for kw args
+ if (args2_len + 2 * (n_kw + kw_dict_len) > args2_alloc) {
+ size_t new_alloc = args2_len + 2 * (n_kw + kw_dict_len);
+ args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t),
+ new_alloc * sizeof(mp_obj_t));
+ args2_alloc = new_alloc;
+ }
- // Extract (key,value) pairs from kw_dict dictionary and append to args2.
- // Note that it can be arbitrary iterator.
- if (kw_dict == MP_OBJ_NULL) {
- // pass
- } else if (mp_obj_is_type(kw_dict, &mp_type_dict)) {
- // dictionary
- mp_map_t *map = mp_obj_dict_get_map(kw_dict);
- assert(args2_len + 2 * map->used <= args2_alloc); // should have enough, since kw_dict_len is in this case hinted correctly above
- for (size_t i = 0; i < map->alloc; i++) {
- if (mp_map_slot_is_filled(map, i)) {
- // the key must be a qstr, so intern it if it's a string
- mp_obj_t key = map->table[i].key;
- if (!mp_obj_is_qstr(key)) {
- key = mp_obj_str_intern_checked(key);
- }
- args2[args2_len++] = key;
- args2[args2_len++] = map->table[i].value;
- }
- }
- } else {
- // generic mapping:
- // - call keys() to get an iterable of all keys in the mapping
- // - call __getitem__ for each key to get the corresponding value
-
- // get the keys iterable
- mp_obj_t dest[3];
- mp_load_method(kw_dict, MP_QSTR_keys, dest);
- mp_obj_t iterable = mp_getiter(mp_call_method_n_kw(0, 0, dest), NULL);
-
- mp_obj_t key;
- while ((key = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
- // expand size of args array if needed
- if (args2_len + 1 >= args2_alloc) {
- uint new_alloc = args2_alloc * 2;
- if (new_alloc < 4) {
- new_alloc = 4;
+ // Copy the kw args.
+ for (size_t i = 0; i < n_kw; i++) {
+ 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) {
+ // double-star args
+ if (kw_value == MP_OBJ_NULL) {
+ // pass
+ } else if (mp_obj_is_type(kw_value, &mp_type_dict)) {
+ // dictionary
+ mp_map_t *map = mp_obj_dict_get_map(kw_value);
+ // should have enough, since kw_dict_len is in this case hinted correctly above
+ assert(args2_len + 2 * map->used <= args2_alloc);
+ for (size_t j = 0; j < map->alloc; j++) {
+ if (mp_map_slot_is_filled(map, j)) {
+ // the key must be a qstr, so intern it if it's a string
+ mp_obj_t key = map->table[j].key;
+ if (!mp_obj_is_qstr(key)) {
+ key = mp_obj_str_intern_checked(key);
+ }
+ args2[args2_len++] = key;
+ args2[args2_len++] = map->table[j].value;
+ }
}
- args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), new_alloc * sizeof(mp_obj_t));
- args2_alloc = new_alloc;
- }
+ } else {
+ // generic mapping:
+ // - call keys() to get an iterable of all keys in the mapping
+ // - call __getitem__ for each key to get the corresponding value
+
+ // get the keys iterable
+ mp_obj_t dest[3];
+ mp_load_method(kw_value, MP_QSTR_keys, dest);
+ mp_obj_t iterable = mp_getiter(mp_call_method_n_kw(0, 0, dest), NULL);
+
+ mp_obj_t key;
+ while ((key = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
+ // expand size of args array if needed
+ if (args2_len + 1 >= args2_alloc) {
+ size_t new_alloc = args2_alloc * 2;
+ args2 = mp_nonlocal_realloc(args2, args2_alloc * sizeof(mp_obj_t), new_alloc * sizeof(mp_obj_t));
+ args2_alloc = new_alloc;
+ }
- // the key must be a qstr, so intern it if it's a string
- if (!mp_obj_is_qstr(key)) {
- key = mp_obj_str_intern_checked(key);
- }
+ // the key must be a qstr, so intern it if it's a string
+ if (!mp_obj_is_qstr(key)) {
+ key = mp_obj_str_intern_checked(key);
+ }
- // get the value corresponding to the key
- mp_load_method(kw_dict, MP_QSTR___getitem__, dest);
- dest[2] = key;
- mp_obj_t value = mp_call_method_n_kw(1, 0, dest);
+ // get the value corresponding to the key
+ mp_load_method(kw_value, MP_QSTR___getitem__, dest);
+ dest[2] = key;
+ mp_obj_t value = mp_call_method_n_kw(1, 0, dest);
- // store the key/value pair in the argument array
- args2[args2_len++] = key;
- args2[args2_len++] = value;
+ // store the key/value pair in the argument array
+ args2[args2_len++] = key;
+ args2[args2_len++] = value;
+ }
+ }
+ } else {
+ // normal kwarg
+ assert(args2_len + 2 <= args2_alloc);
+ args2[args2_len++] = kw_key;
+ args2[args2_len++] = kw_value;
}
}
diff --git a/py/vm.c b/py/vm.c
index 50da90e7d2500..e6265814aad25 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -949,8 +949,8 @@ unwind_jump:;
// unum & 0xff == n_positional
// (unum >> 8) & 0xff == n_keyword
// We have following stack layout here:
- // fun arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS
- sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 2;
+ // fun arg0 arg1 ... kw0 val0 kw1 val1 ... seq <- TOS
+ sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1;
#if MICROPY_STACKLESS
if (mp_obj_get_type(*sp) == &mp_type_fun_bc) {
code_state->ip = ip;
@@ -1034,8 +1034,8 @@ unwind_jump:;
// unum & 0xff == n_positional
// (unum >> 8) & 0xff == n_keyword
// We have following stack layout here:
- // fun self arg0 arg1 ... kw0 val0 kw1 val1 ... seq dict <- TOS
- sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 3;
+ // fun self arg0 arg1 ... kw0 val0 kw1 val1 ... seq <- TOS
+ sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 2;
#if MICROPY_STACKLESS
if (mp_obj_get_type(*sp) == &mp_type_fun_bc) {
code_state->ip = ip;
diff --git a/tests/basics/fun_calldblstar4.py b/tests/basics/fun_calldblstar4.py
new file mode 100644
index 0000000000000..acb332a8c23bb
--- /dev/null
+++ b/tests/basics/fun_calldblstar4.py
@@ -0,0 +1,33 @@
+# test calling a function with multiple **args
+
+
+def f(a, b=None, c=None):
+ print(a, b, c)
+
+
+f(**{"a": 1}, **{"b": 2})
+f(**{"a": 1}, **{"b": 2}, c=3)
+f(**{"a": 1}, b=2, **{"c": 3})
+
+try:
+ f(1, **{"b": 2}, **{"b": 3})
+except TypeError:
+ print("TypeError")
+
+# test calling a method with multiple **args
+
+
+class A:
+ def f(self, a, b=None, c=None):
+ print(a, b, c)
+
+
+a = A()
+a.f(**{"a": 1}, **{"b": 2})
+a.f(**{"a": 1}, **{"b": 2}, c=3)
+a.f(**{"a": 1}, b=2, **{"c": 3})
+
+try:
+ a.f(1, **{"b": 2}, **{"b": 3})
+except TypeError:
+ print("TypeError")
diff --git a/tests/basics/fun_callstar.py b/tests/basics/fun_callstar.py
index a27a288a3c2be..53d2ece3e138e 100644
--- a/tests/basics/fun_callstar.py
+++ b/tests/basics/fun_callstar.py
@@ -3,10 +3,16 @@
def foo(a, b, c):
print(a, b, c)
+foo(*(), 1, 2, 3)
+foo(*(1,), 2, 3)
+foo(*(1, 2), 3)
foo(*(1, 2, 3))
foo(1, *(2, 3))
foo(1, 2, *(3,))
foo(1, 2, 3, *())
+foo(*(1,), 2, *(3,))
+foo(*(1, 2), *(3,))
+foo(*(1,), *(2, 3))
# Another sequence type
foo(1, 2, *[100])
@@ -29,10 +35,16 @@ def foo(self, a, b, c):
print(a, b, c)
a = A()
+a.foo(*(), 1, 2, 3)
+a.foo(*(1,), 2, 3)
+a.foo(*(1, 2), 3)
a.foo(*(1, 2, 3))
a.foo(1, *(2, 3))
a.foo(1, 2, *(3,))
a.foo(1, 2, 3, *())
+a.foo(*(1,), 2, *(3,))
+a.foo(*(1, 2), *(3,))
+a.foo(*(1,), *(2, 3))
# Another sequence type
a.foo(1, 2, *[100])
diff --git a/tests/basics/fun_callstardblstar.py b/tests/basics/fun_callstardblstar.py
index f2fd29107e270..7db458b561b5f 100644
--- a/tests/basics/fun_callstardblstar.py
+++ b/tests/basics/fun_callstardblstar.py
@@ -6,6 +6,11 @@ def f(a, b, c, d):
f(*(1, 2), **{'c':3, 'd':4})
f(*(1, 2), **{['c', 'd'][i]:(3 + i) for i in range(2)})
+try:
+ eval("f(**{'a': 1}, *(2, 3, 4))")
+except SyntaxError:
+ print("SyntaxError")
+
# test calling a method with *tuple and **dict
class A:
@@ -15,3 +20,17 @@ def f(self, a, b, c, d):
a = A()
a.f(*(1, 2), **{'c':3, 'd':4})
a.f(*(1, 2), **{['c', 'd'][i]:(3 + i) for i in range(2)})
+
+try:
+ eval("a.f(**{'a': 1}, *(2, 3, 4))")
+except SyntaxError:
+ print("SyntaxError")
+
+
+# coverage test for arg allocation corner case
+
+def f2(*args, **kwargs):
+ print(len(args), len(kwargs))
+
+
+f2(*iter(range(4)), **{'a': 1})
diff --git a/tests/basics/fun_kwvarargs.py b/tests/basics/fun_kwvarargs.py
index bdc10fcf14d8c..e9fd0720e94ff 100644
--- a/tests/basics/fun_kwvarargs.py
+++ b/tests/basics/fun_kwvarargs.py
@@ -23,3 +23,16 @@ def f4(*vargs, **kwargs):
f4(*(1, 2))
f4(kw_arg=3)
f4(*(1, 2), kw_arg=3)
+
+
+# test evaluation order of arguments
+def f5(*vargs, **kwargs):
+ print(vargs, kwargs)
+
+
+def print_ret(x):
+ print(x)
+ return x
+
+
+f5(*print_ret(["a", "b"]), kw_arg=print_ret(None))
diff --git a/tests/basics/python34.py b/tests/basics/python34.py
index 0f6e4bafd0b9e..36e25e20dd5f9 100644
--- a/tests/basics/python34.py
+++ b/tests/basics/python34.py
@@ -6,27 +6,23 @@
print("SKIP")
raise SystemExit
-# from basics/fun_kwvarargs.py
-# test evaluation order of arguments (in 3.4 it's backwards, 3.5 it's fixed)
-def f4(*vargs, **kwargs):
- print(vargs, kwargs)
+
def print_ret(x):
print(x)
return x
-f4(*print_ret(['a', 'b']), kw_arg=print_ret(None))
# test evaluation order of dictionary key/value pair (in 3.4 it's backwards)
{print_ret(1):print_ret(2)}
+
# from basics/syntaxerror.py
def test_syntax(code):
try:
exec(code)
except SyntaxError:
print("SyntaxError")
-test_syntax("f(*a, *b)") # can't have multiple * (in 3.5 we can)
-test_syntax("f(**a, **b)") # can't have multiple ** (in 3.5 we can)
-test_syntax("f(*a, b)") # can't have positional after *
+
+
test_syntax("f(**a, b)") # can't have positional after **
test_syntax("() = []") # can't assign to empty tuple (in 3.6 we can)
test_syntax("del ()") # can't delete empty tuple (in 3.6 we can)
diff --git a/tests/basics/python34.py.exp b/tests/basics/python34.py.exp
index 8480171307d14..a56c1a50b6996 100644
--- a/tests/basics/python34.py.exp
+++ b/tests/basics/python34.py.exp
@@ -1,14 +1,8 @@
-None
-['a', 'b']
-('a', 'b') {'kw_arg': None}
2
1
SyntaxError
SyntaxError
SyntaxError
-SyntaxError
-SyntaxError
-SyntaxError
3.4
3 4
IndexError('foo',)
diff --git a/tests/cmdline/cmd_showbc.py.exp b/tests/cmdline/cmd_showbc.py.exp
index 92501e1248015..663bacda0159d 100644
--- a/tests/cmdline/cmd_showbc.py.exp
+++ b/tests/cmdline/cmd_showbc.py.exp
@@ -297,8 +297,8 @@ arg names:
208 POP_TOP
209 LOAD_FAST 0
210 LOAD_DEREF 14
-212 LOAD_NULL
-213 CALL_FUNCTION_VAR_KW n=0 nkw=0
+212 LOAD_CONST_SMALL_INT 1
+213 CALL_FUNCTION_VAR_KW n=1 nkw=0
215 POP_TOP
216 LOAD_FAST 0
217 LOAD_METHOD b
@@ -318,8 +318,8 @@ arg names:
239 LOAD_FAST 0
240 LOAD_METHOD b
242 LOAD_FAST 1
-243 LOAD_NULL
-244 CALL_METHOD_VAR_KW n=0 nkw=0
+243 LOAD_CONST_SMALL_INT 1
+244 CALL_METHOD_VAR_KW n=1 nkw=0
246 POP_TOP
247 LOAD_FAST 0
248 POP_JUMP_IF_FALSE 255
diff --git a/tests/cpydiff/syntax_arg_unpacking.py b/tests/cpydiff/syntax_arg_unpacking.py
new file mode 100644
index 0000000000000..e54832ddb9165
--- /dev/null
+++ b/tests/cpydiff/syntax_arg_unpacking.py
@@ -0,0 +1,23 @@
+"""
+categories: Syntax
+description: Argument unpacking does not work if the argument being unpacked is the nth or greater argument where n is the number of bits in an MP_SMALL_INT.
+cause: The implementation uses an MP_SMALL_INT to flag args that need to be unpacked.
+workaround: Use fewer arguments.
+"""
+
+
+def example(*args):
+ print(len(args))
+
+
+MORE = ["a", "b", "c"]
+
+# fmt: off
+example(
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ *MORE,
+)
+# fmt: on
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