D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 7512 - Associative arrays implementation loses const and immutable in AA.get() and AA[key]
Summary: Associative arrays implementation loses const and immutable in AA.get() and A...
Status: RESOLVED FIXED
Alias: None
Product: D
Classification: Unclassified
Component: druntime (show other issues)
Version: D2
Hardware: All Linux
: P2 major
Assignee: No Owner
URL:
Keywords:
: 4337 (view as issue list)
Depends on:
Blocks:
 
Reported: 2012-02-15 11:56 UTC by hsteoh
Modified: 2012-03-23 04:40 UTC (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description hsteoh 2012-02-15 11:56:43 UTC
Test case:

void main() {                   
        int[dstring] map = ["abc"d: 123];
        foreach (key, val; map) {
                assert(map[key] == val); // throws RangeError ?!
        }
}


Changing dstring to string fixes the problem.
Comment 1 hsteoh 2012-02-27 10:29:15 UTC
Update: wstring keys fail the foreach test too:

int[wstring] map = ["abc"w: 1, "def"w: 2];
foreach (key, val; map) {
    assert((key in map) !is null); // throws AssertError
}
Comment 2 hsteoh 2012-02-27 12:42:04 UTC
OK, I've narrowed down this bug to using an AA literal for when the key is a non-string array. For some strange reason, the AA literal *appears* to correctly initialize the AA, as far as foreach can tell:

// CODE:
const int[] key1 = [1,2,3];
const int[] key2 = [1,2,4];
int[int[]] a5 = [
    key1 : 3,
    key2 : 4
];
foreach (key, val; a5) {
    writefln("%s -> %s", key, val);
}

// OUTPUT:
[1, 2, 3] -> 3
[1, 2, 4] -> 4

Seems to be correct, however:

// CODE:
foreach (key, val; a5) {
    assert(a5[key]==val);
}

// OUTPUT:
core.exception.AssertError@aatest(15): Assertion failure

This is the failing case. However, if we *don't* use an AA literal:

// CODE:
const int[] key1 = [1,2,3];
const int[] key2 = [1,2,4];
int[int[]] a5;
a5[key1] = 3;
a5[key2] = 4;
foreach (key, val; a5) {
    assert(a5[key]==val);
}


Then there is no assertion failure.

Conclusion: something screwy is going on when an AA literal is used. The AA appears to be initialized correctly, and foreach appears to return the correct key/value pairs, *but* attempting to do lookups with the keys fails for some unknown reason.
Comment 3 hsteoh 2012-02-27 13:57:09 UTC
Even more weirdness:

int[int[]] a7;
const int[] key1 = [1,3,5];
const int[] key2 = [2,4,6];
a7[key1] = 135;
a7[key2] = 246;

int[int[]] a8 = [key1: 135, key2: 246];
assert(a7 == a8); // OK!
assert(a7[key1] == 135); // OK!
assert(a8[key1] == 135); // throws RangeError (?!)
Comment 4 hsteoh 2012-02-27 14:01:53 UTC
Direct proof that something really weird is going on:

const int[] key1 = [1,3,5];
const int[] key2 = [2,4,6];
int[int[]] a8 = [ key1: 135, key2: 246 ];
a8[key1] = 135; // you'd think this should have no effect
foreach (key, val; a8) {
    // But you'd be wrong...
    writeln("%s -> %s", key, val);
}

// OUTPUT:
[2, 4, 6] -> 246
[1, 3, 5] -> 135
[1, 3, 5] -> 135

(Note the duplicated identical key->value pair.)

So the key/value pair initialized by the AA literal is somehow "different" from the key/value pair assigned by "a8[key]=value;".
Comment 5 hsteoh 2012-02-27 17:39:10 UTC
Found cause of problem:

const int[] key = [1,2,3,4];
int[int[]] map1 = [ key: 1234 ];

// map1's internal hashtable has keyti pointing to typeid(const(int)[])

int[int[]] map2;
map2[key] = 1234;

// map2's internal hashtable has keyti pointing to typeid(int[])

This causes 'key' to map to a different hash value in map1 than in map2. So far, no real problem yet. However, this:

map1[key] = 1234;

computes the hash value of 'key' using typeid(int[]).getHash(), NOT typeid(const(int)[]).getHash(), causing a duplicate entry to appear in map1. That is, the hash value of 'key' in the above line is exactly the same as the hash value of key in "map2[key] = 1234". In fact, it seems that AA.get() and AA[...] always uses the non-const typeinfo to compute the hash value, so it will never find the keys created by the AA literal. The only time the entries in the AA literal are actually visible is via foreach.
Comment 6 hsteoh 2012-02-27 20:12:51 UTC
Apparently AA.get() and AA[key] *always* computes the hash value based on the unqualified type, whereas AA literals use the const type which computes a different hash value. For example:

int[dstring] map = ["abc"d: 1];
assert(map["abc"d]==1); // throws range error
assert(("abc"d in map) !is null); // assertion fails
map["abc"d] = 1; // creates duplicated entry (foreach finds "abc"d:1 twice)
Comment 8 hsteoh 2012-02-29 10:19:39 UTC
Hmm, apparently Walter's hash fix wasn't enough. Something else is still going wrong with the hash value somewhere.
Comment 9 hsteoh 2012-02-29 10:48:36 UTC
Actually nevermind that. We forgot to recompile phobos after recompiling druntime, so the bad code was still lingering in there somewhere. Recompiling phobos with the new runtime fixed everything.
Comment 10 yebblies 2012-03-23 04:40:54 UTC
*** Issue 4337 has been marked as a duplicate of this issue. ***