1# Tests of Starlark 'dict'
2
3load("assert.star", "assert", "freeze")
4
5# literals
6assert.eq({}, {})
7assert.eq({"a": 1}, {"a": 1})
8assert.eq({"a": 1,}, {"a": 1})
9
10# truth
11assert.true({False: False})
12assert.true(not {})
13
14# dict + dict is no longer supported.
15assert.fails(lambda: {"a": 1} + {"b": 2}, 'unknown binary op: dict \\+ dict')
16
17# dict comprehension
18assert.eq({x: x*x for x in range(3)}, {0: 0, 1: 1, 2: 4})
19
20# dict.pop
21x6 = {"a": 1, "b": 2}
22assert.eq(x6.pop("a"), 1)
23assert.eq(str(x6), '{"b": 2}')
24assert.fails(lambda: x6.pop("c"), "pop: missing key")
25assert.eq(x6.pop("c", 3), 3)
26assert.eq(x6.pop("c", None), None) # default=None tests an edge case of UnpackArgs
27assert.eq(x6.pop("b"), 2)
28assert.eq(len(x6), 0)
29
30# dict.popitem
31x7 = {"a": 1, "b": 2}
32assert.eq([x7.popitem(), x7.popitem()], [("a", 1), ("b", 2)])
33assert.fails(x7.popitem, "empty dict")
34assert.eq(len(x7), 0)
35
36# dict.keys, dict.values
37x8 = {"a": 1, "b": 2}
38assert.eq(x8.keys(), ["a", "b"])
39assert.eq(x8.values(), [1, 2])
40
41# equality
42assert.eq({"a": 1, "b": 2}, {"a": 1, "b": 2})
43assert.eq({"a": 1, "b": 2,}, {"a": 1, "b": 2})
44assert.eq({"a": 1, "b": 2}, {"b": 2, "a": 1})
45
46# insertion order is preserved
47assert.eq(dict([("a", 0), ("b", 1), ("c", 2), ("b", 3)]).keys(), ["a", "b", "c"])
48assert.eq(dict([("b", 0), ("a", 1), ("b", 2), ("c", 3)]).keys(), ["b", "a", "c"])
49assert.eq(dict([("b", 0), ("a", 1), ("b", 2), ("c", 3)])["b"], 2)
50# ...even after rehashing (which currently occurs after key 'i'):
51small = dict([("a", 0), ("b", 1), ("c", 2)])
52small.update([("d", 4), ("e", 5), ("f", 6), ("g", 7), ("h", 8), ("i", 9), ("j", 10), ("k", 11)])
53assert.eq(small.keys(), ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"])
54
55# Duplicate keys are not permitted in dictionary expressions (see b/35698444).
56# (Nor in keyword args to function calls---checked by resolver.)
57assert.fails(lambda: {"aa": 1, "bb": 2, "cc": 3, "bb": 4}, 'duplicate key: "bb"')
58
59# Check that even with many positional args, keyword collisions are detected.
60assert.fails(lambda: dict({'b': 3}, a=4, **dict(a=5)), 'dict: duplicate keyword arg: "a"')
61assert.fails(lambda: dict({'a': 2, 'b': 3}, a=4, **dict(a=5)), 'dict: duplicate keyword arg: "a"')
62# positional/keyword arg key collisions are ok
63assert.eq(dict((['a', 2], ), a=4), {'a': 4})
64assert.eq(dict((['a', 2], ['a', 3]), a=4), {'a': 4})
65
66# index
67def setIndex(d, k, v):
68 d[k] = v
69
70x9 = {}
71assert.fails(lambda: x9["a"], 'key "a" not in dict')
72x9["a"] = 1
73assert.eq(x9["a"], 1)
74assert.eq(x9, {"a": 1})
75assert.fails(lambda: setIndex(x9, [], 2), 'unhashable type: list')
76freeze(x9)
77assert.fails(lambda: setIndex(x9, "a", 3), 'cannot insert into frozen hash table')
78
79x9a = {}
80x9a[1, 2] = 3 # unparenthesized tuple is allowed here
81assert.eq(x9a.keys()[0], (1, 2))
82
83# dict.get
84x10 = {"a": 1}
85assert.eq(x10.get("a"), 1)
86assert.eq(x10.get("b"), None)
87assert.eq(x10.get("a", 2), 1)
88assert.eq(x10.get("b", 2), 2)
89
90# dict.clear
91x11 = {"a": 1}
92assert.contains(x11, "a")
93assert.eq(x11["a"], 1)
94x11.clear()
95assert.fails(lambda: x11["a"], 'key "a" not in dict')
96assert.true("a" not in x11)
97freeze(x11)
98assert.fails(x11.clear, "cannot clear frozen hash table")
99
100# dict.setdefault
101x12 = {"a": 1}
102assert.eq(x12.setdefault("a"), 1)
103assert.eq(x12["a"], 1)
104assert.eq(x12.setdefault("b"), None)
105assert.eq(x12["b"], None)
106assert.eq(x12.setdefault("c", 2), 2)
107assert.eq(x12["c"], 2)
108assert.eq(x12.setdefault("c", 3), 2)
109assert.eq(x12["c"], 2)
110freeze(x12)
111assert.eq(x12.setdefault("a", 1), 1) # no change, no error
112assert.fails(lambda: x12.setdefault("d", 1), "cannot insert into frozen hash table")
113
114# dict.update
115x13 = {"a": 1}
116x13.update(a=2, b=3)
117assert.eq(x13, {"a": 2, "b": 3})
118x13.update([("b", 4), ("c", 5)])
119assert.eq(x13, {"a": 2, "b": 4, "c": 5})
120x13.update({"c": 6, "d": 7})
121assert.eq(x13, {"a": 2, "b": 4, "c": 6, "d": 7})
122freeze(x13)
123assert.fails(lambda: x13.update({"a": 8}), "cannot insert into frozen hash table")
124
125# dict as a sequence
126#
127# for loop
128x14 = {1:2, 3:4}
129def keys(dict):
130 keys = []
131 for k in dict: keys.append(k)
132 return keys
133assert.eq(keys(x14), [1, 3])
134#
135# comprehension
136assert.eq([x for x in x14], [1, 3])
137#
138# varargs
139def varargs(*args): return args
140x15 = {"one": 1}
141assert.eq(varargs(*x15), ("one",))
142
143# kwargs parameter does not alias the **kwargs dict
144def kwargs(**kwargs): return kwargs
145x16 = kwargs(**x15)
146assert.eq(x16, x15)
147x15["two"] = 2 # mutate
148assert.ne(x16, x15)
149
150# iterator invalidation
151def iterator1():
152 dict = {1:1, 2:1}
153 for k in dict:
154 dict[2*k] = dict[k]
155assert.fails(iterator1, "insert.*during iteration")
156
157def iterator2():
158 dict = {1:1, 2:1}
159 for k in dict:
160 dict.pop(k)
161assert.fails(iterator2, "delete.*during iteration")
162
163def iterator3():
164 def f(d):
165 d[3] = 3
166 dict = {1:1, 2:1}
167 _ = [f(dict) for x in dict]
168assert.fails(iterator3, "insert.*during iteration")
169
170# This assignment is not a modification-during-iteration:
171# the sequence x should be completely iterated before
172# the assignment occurs.
173def f():
174 x = {1:2, 2:4}
175 a, x[0] = x
176 assert.eq(a, 1)
177 assert.eq(x, {1: 2, 2: 4, 0: 2})
178f()
179
180# Regression test for a bug in hashtable.delete
181def test_delete():
182 d = {}
183
184 # delete tail first
185 d["one"] = 1
186 d["two"] = 2
187 assert.eq(str(d), '{"one": 1, "two": 2}')
188 d.pop("two")
189 assert.eq(str(d), '{"one": 1}')
190 d.pop("one")
191 assert.eq(str(d), '{}')
192
193 # delete head first
194 d["one"] = 1
195 d["two"] = 2
196 assert.eq(str(d), '{"one": 1, "two": 2}')
197 d.pop("one")
198 assert.eq(str(d), '{"two": 2}')
199 d.pop("two")
200 assert.eq(str(d), '{}')
201
202 # delete middle
203 d["one"] = 1
204 d["two"] = 2
205 d["three"] = 3
206 assert.eq(str(d), '{"one": 1, "two": 2, "three": 3}')
207 d.pop("two")
208 assert.eq(str(d), '{"one": 1, "three": 3}')
209 d.pop("three")
210 assert.eq(str(d), '{"one": 1}')
211 d.pop("one")
212 assert.eq(str(d), '{}')
213
214test_delete()
215
216# Regression test for github.com/google/starlark-go/issues/128.
217assert.fails(lambda: dict(None), 'got NoneType, want iterable')
218assert.fails(lambda: {}.update(None), 'got NoneType, want iterable')
219
220---
221# Verify position of an "unhashable key" error in a dict literal.
222
223_ = {
224 "one": 1,
225 ["two"]: 2, ### "unhashable type: list"
226 "three": 3,
227}
228
229---
230# Verify position of a "duplicate key" error in a dict literal.
231
232_ = {
233 "one": 1,
234 "one": 1, ### `duplicate key: "one"`
235 "three": 3,
236}
237
238---
239# Verify position of an "unhashable key" error in a dict comprehension.
240
241_ = {
242 k: v ### "unhashable type: list"
243 for k, v in [
244 ("one", 1),
245 (["two"], 2),
246 ("three", 3),
247 ]
248}
249
250
251---
252# dict | dict (union)
253
254load("assert.star", "assert", "freeze")
255
256empty_dict = dict()
257dict_with_a_b = dict(a=1, b=[1, 2])
258dict_with_b = dict(b=[1, 2])
259dict_with_other_b = dict(b=[3, 4])
260
261assert.eq(empty_dict | dict_with_a_b, dict_with_a_b)
262# Verify iteration order.
263assert.eq((empty_dict | dict_with_a_b).items(), dict_with_a_b.items())
264assert.eq(dict_with_a_b | empty_dict, dict_with_a_b)
265assert.eq((dict_with_a_b | empty_dict).items(), dict_with_a_b.items())
266assert.eq(dict_with_b | dict_with_a_b, dict_with_a_b)
267assert.eq((dict_with_b | dict_with_a_b).items(), dict(b=[1, 2], a=1).items())
268assert.eq(dict_with_a_b | dict_with_b, dict_with_a_b)
269assert.eq((dict_with_a_b | dict_with_b).items(), dict_with_a_b.items())
270assert.eq(dict_with_b | dict_with_other_b, dict_with_other_b)
271assert.eq((dict_with_b | dict_with_other_b).items(), dict_with_other_b.items())
272assert.eq(dict_with_other_b | dict_with_b, dict_with_b)
273assert.eq((dict_with_other_b | dict_with_b).items(), dict_with_b.items())
274
275assert.eq(empty_dict, dict())
276assert.eq(dict_with_b, dict(b=[1,2]))
277
278assert.fails(lambda: dict() | [], "unknown binary op: dict [|] list")
279
280# dict |= dict (in-place union)
281
282def test_dict_union_assignment():
283 x = dict()
284 saved = x
285 x |= {"a": 1}
286 x |= {"b": 2}
287 x |= {"c": "3", 7: 4}
288 x |= {"b": "5", "e": 6}
289 want = {"a": 1, "b": "5", "c": "3", 7: 4, "e": 6}
290 assert.eq(x, want)
291 assert.eq(x.items(), want.items())
292 assert.eq(saved, x) # they are aliases
293
294 a = {8: 1, "b": 2}
295 b = {"b": 1, "c": 6}
296 c = {"d": 7}
297 d = {(5, "a"): ("c", 8)}
298 orig_a, orig_c = a, c
299 a |= b
300 c |= a
301 c |= d
302 expected_2 = {"d": 7, 8: 1, "b": 1, "c": 6, (5, "a"): ("c", 8)}
303 assert.eq(c, expected_2)
304 assert.eq(c.items(), expected_2.items())
305 assert.eq(b, {"b": 1, "c": 6})
306
307 # aliasing:
308 assert.eq(a, orig_a)
309 assert.eq(c, orig_c)
310 a.clear()
311 c.clear()
312 assert.eq(a, orig_a)
313 assert.eq(c, orig_c)
314
315test_dict_union_assignment()
316
317def dict_union_assignment_type_mismatch():
318 some_dict = dict()
319 some_dict |= []
320
321assert.fails(dict_union_assignment_type_mismatch, "unknown binary op: dict [|] list")
View as plain text