Home > database >  How to type this Symbol Iterator implementation in TypeScript
How to type this Symbol Iterator implementation in TypeScript

Time:12-30

I'm trying to get this working JavaScript (but not mine :P) pass typescript type checking. Maybe it's just late in the day but I could use some help.


class GPUTexture {}

class BaseState<T> {
    webgpuObject: T | null;

    constructor() {
        this.webgpuObject = null;
    }
}

class TextureState extends BaseState<GPUTexture> {
   name = "foo";
}

interface CaptureStateBase<GPUType> {
    webgpuObject: GPUType | null;
}

class ObjectRegistry<GPUType extends Object, CaptureState extends CaptureStateBase<GPUType>> {
    iterating: boolean;
    dataMap: WeakMap<GPUType, CaptureState>;
    objects: WeakRef<GPUType>[];

    constructor() {
        this.dataMap = new WeakMap();
        this.objects = [];
        this.iterating = false;
    }

    add(obj: GPUType, data: CaptureState) {
        if (this.iterating) {
            throw new Error('Mutating Registry while iterating it.');
        }

        this.dataMap.set(obj, data);
        this.objects.push(new WeakRef(obj));
        data.webgpuObject = obj;
    }

    get(obj: GPUType) {
        return this.dataMap.get(obj);
    }

    [Symbol.iterator](): IterableIterator<CaptureState> {
        let i = 0;
        this.iterating = true;

        return {
            registry: this,
            next() {
                while (i < this.registry.objects.length) {
                    const obj = this.registry.objects[i  ].deref();
                    if (obj === undefined) {
                        continue;
                    }
                    return { value: this.registry.get(obj), done: false };
                }
                this.registry.iterating = false;
                return { done: true };
            },
        };
    }
}

const textures = new ObjectRegistry<GPUTexture, TextureState>();
for (const tex of textures) {
  console.log(tex.name);
}

I'd try to simplify the code but I'm not sure I understand the error. I see 2 errors actually. One is, typescript seems confused about this inside next. I complains about this.registry not existing

The other is

(method) Iterator<CaptureState, any, undefined>.next(...args: [] | [undefined]): IteratorResult<CaptureState, any>

Type '() => { value: any; done: false; } | { done: true; value?: undefined; }' is not assignable to type '(...args: [] | [undefined]) => IteratorResult<CaptureState, any>'.

Type '{ value: any; done: false; } | { done: true; value?: undefined; }' is not assignable to type 'IteratorResult<CaptureState, any>'.

Type '{ done: true; value?: undefined; }' is not assignable to type 'IteratorResult<CaptureState, any>'.

Type '{ done: true; value?: undefined; }' is not assignable to type 'IteratorReturnResult'.

Property 'value' is optional in type '{ done: true; value?: undefined; }' but required in type 'IteratorReturnResult'.(2322)

typescript playground

I tried to fix the first one by moving next up like this


class GPUTexture {}

class BaseState<T> {
    webgpuObject: T | null;

    constructor() {
        this.webgpuObject = null;
    }
}

class TextureState extends BaseState<GPUTexture> {
    name = "foo";
}

interface CaptureStateBase<GPUType> {
    webgpuObject: GPUType | null;
}

class ObjectRegistry<GPUType extends Object, CaptureState extends CaptureStateBase<GPUType>> {
    iterating: boolean;
    dataMap: WeakMap<GPUType, CaptureState>;
    objects: WeakRef<GPUType>[];

    constructor() {
        this.dataMap = new WeakMap();
        this.objects = [];
        this.iterating = false;
    }

    add(obj: GPUType, data: CaptureState) {
        if (this.iterating) {
            throw new Error('Mutating Registry while iterating it.');
        }

        this.dataMap.set(obj, data);
        this.objects.push(new WeakRef(obj));
        data.webgpuObject = obj;
    }

    get(obj: GPUType) {
        return this.dataMap.get(obj);
    }

    [Symbol.iterator](): IterableIterator<CaptureState> {
        let i = 0;
        this.iterating = true;

        const next = () => {
            while (i < this.objects.length) {
                const obj = this.objects[i  ].deref();
                if (obj === undefined) {
                    continue;
                }
                return { value: this.get(obj), done: false };
            }
            this.iterating = false;
            return { done: true };
        };

        return { next };
    }
}

const textures = new ObjectRegistry<GPUTexture, TextureState>();
for (const tex of textures) {
  console.log(tex.name);
}

typescript playground

That solves the first error but not the second

CodePudding user response:

Referencing a separate this.registry as a custom value on an iterator object is odd and can cause problems. It'd be easier to ditch that entirely and just use an arrow function and the outer this. That way, the typing for it will just work - your arrow function is the right idea.

next: () => {
    while (i < this.objects.length) {
        const obj = this.objects[i  ].deref();
        // ...

After that, there's just one more warning due to the typing here:

[Symbol.iterator](): IterableIterator<CaptureState> {

which can be fixed by specifying that the this is the same as the class that was instantiated, and letting TypeScript infer the iterator type on its own.

[Symbol.iterator](this: ObjectRegistry<GPUType, CaptureState>) {

Playground link


The

Property 'value' is optional in type '{ done: true; value?: undefined; }' but required in type 'IteratorReturnResult'.(2322)

can be fixed by changing

return { done: true };

to

return { done: true, value: undefined };

but doesn't look necessary.

  • Related