Skip to content

Commit

Permalink
Fix Result deep toObject when a parent is an Array (#4681).
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo committed May 1, 2024
1 parent 5817972 commit d8cb849
Showing 1 changed file with 56 additions and 18 deletions.
74 changes: 56 additions & 18 deletions src.ts/abi/coders/abstract-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
defineProperties, concat, getBytesCopy, getNumber, hexlify,
toBeArray, toBigInt, toNumber,
assert, assertArgument
/*, isError*/
} from "../../utils/index.js";

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

const _guard = { };

const resultNames: WeakMap<Result, ReadonlyArray<null | string>> = new WeakMap();

function getNames(result: Result): ReadonlyArray<null | string> {
return resultNames.get(result)!;
}
function setNames(result: Result, names: ReadonlyArray<null | string>): void {
resultNames.set(result, names);
}

function throwError(name: string, error: Error): never {
const wrapped = new Error(`deferred error during ABI decoding triggered accessing ${ name }`);
(<any>wrapped).error = error;
throw wrapped;
}

function toObject(names: ReadonlyArray<null | string>, items: Result, deep?: boolean): Record<string, any> | Array<any> {
if (names.indexOf(null) >= 0) {
return items.map((item, index) => {
if (item instanceof Result) {
return toObject(getNames(item), item, deep);
}
return item;
});
}

return (<Array<string>>names).reduce((accum, name, index) => {
let item = items.getValue(name);
if (!(name in accum)) {
if (deep && item instanceof Result) {
item = toObject(getNames(item), item, deep);
}
accum[name] = item;
}
return accum;
}, <Record<string, any>>{ });
}


/**
* A [[Result]] is a sub-class of Array, which allows accessing any
* of its values either positionally by its index or, if keys are
Expand All @@ -33,6 +66,9 @@ function throwError(name: string, error: Error): never {
* @_docloc: api/abi
*/
export class Result extends Array<any> {
// No longer used; but cannot be removed as it will remove the
// #private field from the .d.ts which may break backwards
// compatibility
readonly #names: ReadonlyArray<null | string>;

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

// Remove any key thats not unique
this.#names = Object.freeze(items.map((item, index) => {
setNames(this, Object.freeze(items.map((item, index) => {
const name = names[index];
if (name != null && nameCounts.get(name) === 1) {
return name;
}
return null;
}));
})));

// Dummy operations to prevent TypeScript from complaining
this.#names = [ ];
if (this.#names == null) { void(this.#names); }

if (!wrap) { return; }

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

// Proxy indices and names so we can trap deferred errors
return new Proxy(this, {
const proxy = new Proxy(this, {
get: (target, prop, receiver) => {
if (typeof(prop) === "string") {

Expand Down Expand Up @@ -127,6 +167,7 @@ export class Result extends Array<any> {
return Reflect.get(target, prop, receiver);
}
});
setNames(proxy, getNames(this));
}

/**
Expand Down Expand Up @@ -157,21 +198,14 @@ export class Result extends Array<any> {
* any outstanding deferred errors.
*/
toObject(deep?: boolean): Record<string, any> {
return this.#names.reduce((accum, name, index) => {
assert(name != null, "value at index ${ index } unnamed", "UNSUPPORTED_OPERATION", {
const names = getNames(this);
return names.reduce((accum, name, index) => {

assert(name != null, `value at index ${ index } unnamed`, "UNSUPPORTED_OPERATION", {
operation: "toObject()"
});

// Add values for names that don't conflict
if (!(name in accum)) {
let child = this.getValue(name);
if (deep && child instanceof Result) {
child = child.toObject(deep);
}
accum[name] = child;
}

return accum;
return toObject(names, this, deep);
}, <Record<string, any>>{});
}

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

const _names = getNames(this);

const result: Array<any> = [ ], names: Array<null | string> = [ ];
for (let i = start; i < end; i++) {
result.push(this[i]);
names.push(this.#names[i]);
names.push(_names[i]);
}

return new Result(_guard, result, names);
Expand All @@ -205,6 +241,8 @@ export class Result extends Array<any> {
* @_ignore
*/
filter(callback: (el: any, index: number, array: Result) => boolean, thisArg?: any): Result {
const _names = getNames(this);

const result: Array<any> = [ ], names: Array<null | string> = [ ];
for (let i = 0; i < this.length; i++) {
const item = this[i];
Expand All @@ -214,7 +252,7 @@ export class Result extends Array<any> {

if (callback.call(thisArg, item, i, this)) {
result.push(item);
names.push(this.#names[i]);
names.push(_names[i]);
}
}

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

const value = this[index];
Expand Down

0 comments on commit d8cb849

Please sign in to comment.