Skip to content

Commit 756265a

Browse files
fix: preserve shared-reference identity for Map keys in uneval (#164)
* fix: preserve shared-reference identity for Map keys in uneval * test: simplify Map-key identity assertions Materialize each sub-map's keys once instead of spreading inline per assertion. Keeps all six slot-checks; no behavior change. --------- Co-authored-by: Elliott Johnson <hello+git@ell.iott.dev>
1 parent 8c0db06 commit 756265a

3 files changed

Lines changed: 67 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'devalue': patch
3+
---
4+
5+
fix: preserve shared-reference identity for Map keys in `uneval`

‎src/uneval.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export function uneval(value, replacer) {
7979
case 'Map':
8080
for (const [key, value] of thing) {
8181
keys.push(`.get(${is_primitive(key) ? stringify_primitive(key) : '...'})`);
82+
walk(key);
8283
walk(value);
8384
keys.pop();
8485
}

‎test/index.test.js‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ const fixtures = {
727727
js: '(function(a,b){a=new DataView(b);return [a,a,b]}({},new Uint8Array([0,1,2,3,4,5,6,7,8,9]).buffer))',
728728
json: '[[1,1,2],["DataView",2],["ArrayBuffer","AAECAwQFBgcICQ=="]]'
729729
},
730+
730731
{
731732
name: 'DataView subview (repetition)',
732733
value: (() => {
@@ -749,6 +750,66 @@ const fixtures = {
749750
assert.is(a, b);
750751
assert.ok(a instanceof Temporal.Instant);
751752
}
753+
},
754+
755+
{
756+
name: 'Map key (repetition)',
757+
value: (() => {
758+
const shared = { id: 1 };
759+
return [shared, new Map([[shared, 'v']])];
760+
})(),
761+
js: '(function(a){a.id=1;return [a,new Map([[a,"v"]])]}({}))',
762+
json: '[[1,3],{"id":2},1,["Map",1,4],"v"]',
763+
validate: ([obj, map]) => assert.is([...map.keys()][0], obj)
764+
},
765+
766+
{
767+
name: 'Map keys (interlinked)',
768+
value: (() => {
769+
const node1 = { id: 1 };
770+
const node2 = { id: 2 };
771+
const node3 = { id: 3 };
772+
return new Map([
773+
[
774+
node1,
775+
new Map([
776+
[node2, 1],
777+
[node3, 1]
778+
])
779+
],
780+
[
781+
node2,
782+
new Map([
783+
[node1, 1],
784+
[node3, 1]
785+
])
786+
],
787+
[
788+
node3,
789+
new Map([
790+
[node1, 1],
791+
[node2, 1]
792+
])
793+
]
794+
]);
795+
})(),
796+
js: '(function(a,b,c){a.id=1;b.id=2;c.id=3;return new Map([[a,new Map([[b,1],[c,1]])],[b,new Map([[a,1],[c,1]])],[c,new Map([[a,1],[b,1]])]])}({},{},{}))',
797+
json: '[["Map",1,3,4,8,6,9],{"id":2},1,["Map",4,2,6,2],{"id":5},2,{"id":7},3,["Map",1,2,6,2],["Map",1,2,4,2]]',
798+
validate: (map) => {
799+
const [node1, node2, node3] = map.keys();
800+
const from1 = [...map.get(node1).keys()];
801+
const from2 = [...map.get(node2).keys()];
802+
const from3 = [...map.get(node3).keys()];
803+
804+
// each node appears as a key in the two sibling sub-maps;
805+
// the same object identity must be preserved everywhere
806+
assert.is(from2[0], node1);
807+
assert.is(from3[0], node1);
808+
assert.is(from1[0], node2);
809+
assert.is(from3[1], node2);
810+
assert.is(from1[1], node3);
811+
assert.is(from2[1], node3);
812+
}
752813
}
753814
],
754815

0 commit comments

Comments
 (0)