void main() { testKeysValues1(); testKeysValues2(); testGet1(); testGet2(); testRequire1(); testRequire2(); testRequire3(); testUpdate1(); testUpdate2(); testByKey1(); testByKey2(); testByKey3(); testByKey4(); issue5842(); issue5842Expanded(); issue5925(); issue8583(); issue9052(); issue9119(); issue9852(); issue10381(); issue10720(); issue11761(); issue13078(); issue14104(); issue14626(); issue15290(); issue15367(); issue16974(); issue18071(); testIterationWithConst(); testStructArrayKey(); miscTests1(); miscTests2(); testRemove(); testZeroSizedValue(); testTombstonePurging(); testClear(); } void testKeysValues1() { static struct T { byte b; static size_t count; this(this) { ++count; } } T[int] aa; T t; aa[0] = t; aa[1] = t; assert(T.count == 2); auto vals = aa.values; assert(vals.length == 2); assert(T.count == 4); T.count = 0; int[T] aa2; aa2[t] = 0; assert(T.count == 1); aa2[t] = 1; assert(T.count == 1); auto keys = aa2.keys; assert(keys.length == 1); assert(T.count == 2); } void testKeysValues2() nothrow pure { int[string] aa; assert(aa.keys.length == 0); assert(aa.values.length == 0); aa["hello"] = 3; assert(aa["hello"] == 3); aa["hello"]++; assert(aa["hello"] == 4); assert(aa.length == 1); string[] keys = aa.keys; assert(keys.length == 1); assert(keys[0] == "hello"); int[] values = aa.values; assert(values.length == 1); assert(values[0] == 4); aa.rehash; assert(aa.length == 1); assert(aa["hello"] == 4); aa["foo"] = 1; aa["bar"] = 2; aa["batz"] = 3; assert(aa.keys.length == 4); assert(aa.values.length == 4); foreach (a; aa.keys) { assert(a.length != 0); assert(a.ptr != null); } foreach (v; aa.values) { assert(v != 0); } } void testGet1() @safe { int[string] aa; int a; foreach (val; aa.byKeyValue) { ++aa[val.key]; a = val.value; } } void testGet2() { static class T { static size_t count; this() { ++count; } } T[string] aa; auto a = new T; aa["foo"] = a; assert(T.count == 1); auto b = aa.get("foo", new T); assert(T.count == 1); assert(b is a); auto c = aa.get("bar", new T); assert(T.count == 2); assert(c !is a); //Obviously get doesn't add. assert("bar" !in aa); } void testRequire1() { static class T { static size_t count; this() { ++count; } } T[string] aa; auto a = new T; aa["foo"] = a; assert(T.count == 1); auto b = aa.require("foo", new T); assert(T.count == 1); assert(b is a); auto c = aa.require("bar", null); assert(T.count == 1); assert(c is null); assert("bar" in aa); auto d = aa.require("bar", new T); assert(d is null); auto e = aa.require("baz", new T); assert(T.count == 2); assert(e !is a); assert("baz" in aa); bool created = false; auto f = aa.require("qux", { created = true; return new T; }()); assert(created == true); T g; auto h = aa.require("qux", { g = new T; return g; }()); assert(g !is h); } void testRequire2() { static struct S { int value; } S[string] aa; aa.require("foo").value = 1; assert(aa == ["foo" : S(1)]); aa["bar"] = S(2); auto a = aa.require("bar", S(3)); assert(a == S(2)); auto b = aa["bar"]; assert(b == S(2)); S* c = &aa.require("baz", S(4)); assert(c is &aa["baz"]); assert(*c == S(4)); assert("baz" in aa); auto d = aa["baz"]; assert(d == S(4)); } void testRequire3() pure { string[string] aa; auto a = aa.require("foo", "bar"); assert("foo" in aa); } void testUpdate1() { static class C {} C[string] aa; C orig = new C; aa["foo"] = orig; C newer; C older; void test(string key) { aa.update(key, { newer = new C; return newer; }, (ref C c) { older = c; newer = new C; return newer; }); } test("foo"); assert(older is orig); assert(newer is aa["foo"]); test("bar"); assert(newer is aa["bar"]); } void testUpdate2() { static class C {} C[string] aa; auto created = false; auto updated = false; class Creator { C opCall() { created = true; return new C(); } } class Updater { C opCall(ref C) { updated = true; return new C(); } } aa.update("foo", new Creator, new Updater); assert(created); aa.update("foo", new Creator, new Updater); assert(updated); } void testByKey1() { static assert(!__traits(compiles, () @safe { struct BadValue { int x; this(this) @safe { *(cast(ubyte*)(null) + 100000) = 5; } // not @safe alias x this; } BadValue[int] aa; () @safe { auto x = aa.byKey.front; } (); } )); } void testByKey2() nothrow pure { int[int] a; foreach (i; a.byKey) { assert(false); } foreach (i; a.byValue) { assert(false); } } void testByKey3() /*nothrow*/ pure { auto a = [ 1:"one", 2:"two", 3:"three" ]; auto b = a.dup; assert(b == [ 1:"one", 2:"two", 3:"three" ]); int[] c; foreach (k; a.byKey) { c ~= k; } assert(c.length == 3); assert(c[0] == 1 || c[1] == 1 || c[2] == 1); assert(c[0] == 2 || c[1] == 2 || c[2] == 2); assert(c[0] == 3 || c[1] == 3 || c[2] == 3); } void testByKey4() nothrow pure { string[] keys = ["a", "b", "c", "d", "e", "f"]; // Test forward range capabilities of byKey { int[string] aa; foreach (key; keys) aa[key] = 0; auto keyRange = aa.byKey(); auto savedKeyRange = keyRange.save; // Consume key range once size_t keyCount = 0; while (!keyRange.empty) { aa[keyRange.front]++; keyCount++; keyRange.popFront(); } foreach (key; keys) { assert(aa[key] == 1); } assert(keyCount == keys.length); // Verify it's possible to iterate the range the second time keyCount = 0; while (!savedKeyRange.empty) { aa[savedKeyRange.front]++; keyCount++; savedKeyRange.popFront(); } foreach (key; keys) { assert(aa[key] == 2); } assert(keyCount == keys.length); } // Test forward range capabilities of byValue { size_t[string] aa; foreach (i; 0 .. keys.length) { aa[keys[i]] = i; } auto valRange = aa.byValue(); auto savedValRange = valRange.save; // Consume value range once int[] hasSeen; hasSeen.length = keys.length; while (!valRange.empty) { assert(hasSeen[valRange.front] == 0); hasSeen[valRange.front]++; valRange.popFront(); } foreach (sawValue; hasSeen) { assert(sawValue == 1); } // Verify it's possible to iterate the range the second time hasSeen = null; hasSeen.length = keys.length; while (!savedValRange.empty) { assert(!hasSeen[savedValRange.front]); hasSeen[savedValRange.front] = true; savedValRange.popFront(); } foreach (sawValue; hasSeen) { assert(sawValue); } } } void issue5842() pure nothrow { string[string] test = null; test["test1"] = "test1"; test.remove("test1"); test.rehash; test["test3"] = "test3"; // causes divide by zero if rehash broke the AA } /// expanded test for 5842: increase AA size past the point where the AA /// stops using binit, in order to test another code path in rehash. void issue5842Expanded() pure nothrow { int[int] aa; foreach (int i; 0 .. 32) aa[i] = i; foreach (int i; 0 .. 32) aa.remove(i); aa.rehash; aa[1] = 1; } void issue5925() nothrow pure { const a = [4:0]; const b = [4:0]; assert(a == b); } /// test for bug 8583: ensure Slot and aaA are on the same page wrt value alignment void issue8583() nothrow pure { string[byte] aa0 = [0: "zero"]; string[uint[3]] aa1 = [[1,2,3]: "onetwothree"]; ushort[uint[3]] aa2 = [[9,8,7]: 987]; ushort[uint[4]] aa3 = [[1,2,3,4]: 1234]; string[uint[5]] aa4 = [[1,2,3,4,5]: "onetwothreefourfive"]; assert(aa0.byValue.front == "zero"); assert(aa1.byValue.front == "onetwothree"); assert(aa2.byValue.front == 987); assert(aa3.byValue.front == 1234); assert(aa4.byValue.front == "onetwothreefourfive"); } void issue9052() nothrow pure { static struct Json { Json[string] aa; void opAssign(Json) {} size_t length() const { return aa.length; } // This length() instantiates AssociativeArray!(string, const(Json)) to call AA.length(), and // inside ref Slot opAssign(Slot p); (which is automatically generated by compiler in Slot), // this.value = p.value would actually fail, because both side types of the assignment // are const(Json). } } void issue9119() { int[string] aa; assert(aa.byKeyValue.empty); aa["a"] = 1; aa["b"] = 2; aa["c"] = 3; auto pairs = aa.byKeyValue; auto savedPairs = pairs.save; size_t count = 0; while (!pairs.empty) { assert(pairs.front.key in aa); assert(pairs.front.value == aa[pairs.front.key]); count++; pairs.popFront(); } assert(count == aa.length); // Verify that saved range can iterate over the AA again count = 0; while (!savedPairs.empty) { assert(savedPairs.front.key in aa); assert(savedPairs.front.value == aa[savedPairs.front.key]); count++; savedPairs.popFront(); } assert(count == aa.length); } void issue9852() nothrow pure { // Original test case (revised, original assert was wrong) int[string] a; a["foo"] = 0; a.remove("foo"); assert(a == null); // should not crash int[string] b; assert(b is null); assert(a == b); // should not deref null assert(b == a); // ditto int[string] c; c["a"] = 1; assert(a != c); // comparison with empty non-null AA assert(c != a); assert(b != c); // comparison with null AA assert(c != b); } void issue10381() { alias II = int[int]; II aa1 = [0 : 1]; II aa2 = [0 : 1]; II aa3 = [0 : 2]; assert(aa1 == aa2); // Passes assert(typeid(II).equals(&aa1, &aa2)); assert(!typeid(II).equals(&aa1, &aa3)); } void issue10720() nothrow pure { static struct NC { @disable this(this) { } } NC[string] aa; static assert(!is(aa.nonExistingField)); } /// bug 11761: test forward range functionality void issue11761() pure nothrow { auto aa = ["a": 1]; void testFwdRange(R, T)(R fwdRange, T testValue) { assert(!fwdRange.empty); assert(fwdRange.front == testValue); static assert(is(typeof(fwdRange.save) == typeof(fwdRange))); auto saved = fwdRange.save; fwdRange.popFront(); assert(fwdRange.empty); assert(!saved.empty); assert(saved.front == testValue); saved.popFront(); assert(saved.empty); } testFwdRange(aa.byKey, "a"); testFwdRange(aa.byValue, 1); //testFwdRange(aa.byPair, tuple("a", 1)); } void issue13078() nothrow pure { shared string[][string] map; map.rehash; } void issue14104() { import core.stdc.stdio; alias K = const(ubyte)*; size_t[K] aa; immutable key = cast(K)(cast(size_t) uint.max + 1); aa[key] = 12; assert(key in aa); } void issue14626() { static struct S { string[string] aa; inout(string) key() inout { return aa.byKey().front; } inout(string) val() inout { return aa.byValue().front; } auto keyval() inout { return aa.byKeyValue().front; } } S s = S(["a":"b"]); assert(s.key() == "a"); assert(s.val() == "b"); assert(s.keyval().key == "a"); assert(s.keyval().value == "b"); void testInoutKeyVal(inout(string) key) { inout(string)[typeof(key)] aa; foreach (i; aa.byKey()) {} foreach (i; aa.byValue()) {} foreach (i; aa.byKeyValue()) {} } const int[int] caa; static assert(is(typeof(caa.byValue().front) == const int)); } /// test duplicated keys in AA literal /// https://issues.dlang.org/show_bug.cgi?id=15290 void issue15290() { string[int] aa = [ 0: "a", 0: "b" ]; assert(aa.length == 1); assert(aa.keys == [ 0 ]); } void issue15367() { void f1() {} void f2() {} // TypeInfo_Delegate.getHash int[void delegate()] aa; assert(aa.length == 0); aa[&f1] = 1; assert(aa.length == 1); aa[&f1] = 1; assert(aa.length == 1); auto a1 = [&f2, &f1]; auto a2 = [&f2, &f1]; // TypeInfo_Delegate.equals for (auto i = 0; i < 2; i++) assert(a1[i] == a2[i]); assert(a1 == a2); // TypeInfo_Delegate.compare for (auto i = 0; i < 2; i++) assert(a1[i] <= a2[i]); assert(a1 <= a2); } /// test AA as key /// https://issues.dlang.org/show_bug.cgi?id=16974 void issue16974() { int[int] a = [1 : 2], a2 = [1 : 2]; assert([a : 3] == [a : 3]); assert([a : 3] == [a2 : 3]); assert(typeid(a).getHash(&a) == typeid(a).getHash(&a)); assert(typeid(a).getHash(&a) == typeid(a).getHash(&a2)); } /// test safety for alias-this'd AA that have unsafe opCast /// https://issues.dlang.org/show_bug.cgi?id=18071 void issue18071() { static struct Foo { int[int] aa; auto opCast() pure nothrow @nogc { *cast(uint*)0xdeadbeef = 0xcafebabe;// unsafe return null; } alias aa this; } Foo f; () @safe { assert(f.byKey.empty); }(); } /// Verify iteration with const. void testIterationWithConst() { auto aa = [1:2, 3:4]; foreach (const t; aa.byKeyValue) { auto k = t.key; auto v = t.value; } } void testStructArrayKey() @safe { struct S { int i; const @safe nothrow: hash_t toHash() { return 0; } bool opEquals(const S) { return true; } int opCmp(const S) { return 0; } } int[S[]] aa = [[S(11)] : 13]; assert(aa[[S(12)]] == 13); } void miscTests1() pure nothrow { string[int] key1 = [1 : "true", 2 : "false"]; string[int] key2 = [1 : "false", 2 : "true"]; string[int] key3; // AA lits create a larger hashtable int[string[int]] aa1 = [key1 : 100, key2 : 200, key3 : 300]; // Ensure consistent hash values are computed for key1 assert((key1 in aa1) !is null); // Manually assigning to an empty AA creates a smaller hashtable int[string[int]] aa2; aa2[key1] = 100; aa2[key2] = 200; aa2[key3] = 300; assert(aa1 == aa2); // Ensure binary-independence of equal hash keys string[int] key2a; key2a[1] = "false"; key2a[2] = "true"; assert(aa1[key2a] == 200); } void miscTests2() { int[int] aa; foreach (k, v; aa) assert(false); foreach (v; aa) assert(false); assert(aa.byKey.empty); assert(aa.byValue.empty); assert(aa.byKeyValue.empty); size_t n; aa = [0 : 3, 1 : 4, 2 : 5]; foreach (k, v; aa) { n += k; assert(k >= 0 && k < 3); assert(v >= 3 && v < 6); } assert(n == 3); n = 0; foreach (v; aa) { n += v; assert(v >= 3 && v < 6); } assert(n == 12); n = 0; foreach (k, v; aa) { ++n; break; } assert(n == 1); n = 0; foreach (v; aa) { ++n; break; } assert(n == 1); } void testRemove() { int[int] aa; assert(!aa.remove(0)); aa = [0 : 1]; assert(aa.remove(0)); assert(!aa.remove(0)); aa[1] = 2; assert(!aa.remove(0)); assert(aa.remove(1)); assert(aa.length == 0); assert(aa.byKey.empty); } /// test zero sized value (hashset) void testZeroSizedValue() { alias V = void[0]; auto aa = [0 : V.init]; assert(aa.length == 1); assert(aa.byKey.front == 0); assert(aa.byValue.front == V.init); aa[1] = V.init; assert(aa.length == 2); aa[0] = V.init; assert(aa.length == 2); assert(aa.remove(0)); aa[0] = V.init; assert(aa.length == 2); assert(aa == [0 : V.init, 1 : V.init]); } void testTombstonePurging() { int[int] aa; foreach (i; 0 .. 6) aa[i] = i; foreach (i; 0 .. 6) assert(aa.remove(i)); foreach (i; 6 .. 10) aa[i] = i; assert(aa.length == 4); foreach (i; 6 .. 10) assert(i in aa); } void testClear() { int[int] aa; assert(aa.length == 0); foreach (i; 0 .. 100) aa[i] = i * 2; assert(aa.length == 100); auto aa2 = aa; assert(aa2.length == 100); aa.clear(); assert(aa.length == 0); assert(aa2.length == 0); aa2[5] = 6; assert(aa.length == 1); assert(aa[5] == 6); }