Skip to content

Commit ab47db9

Browse files
committed
finish dict literals
1 parent 2b458bc commit ab47db9

File tree

11 files changed

+186
-157
lines changed

11 files changed

+186
-157
lines changed

mypyc/analysis/ircheck.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,25 @@ def check_frozenset_items_valid_literals(self, op: LoadLiteral, s: frozenset[obj
285285
else:
286286
self.fail(op, f"Invalid type for item of frozenset literal: {type(x)})")
287287

288+
def check_dict_items_valid_literals(self, op: LoadLiteral, d: dict[object, object]) -> None:
289+
key_types = (str, bytes, bool, int, float, complex, tuple)
290+
value_types = (str, bytes, bool, int, float, complex, tuple, dict, frozenset)
291+
for k, v in d.items():
292+
# Acceptable key types: str, bytes, bool, int, float, complex, tuple, None
293+
if k is not None and not isinstance(k, key_types):
294+
self.fail(op, f"Invalid type for key of dict literal: {type(k)})")
295+
if isinstance(k, tuple):
296+
self.check_tuple_items_valid_literals(op, k)
297+
# Acceptable value types: str, bytes, bool, int, float, complex, tuple, dict, frozenset, None
298+
if v is not None and not isinstance(v, value_types):
299+
self.fail(op, f"Invalid type for value of dict literal: {type(v)})")
300+
if isinstance(v, tuple):
301+
self.check_tuple_items_valid_literals(op, v)
302+
elif isinstance(v, dict):
303+
self.check_dict_items_valid_literals(op, v)
304+
elif isinstance(v, frozenset):
305+
self.check_frozenset_items_valid_literals(op, v)
306+
288307
def visit_load_literal(self, op: LoadLiteral) -> None:
289308
expected_type = None
290309
if op.value is None:
@@ -309,6 +328,9 @@ def visit_load_literal(self, op: LoadLiteral) -> None:
309328
# it's a set (when it's really a frozenset).
310329
expected_type = "builtins.set"
311330
self.check_frozenset_items_valid_literals(op, op.value)
331+
elif isinstance(op.value, dict):
332+
expected_type = "builtins.dict"
333+
self.check_dict_items_valid_literals(op, op.value)
312334

313335
assert expected_type is not None, "Missed a case for LoadLiteral check"
314336

mypyc/codegen/emitmodule.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,9 @@ def generate_literal_tables(self) -> None:
708708
# Descriptions of frozenset literals
709709
init_frozenset = c_array_initializer(literals.encoded_frozenset_values())
710710
self.declare_global("const int []", "CPyLit_FrozenSet", initializer=init_frozenset)
711+
# Descriptions of dict literals
712+
init_dict = c_array_initializer(literals.encoded_dict_values())
713+
self.declare_global("const int []", "CPyLit_Dict", initializer=init_dict)
711714

712715
def generate_export_table(self, decl_emitter: Emitter, code_emitter: Emitter) -> None:
713716
"""Generate the declaration and definition of the group's export struct.
@@ -926,11 +929,13 @@ def generate_globals_init(self, emitter: Emitter) -> None:
926929
for symbol, fixup in self.simple_inits:
927930
emitter.emit_line(f"{symbol} = {fixup};")
928931

929-
values = "CPyLit_Str, CPyLit_Bytes, CPyLit_Int, CPyLit_Float, CPyLit_Complex, CPyLit_Tuple, CPyLit_FrozenSet"
932+
values = "CPyLit_Str, CPyLit_Bytes, CPyLit_Int, CPyLit_Float, CPyLit_Complex, CPyLit_Tuple, CPyLit_FrozenSet, CPyLit_Dict"
930933
emitter.emit_lines(
931934
f"if (CPyStatics_Initialize(CPyStatics, {values}) < 0) {{", "return -1;", "}"
932935
)
933936

937+
# (No per-dict static assignments needed; dict statics are handled by Literals)
938+
934939
emitter.emit_lines("is_initialized = 1;", "return 0;", "}")
935940

936941
def generate_module_def(self, emitter: Emitter, module_name: str, module: ModuleIR) -> None:

mypyc/codegen/literals.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ def record_literal(self, value: LiteralValue) -> None:
7272
self.record_literal(item)
7373
frozenset_literals[value] = len(frozenset_literals)
7474
elif isinstance(value, dict):
75-
# Represent dicts as a tuple of sorted (key, value) pairs for uniqueness
76-
items = tuple(sorted(value.items()))
75+
items = self.make_dict_literal_key(value)
7776
dict_literals = self.dict_literals
7877
if items not in dict_literals:
7978
for k, v in items:
@@ -118,10 +117,14 @@ def literal_index(self, value: LiteralValue) -> int:
118117
return n + self.frozenset_literals[value]
119118
n += len(self.frozenset_literals)
120119
if isinstance(value, dict):
121-
items = tuple(sorted(value.items()))
122-
return n + self.dict_literals[items]
120+
key = self.make_dict_literal_key(value)
121+
return n + self.dict_literals[key]
123122
assert False, "invalid literal: %r" % value
124123

124+
def make_dict_literal_key(self, value: dict) -> tuple:
125+
"""Make a unique key for a literal dict."""
126+
return tuple(value.items())
127+
125128
def num_literals(self) -> int:
126129
# The first three are for None, True and False
127130
return (

mypyc/ir/ops.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,10 @@ class LoadLiteral(RegisterOp):
812812
813813
Tuple / frozenset literals must contain only valid literal values as items.
814814
815+
Dict literals must contain only literal keys and literal values.
816+
Due to their mutability, dict literals will be copied from the main template
817+
at each use.
818+
815819
NOTE: You can use this to load boxed (Python) int objects. Use
816820
Integer to load unboxed, tagged integers or fixed-width,
817821
low-level integers.

mypyc/irbuild/expression.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,8 +1015,8 @@ def _visit_tuple_display(builder: IRBuilder, expr: TupleExpr) -> Value:
10151015
def dict_literal_values(builder: IRBuilder, items: Sequence[tuple[Expression | None, Expression]], line: int) -> "Value | None":
10161016
"""Try to extract a constant dict from a dict literal.
10171017
1018-
If all keys and values are deeply immutable and constant, build a static dict at module init,
1019-
register it as a static, and return a LoadStatic for it. Otherwise, return None.
1018+
If all keys and values are deeply immutable and constant, register it as a static
1019+
and return a LoadLiteral for it. Otherwise, return None.
10201020
"""
10211021
result = {}
10221022
for key_expr, value_expr in items:
@@ -1032,19 +1032,7 @@ def dict_literal_values(builder: IRBuilder, items: Sequence[tuple[Expression | N
10321032
return None
10331033
result[key] = value
10341034

1035-
dict_reg = builder.call_c(dict_new_op, [], line)
1036-
for k, v in result.items():
1037-
key = builder.load_literal_value(k)
1038-
value = builder.load_literal_value(v)
1039-
builder.primitive_op(dict_set_item_op, [dict_reg, key, value], line)
1040-
1041-
# Register as a static with a unique name
1042-
static_name = f"__mypyc_dict_template__{abs(hash(tuple(sorted(result.items()))))}"
1043-
1044-
if _static_dicts.get(static_name) is None:
1045-
_static_dicts[static_name] = builder.add(InitStatic(dict_reg, static_name, builder.module_name))
1046-
1047-
return builder.add(LoadStatic(dict_rprimitive, static_name, builder.module_name))
1035+
return builder.add(LoadLiteral(result, dict_rprimitive)) if result else None
10481036

10491037
def transform_dict_expr(builder: IRBuilder, expr: DictExpr) -> Value:
10501038
"""First accepts all keys and values, then makes a dict out of them.
@@ -1068,8 +1056,6 @@ def transform_dict_expr(builder: IRBuilder, expr: DictExpr) -> Value:
10681056
return builder.builder.make_dict(key_value_pairs, expr.line)
10691057

10701058

1071-
_static_dicts = {}
1072-
10731059
def transform_set_expr(builder: IRBuilder, expr: SetExpr) -> Value:
10741060
return _visit_display(
10751061
builder, expr.items, builder.new_set_op, set_add_op, set_update_op, expr.line, False

mypyc/lib-rt/CPy.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,8 @@ int CPyStatics_Initialize(PyObject **statics,
931931
const double *floats,
932932
const double *complex_numbers,
933933
const int *tuples,
934-
const int *frozensets);
934+
const int *frozensets,
935+
const int *dicts);
935936
PyObject *CPy_Super(PyObject *builtins, PyObject *self);
936937
PyObject *CPy_CallReverseOpMethod(PyObject *left, PyObject *right, const char *op,
937938
_Py_Identifier *method);

mypyc/lib-rt/misc_ops.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,8 @@ int CPyStatics_Initialize(PyObject **statics,
615615
const double *floats,
616616
const double *complex_numbers,
617617
const int *tuples,
618-
const int *frozensets) {
618+
const int *frozensets,
619+
const int *dicts) {
619620
PyObject **result = statics;
620621
// Start with some hard-coded values
621622
*result++ = Py_None;
@@ -733,6 +734,31 @@ int CPyStatics_Initialize(PyObject **statics,
733734
*result++ = obj;
734735
}
735736
}
737+
if (dicts) {
738+
int num = *dicts++;
739+
while (num-- > 0) {
740+
int num_items = *dicts++;
741+
PyObject *obj = PyDict_New();
742+
if (obj == NULL) {
743+
return -1;
744+
}
745+
for (int i = 0; i < num_items; i++) {
746+
PyObject *key = statics[*dicts++];
747+
PyObject *value = statics[*dicts++];
748+
Py_INCREF(key);
749+
Py_INCREF(value);
750+
if (PyDict_SetItem(obj, key, value) == -1) {
751+
Py_DECREF(key);
752+
Py_DECREF(value);
753+
Py_DECREF(obj);
754+
return -1;
755+
}
756+
Py_DECREF(key);
757+
Py_DECREF(value);
758+
}
759+
*result++ = obj;
760+
}
761+
}
736762
return 0;
737763
}
738764

mypyc/test-data/irbuild-basic.test

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,61 +1730,49 @@ L0:
17301730
r0 = (a, b, c)
17311731
return r0
17321732
def g():
1733-
r0, r1, r2 :: str
1734-
r3, r4, r5 :: object
1735-
r6, r7 :: dict
1736-
r8 :: str
1737-
r9 :: object
1738-
r10 :: tuple
1739-
r11 :: dict
1740-
r12 :: object
1741-
r13 :: tuple[int, int, int]
1742-
L0:
1743-
r0 = 'a'
1744-
r1 = 'b'
1745-
r2 = 'c'
1746-
r3 = object 1
1747-
r4 = object 2
1748-
r5 = object 3
1749-
r6 = CPyDict_Build(3, r0, r3, r1, r4, r2, r5)
1750-
r7 = __main__.globals :: static
1751-
r8 = 'f'
1752-
r9 = CPyDict_GetItem(r7, r8)
1753-
r10 = CPyTuple_LoadEmptyTupleConstant()
1754-
r11 = PyDict_Copy(r6)
1755-
r12 = PyObject_Call(r9, r10, r11)
1756-
r13 = unbox(tuple[int, int, int], r12)
1757-
return r13
1758-
def h():
1759-
r0, r1 :: str
1760-
r2, r3 :: object
1761-
r4, r5 :: dict
1762-
r6 :: str
1733+
r0, r1, r2 :: dict
1734+
r3 :: str
1735+
r4 :: object
1736+
r5 :: tuple
1737+
r6 :: dict
17631738
r7 :: object
1764-
r8 :: dict
1765-
r9 :: i32
1766-
r10 :: bit
1767-
r11 :: object
1768-
r12 :: tuple
1769-
r13 :: object
1770-
r14 :: tuple[int, int, int]
1739+
r8 :: tuple[int, int, int]
1740+
L0:
1741+
r0 = {'a': 1, 'b': 2, 'c': 3}
1742+
r1 = PyDict_Copy(r0)
1743+
r2 = __main__.globals :: static
1744+
r3 = 'f'
1745+
r4 = CPyDict_GetItem(r2, r3)
1746+
r5 = CPyTuple_LoadEmptyTupleConstant()
1747+
r6 = PyDict_Copy(r1)
1748+
r7 = PyObject_Call(r4, r5, r6)
1749+
r8 = unbox(tuple[int, int, int], r7)
1750+
return r8
1751+
def h():
1752+
r0, r1, r2 :: dict
1753+
r3 :: str
1754+
r4 :: object
1755+
r5 :: dict
1756+
r6 :: i32
1757+
r7 :: bit
1758+
r8 :: object
1759+
r9 :: tuple
1760+
r10 :: object
1761+
r11 :: tuple[int, int, int]
17711762
L0:
1772-
r0 = 'b'
1773-
r1 = 'c'
1774-
r2 = object 2
1775-
r3 = object 3
1776-
r4 = CPyDict_Build(2, r0, r2, r1, r3)
1777-
r5 = __main__.globals :: static
1778-
r6 = 'f'
1779-
r7 = CPyDict_GetItem(r5, r6)
1780-
r8 = PyDict_New()
1781-
r9 = CPyDict_UpdateInDisplay(r8, r4)
1782-
r10 = r9 >= 0 :: signed
1783-
r11 = object 1
1784-
r12 = PyTuple_Pack(1, r11)
1785-
r13 = PyObject_Call(r7, r12, r8)
1786-
r14 = unbox(tuple[int, int, int], r13)
1787-
return r14
1763+
r0 = {'b': 2, 'c': 3}
1764+
r1 = PyDict_Copy(r0)
1765+
r2 = __main__.globals :: static
1766+
r3 = 'f'
1767+
r4 = CPyDict_GetItem(r2, r3)
1768+
r5 = PyDict_New()
1769+
r6 = CPyDict_UpdateInDisplay(r5, r1)
1770+
r7 = r6 >= 0 :: signed
1771+
r8 = object 1
1772+
r9 = PyTuple_Pack(1, r8)
1773+
r10 = PyObject_Call(r4, r9, r5)
1774+
r11 = unbox(tuple[int, int, int], r10)
1775+
return r11
17881776

17891777
[case testFunctionCallWithDefaultArgs]
17901778
def f(x: int, y: int = 3, z: str = "test") -> None:

mypyc/test-data/irbuild-dict.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,18 @@ L2:
565565
L3:
566566
r7 = box(None, 1)
567567
return r7
568+
569+
[case testNestedDictLiteral]
570+
def f() -> None:
571+
d = {"a": {2: 3.4}}
572+
[out]
573+
def f():
574+
r0 :: str
575+
r1, r2, r3, d :: dict
576+
L0:
577+
r0 = 'a'
578+
r1 = {2: 3.4}
579+
r2 = PyDict_Copy(r1)
580+
r3 = CPyDict_Build(1, r0, r2)
581+
d = r3
582+
return 1

0 commit comments

Comments
 (0)