diff --git a/src/foundry/common/utils/collection.d.mts b/src/foundry/common/utils/collection.d.mts index 8d4181aa6..a2cfa55d0 100644 --- a/src/foundry/common/utils/collection.d.mts +++ b/src/foundry/common/utils/collection.d.mts @@ -1,31 +1,17 @@ -interface MapReplacementMembers { - set(key: K, value: V): this; +// This class exists make it as sound as possible to override these parts of the class and make them +// completely unrelated. It's done this way specifically to avoid situations like +declare class LenientMap extends Map { + [Symbol.iterator](): any; + forEach(...args: any[]): any; } -type PatchedMap = Omit, "forEach" | typeof Symbol.iterator | "get" | "set"> & - MapReplacementMembers; - -declare namespace PatchedMap { - type Any = PatchedMap; -} - -interface PatchedMapConstructor { - new (): PatchedMap.Any; - new (entries?: readonly (readonly [K, V])[] | null): PatchedMap; - new (iterable: Iterable): PatchedMap; - readonly [Symbol.species]: PatchedMapConstructor; - readonly prototype: PatchedMap.Any; -} - -declare const Map: PatchedMapConstructor; - /** * A reusable storage concept which blends the functionality of an Array with the efficient key-based lookup of a Map. * This concept is reused throughout Foundry VTT where a collection of uniquely identified elements is required. * @typeParam T - The type of the objects contained in the Collection */ -declare class Collection extends Map { - constructor(entries?: readonly (readonly [string, V])[] | null); +declare class Collection extends LenientMap { + constructor(entries?: Iterable | null); /** * When iterating over a Collection, we should iterate over its values instead of over its entries diff --git a/tests/foundry/common/utils/collection.mjs.test-d.ts b/tests/foundry/common/utils/collection.mjs.test-d.ts index 8733b3bbd..1511cb2c0 100644 --- a/tests/foundry/common/utils/collection.mjs.test-d.ts +++ b/tests/foundry/common/utils/collection.mjs.test-d.ts @@ -15,8 +15,25 @@ function isString(e: string | null): e is string { } const cn = new Collection(); -expectTypeOf(cn.filter((each) => typeof each === "string")).toEqualTypeOf>(); -expectTypeOf(cn.find((each) => typeof each === "string")).toEqualTypeOf(); +expectTypeOf(cn.filter((each) => typeof each === "string")).toEqualTypeOf>(); +expectTypeOf(cn.find((each) => typeof each === "string")).toEqualTypeOf(); expectTypeOf(cn.filter(isString)).toEqualTypeOf(); expectTypeOf(cn.find(isString)).toEqualTypeOf(); + +// This is a regression test for the error: +// Class '...' defines instance member property '...', but extended class '...' defines it as instance member function. +// This occurred because of how `Map` was patched to allow the unsound subclassing of `Collection`. +class CustomCollection extends Collection { + override clear(): void {} + + override delete(_key: string): boolean { + return true; + } +} + +declare const customCollection: CustomCollection; + +if (customCollection instanceof Map) { + expectTypeOf(customCollection).toEqualTypeOf(); +}