From fea40ad46815cb372312f8ae223efcb3b879dd0f Mon Sep 17 00:00:00 2001
From: Damien George <damien.p.george@gmail.com>
Date: Thu, 21 Apr 2016 16:51:36 +0100
Subject: [PATCH] py: Fix bug passing a string as a keyword arg in a dict.

Addresses issue #1998.
---
 py/bc.c                          |  1 +
 py/runtime.c                     | 14 ++++++++++++--
 tests/basics/fun_calldblstar2.py | 13 +++++++++++++
 3 files changed, 26 insertions(+), 2 deletions(-)
 create mode 100644 tests/basics/fun_calldblstar2.py

diff --git a/py/bc.c b/py/bc.c
index 750ca2aed..5cdaa4770 100644
--- a/py/bc.c
+++ b/py/bc.c
@@ -171,6 +171,7 @@ void mp_setup_code_state(mp_code_state *code_state, mp_obj_fun_bc_t *self, size_
         const mp_obj_t *arg_names = (const mp_obj_t*)code_state->const_table;
 
         for (size_t i = 0; i < n_kw; i++) {
+            // the keys in kwargs are expected to be qstr objects
             mp_obj_t wanted_arg_name = kwargs[2 * i];
             for (size_t j = 0; j < n_pos_args + n_kwonly_args; j++) {
                 if (wanted_arg_name == arg_names[j]) {
diff --git a/py/runtime.c b/py/runtime.c
index adbab579f..67534c4b5 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -704,7 +704,12 @@ void mp_call_prepare_args_n_kw_var(bool have_self, mp_uint_t n_args_n_kw, const
         assert(args2_len + 2 * map->used <= args2_alloc); // should have enough, since kw_dict_len is in this case hinted correctly above
         for (mp_uint_t i = 0; i < map->alloc; i++) {
             if (MP_MAP_SLOT_IS_FILLED(map, i)) {
-                args2[args2_len++] = map->table[i].key;
+                // 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_TYPE(key, &mp_type_str)) {
+                    key = mp_obj_str_intern(key);
+                }
+                args2[args2_len++] = key;
                 args2[args2_len++] = map->table[i].value;
             }
         }
@@ -726,7 +731,12 @@ void mp_call_prepare_args_n_kw_var(bool have_self, mp_uint_t n_args_n_kw, const
             }
             mp_obj_t *items;
             mp_obj_get_array_fixed_n(item, 2, &items);
-            args2[args2_len++] = items[0];
+            // the key must be a qstr, so intern it if it's a string
+            mp_obj_t key = items[0];
+            if (MP_OBJ_IS_TYPE(key, &mp_type_str)) {
+                key = mp_obj_str_intern(key);
+            }
+            args2[args2_len++] = key;
             args2[args2_len++] = items[1];
         }
     }
diff --git a/tests/basics/fun_calldblstar2.py b/tests/basics/fun_calldblstar2.py
new file mode 100644
index 000000000..cf982ef5b
--- /dev/null
+++ b/tests/basics/fun_calldblstar2.py
@@ -0,0 +1,13 @@
+# test passing a string object as the key for a keyword argument
+
+# they key in this dict is a string object and is not interned
+args = {'thisisaverylongargumentname': 123}
+
+# when this string is executed it will intern the keyword argument
+exec("def foo(*,thisisaverylongargumentname=1):\n print(thisisaverylongargumentname)")
+
+# test default arg
+foo()
+
+# the string from the dict should match the interned keyword argument
+foo(**args)
-- 
GitLab