Search code examples
javascriptnode.jstypescriptvitest

Type error in TypeScript 5.6: Type '() => Generator<string, void, any>' is not assignable to type '() => BuiltinIterator<string, undefined, any>'


For my unit tests I create a fake window.location object. Here's the slightly simplified code:

function getFakeLocation(getCurrentUrl: () => URL): Location {
  return {
    get ancestorOrigins(): DOMStringList {
      return {
        length: 1,
        contains: (origin: string) => {
          // …
        },
        item(index: number) {
          // …
        },
        *[Symbol.iterator]() {
          yield getCurrentUrl().origin;
        },
      };
    },
    assign: (url: string) => {
      // …
    }),
    get href() {
      // …
    },
    set href(url: string) {
      //
    },
    // …
  };
}

This worked fine until I upgraded from TypeScript 5.4.2 to 5.6.0-beta. Now, tsc complains about the ancestorOrigins definition above:

Type '() => Generator<string, void, any>' is not assignable to type '() => BuiltinIterator<string, undefined, any>'.
  Call signature return types 'Generator<string, void, any>' and 'BuiltinIterator<string, undefined, any>' are incompatible.
    The types returned by 'next(...)' are incompatible between these types.
      Type 'IteratorResult<string, void>' is not assignable to type 'IteratorResult<string, undefined>'.
        Type 'IteratorReturnResult<void>' is not assignable to type 'IteratorResult<string, undefined>'.
          Type 'IteratorReturnResult<void>' is not assignable to type 'IteratorReturnResult<undefined>'.
            Type 'void' is not assignable to type 'undefined'.

            *[Symbol.iterator]() {
             ~~~~~~~~~~~~~~~~~

I already updated @types/node to version 20.16.1 as per this question which solved another type error but the above error still remains. However, to me the latter sounds very similar to what Daniel Rosenwasser wrote in his answer in that thread.


Solution

  • The DOMStringList interface changed between 5.5.4 and 5.6.0

    5.5.4:

    interface DOMStringList {
        [Symbol.iterator](): IterableIterator<string>;
    }
    

    5.6.0-beta:

    interface DOMStringList {
        [Symbol.iterator](): BuiltinIterator<string, BuiltinIteratorReturn>;
    }
    

    For motivation see:

    You stumbled upon a problem described in Disambiguate BuiltinIterator/BuiltinIteratorReturn #59506

    In #58243 we introduced the --strictBuiltinIteratorReturn flag and the BuiltinIteratorReturn type to improve type checking for iterator results produced by the iterators of built-ins like Array, Map, Set, etc. While in #58222 we separately introduced the type BuiltinIterator to identify the shape of the new Iterator.prototype used by built-ins like Array, Map, and Set, but also for Generators. Where these two features touch, you will generally see a type reference like BuiltinIterator<T, BuiltinIteratorReturn>.

    Despite the overlap between these two features, there is a subtle distinction between the two mechanisms that are causing some confusion for users. In #59444, it was requested that we make BuiltinIteratorReturn the default for the TReturn type parameter of BuiltinIterator, but this is problematic since Generator now inherits from BuiltinIterator, but the default inference we make for the TReturn of a Generator is void, which is not assignable to BuiltinIteratorReturn when the strictBuiltinIteratorReturn flag is set. On its own, this is not terribly problematic since a Generator instantiation would properly instantiate BuiltinIterator with the correct return type, it does mean that the following code sample would produce an error:

    function* f() { yield ""; }
    const it: BuiltinIterator<string> = f(); // error: `void` is not assignable to `undefined`
    

    The example above works in 5.6.0-beta, but this does not

    function* f() { yield ""; }
    const it: BuiltinIterator<string, BuiltinIteratorReturn> = f(); // error: `void` is not assignable to `undefined`
    

    Note that this is a precise definition of iterator in DOMStringList

    You have 2 options to solve:

    Explicit type

    *[Symbol.iterator](): BuiltinIterator<string, BuiltinIteratorReturn> {
      yield getCurrentUrl().origin;      
    },
    

    Return value

    *[Symbol.iterator]() {
      yield getCurrentUrl().origin;      
      return undefined;
    },