Skip to content

Commit d8cb849

Browse files
committed
Fix Result deep toObject when a parent is an Array (#4681).
1 parent 5817972 commit d8cb849

File tree

1 file changed

+56
-18
lines changed

1 file changed

+56
-18
lines changed

src.ts/abi/coders/abstract-coder.ts

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
defineProperties, concat, getBytesCopy, getNumber, hexlify,
44
toBeArray, toBigInt, toNumber,
55
assert, assertArgument
6+
/*, isError*/
67
} from "../../utils/index.js";
78

89
import type { BigNumberish, BytesLike } from "../../utils/index.js";
@@ -19,12 +20,44 @@ const passProperties = [ "then" ];
1920

2021
const _guard = { };
2122

23+
const resultNames: WeakMap<Result, ReadonlyArray<null | string>> = new WeakMap();
24+
25+
function getNames(result: Result): ReadonlyArray<null | string> {
26+
return resultNames.get(result)!;
27+
}
28+
function setNames(result: Result, names: ReadonlyArray<null | string>): void {
29+
resultNames.set(result, names);
30+
}
31+
2232
function throwError(name: string, error: Error): never {
2333
const wrapped = new Error(`deferred error during ABI decoding triggered accessing ${ name }`);
2434
(<any>wrapped).error = error;
2535
throw wrapped;
2636
}
2737

38+
function toObject(names: ReadonlyArray<null | string>, items: Result, deep?: boolean): Record<string, any> | Array<any> {
39+
if (names.indexOf(null) >= 0) {
40+
return items.map((item, index) => {
41+
if (item instanceof Result) {
42+
return toObject(getNames(item), item, deep);
43+
}
44+
return item;
45+
});
46+
}
47+
48+
return (<Array<string>>names).reduce((accum, name, index) => {
49+
let item = items.getValue(name);
50+
if (!(name in accum)) {
51+
if (deep && item instanceof Result) {
52+
item = toObject(getNames(item), item, deep);
53+
}
54+
accum[name] = item;
55+
}
56+
return accum;
57+
}, <Record<string, any>>{ });
58+
}
59+
60+
2861
/**
2962
* A [[Result]] is a sub-class of Array, which allows accessing any
3063
* of its values either positionally by its index or, if keys are
@@ -33,6 +66,9 @@ function throwError(name: string, error: Error): never {
3366
* @_docloc: api/abi
3467
*/
3568
export class Result extends Array<any> {
69+
// No longer used; but cannot be removed as it will remove the
70+
// #private field from the .d.ts which may break backwards
71+
// compatibility
3672
readonly #names: ReadonlyArray<null | string>;
3773

3874
[ K: string | number ]: any
@@ -73,21 +109,25 @@ export class Result extends Array<any> {
73109
}, <Map<string, number>>(new Map()));
74110

75111
// Remove any key thats not unique
76-
this.#names = Object.freeze(items.map((item, index) => {
112+
setNames(this, Object.freeze(items.map((item, index) => {
77113
const name = names[index];
78114
if (name != null && nameCounts.get(name) === 1) {
79115
return name;
80116
}
81117
return null;
82-
}));
118+
})));
119+
120+
// Dummy operations to prevent TypeScript from complaining
121+
this.#names = [ ];
122+
if (this.#names == null) { void(this.#names); }
83123

84124
if (!wrap) { return; }
85125

86126
// A wrapped Result is immutable
87127
Object.freeze(this);
88128

89129
// Proxy indices and names so we can trap deferred errors
90-
return new Proxy(this, {
130+
const proxy = new Proxy(this, {
91131
get: (target, prop, receiver) => {
92132
if (typeof(prop) === "string") {
93133

@@ -127,6 +167,7 @@ export class Result extends Array<any> {
127167
return Reflect.get(target, prop, receiver);
128168
}
129169
});
170+
setNames(proxy, getNames(this));
130171
}
131172

132173
/**
@@ -157,21 +198,14 @@ export class Result extends Array<any> {
157198
* any outstanding deferred errors.
158199
*/
159200
toObject(deep?: boolean): Record<string, any> {
160-
return this.#names.reduce((accum, name, index) => {
161-
assert(name != null, "value at index ${ index } unnamed", "UNSUPPORTED_OPERATION", {
201+
const names = getNames(this);
202+
return names.reduce((accum, name, index) => {
203+
204+
assert(name != null, `value at index ${ index } unnamed`, "UNSUPPORTED_OPERATION", {
162205
operation: "toObject()"
163206
});
164207

165-
// Add values for names that don't conflict
166-
if (!(name in accum)) {
167-
let child = this.getValue(name);
168-
if (deep && child instanceof Result) {
169-
child = child.toObject(deep);
170-
}
171-
accum[name] = child;
172-
}
173-
174-
return accum;
208+
return toObject(names, this, deep);
175209
}, <Record<string, any>>{});
176210
}
177211

@@ -192,10 +226,12 @@ export class Result extends Array<any> {
192226
}
193227
if (end > this.length) { end = this.length; }
194228

229+
const _names = getNames(this);
230+
195231
const result: Array<any> = [ ], names: Array<null | string> = [ ];
196232
for (let i = start; i < end; i++) {
197233
result.push(this[i]);
198-
names.push(this.#names[i]);
234+
names.push(_names[i]);
199235
}
200236

201237
return new Result(_guard, result, names);
@@ -205,6 +241,8 @@ export class Result extends Array<any> {
205241
* @_ignore
206242
*/
207243
filter(callback: (el: any, index: number, array: Result) => boolean, thisArg?: any): Result {
244+
const _names = getNames(this);
245+
208246
const result: Array<any> = [ ], names: Array<null | string> = [ ];
209247
for (let i = 0; i < this.length; i++) {
210248
const item = this[i];
@@ -214,7 +252,7 @@ export class Result extends Array<any> {
214252

215253
if (callback.call(thisArg, item, i, this)) {
216254
result.push(item);
217-
names.push(this.#names[i]);
255+
names.push(_names[i]);
218256
}
219257
}
220258

@@ -248,7 +286,7 @@ export class Result extends Array<any> {
248286
* accessible by name.
249287
*/
250288
getValue(name: string): any {
251-
const index = this.#names.indexOf(name);
289+
const index = getNames(this).indexOf(name);
252290
if (index === -1) { return undefined; }
253291

254292
const value = this[index];

0 commit comments

Comments
 (0)