diff --git a/src.ts/abi/coders/abstract-coder.ts b/src.ts/abi/coders/abstract-coder.ts index 981b2b80a1..3a62e66dd8 100644 --- a/src.ts/abi/coders/abstract-coder.ts +++ b/src.ts/abi/coders/abstract-coder.ts @@ -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"; @@ -19,12 +20,44 @@ const passProperties = [ "then" ]; const _guard = { }; +const resultNames: WeakMap> = new WeakMap(); + +function getNames(result: Result): ReadonlyArray { + return resultNames.get(result)!; +} +function setNames(result: Result, names: ReadonlyArray): void { + resultNames.set(result, names); +} + function throwError(name: string, error: Error): never { const wrapped = new Error(`deferred error during ABI decoding triggered accessing ${ name }`); (wrapped).error = error; throw wrapped; } +function toObject(names: ReadonlyArray, items: Result, deep?: boolean): Record | Array { + if (names.indexOf(null) >= 0) { + return items.map((item, index) => { + if (item instanceof Result) { + return toObject(getNames(item), item, deep); + } + return item; + }); + } + + return (>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; + }, >{ }); +} + + /** * A [[Result]] is a sub-class of Array, which allows accessing any * of its values either positionally by its index or, if keys are @@ -33,6 +66,9 @@ function throwError(name: string, error: Error): never { * @_docloc: api/abi */ export class Result extends Array { + // 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; [ K: string | number ]: any @@ -73,13 +109,17 @@ export class Result extends Array { }, >(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; } @@ -87,7 +127,7 @@ export class Result extends Array { 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") { @@ -127,6 +167,7 @@ export class Result extends Array { return Reflect.get(target, prop, receiver); } }); + setNames(proxy, getNames(this)); } /** @@ -157,21 +198,14 @@ export class Result extends Array { * any outstanding deferred errors. */ toObject(deep?: boolean): Record { - 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); }, >{}); } @@ -192,10 +226,12 @@ export class Result extends Array { } if (end > this.length) { end = this.length; } + const _names = getNames(this); + const result: Array = [ ], names: Array = [ ]; 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); @@ -205,6 +241,8 @@ export class Result extends Array { * @_ignore */ filter(callback: (el: any, index: number, array: Result) => boolean, thisArg?: any): Result { + const _names = getNames(this); + const result: Array = [ ], names: Array = [ ]; for (let i = 0; i < this.length; i++) { const item = this[i]; @@ -214,7 +252,7 @@ export class Result extends Array { if (callback.call(thisArg, item, i, this)) { result.push(item); - names.push(this.#names[i]); + names.push(_names[i]); } } @@ -248,7 +286,7 @@ export class Result extends Array { * 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];