Skip to content

Commit 992b4b6

Browse files
committed
Add frozendict support for recursive constant merging
1 parent b027839 commit 992b4b6

2 files changed

Lines changed: 89 additions & 3 deletions

File tree

Lib/test/test_compile.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,21 @@ def check_same_constant(const):
842842
)
843843
self.assertEqual(c.co_consts, (frozendict({0: frozendict({1: 2})}),))
844844

845+
# A tuple value inside a frozendict is merged with the same
846+
# constant used elsewhere. Use a variable to ensure the two tuple
847+
# objects are distinct before they are merged.
848+
name = "not a name"
849+
t_standalone = (name,)
850+
m = ast.Interactive([
851+
ast.Expr(ast.Constant(t_standalone)),
852+
ast.Expr(ast.Constant(frozendict({0: (name,)}))),
853+
ast.Expr(ast.Constant(frozendict({(name,): 0}))),
854+
])
855+
ast.fix_missing_locations(m)
856+
c = compile(m, "<test>", "single")
857+
self.assertIs(c.co_consts[0], c.co_consts[1][0])
858+
self.assertIs(c.co_consts[0], next(iter(c.co_consts[2])))
859+
845860
# Merging equal co_linetable is not a strict requirement
846861
# for the Python semantics, it's a more an implementation detail.
847862
@support.cpython_only

Python/compile.c

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ compiler_set_qualname(compiler *c)
332332
}
333333

334334
/* Merge const *o* and return constant key object.
335-
* If recursive, insert all elements if o is a tuple or frozen set.
335+
* If recursive, insert all elements if o is a tuple, frozenset, or frozendict.
336336
*/
337337
static PyObject*
338338
const_cache_insert(PyObject *const_cache, PyObject *o, bool recursive)
@@ -364,7 +364,7 @@ const_cache_insert(PyObject *const_cache, PyObject *o, bool recursive)
364364
}
365365

366366
// We registered o in const_cache.
367-
// When o is a tuple or frozenset, we want to merge its
367+
// When o is a tuple, frozenset, or frozendict, we want to merge its
368368
// items too.
369369
if (PyTuple_CheckExact(o)) {
370370
Py_ssize_t len = PyTuple_GET_SIZE(o);
@@ -431,7 +431,7 @@ const_cache_insert(PyObject *const_cache, PyObject *o, bool recursive)
431431
}
432432

433433
// Instead of rewriting o, we create new frozenset and embed in the
434-
// key tuple. Caller should get merged frozenset from the key tuple.
434+
// key tuple. Caller should get merged frozenset from the key tuple.
435435
PyObject *new = PyFrozenSet_New(tuple);
436436
Py_DECREF(tuple);
437437
if (new == NULL) {
@@ -442,6 +442,77 @@ const_cache_insert(PyObject *const_cache, PyObject *o, bool recursive)
442442
Py_DECREF(o);
443443
PyTuple_SET_ITEM(key, 1, new);
444444
}
445+
else if (PyFrozenDict_CheckExact(o)) {
446+
// *key* is tuple. And its first item is frozendict of
447+
// constant keys.
448+
// See _PyCode_ConstantKey() for detail.
449+
assert(PyTuple_CheckExact(key));
450+
assert(PyTuple_GET_SIZE(key) == 2);
451+
452+
if (PyDict_GET_SIZE(o) == 0) { // empty frozendict should not be re-created.
453+
return key;
454+
}
455+
PyObject *new_dict = PyDict_New();
456+
if (new_dict == NULL) {
457+
Py_DECREF(key);
458+
return NULL;
459+
}
460+
Py_ssize_t pos = 0;
461+
PyObject *k_obj, *v_obj;
462+
while (PyDict_Next(o, &pos, &k_obj, &v_obj)) {
463+
PyObject *k_result = const_cache_insert(const_cache, k_obj, recursive);
464+
if (k_result == NULL) {
465+
Py_DECREF(new_dict);
466+
Py_DECREF(key);
467+
return NULL;
468+
}
469+
PyObject *k_merged;
470+
if (PyTuple_CheckExact(k_result)) {
471+
k_merged = Py_NewRef(PyTuple_GET_ITEM(k_result, 1));
472+
Py_DECREF(k_result);
473+
}
474+
else {
475+
k_merged = k_result;
476+
}
477+
478+
PyObject *v_result = const_cache_insert(const_cache, v_obj, recursive);
479+
if (v_result == NULL) {
480+
Py_DECREF(k_merged);
481+
Py_DECREF(new_dict);
482+
Py_DECREF(key);
483+
return NULL;
484+
}
485+
PyObject *v_merged;
486+
if (PyTuple_CheckExact(v_result)) {
487+
v_merged = Py_NewRef(PyTuple_GET_ITEM(v_result, 1));
488+
Py_DECREF(v_result);
489+
}
490+
else {
491+
v_merged = v_result;
492+
}
493+
494+
int res = PyDict_SetItem(new_dict, k_merged, v_merged);
495+
Py_DECREF(k_merged);
496+
Py_DECREF(v_merged);
497+
if (res < 0) {
498+
Py_DECREF(new_dict);
499+
Py_DECREF(key);
500+
return NULL;
501+
}
502+
}
503+
504+
// Instead of rewriting o, we create new frozendict and embed in
505+
// the key tuple. Caller should get merged frozendict from the key tuple.
506+
PyObject *new = PyFrozenDict_New(new_dict);
507+
Py_DECREF(new_dict);
508+
if (new == NULL) {
509+
Py_DECREF(key);
510+
return NULL;
511+
}
512+
assert(PyTuple_GET_ITEM(key, 1) == o);
513+
Py_DECREF(o);
514+
PyTuple_SET_ITEM(key, 1, new);
515+
}
445516

446517
return key;
447518
}

0 commit comments

Comments
 (0)