Skip to content

Errors in @types/jsdom, @sinclair/typebox #929

Open
@andyfleming

Description

@andyfleming
  • Node v20.19.2 on mac
  • Works fine with tsc from typescript@5.5.4
  • Errors visible with @typescript/native-preview-darwin-arm64@7.0.0-dev.20250523.1
  • Dependency chain jest-environment-jsdom@29.7.0
    • @types/jsdom@20.0.1
      • @sinclair/typebox@0.27.8
npx tsgo src
node_modules/@sinclair/typebox/typebox.d.ts:137:63 - error TS2321: Excessive stack depth comparing types 'UnionToTuple<{ [K in T]: TLiteral<K>; }[T], UnionLast<{ [K in T]: TLiteral<K>; }[T]>>' and 'TSchema[]'.

137 export type TExcludeTemplateLiteralResult<T extends string> = TUnionResult<Assert<UnionToTuple<{
                                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
138     [K in T]: TLiteral<K>;
    ~~~~~~~~~~~~~~~~~~~~~~~~~~
139 }[T]>, TSchema[]>>;
    ~~~~~~~~~~~~~~~~~~
node_modules/@sinclair/typebox/typebox.d.ts:145:63 - error TS2321: Excessive stack depth comparing types 'UnionToTuple<{ [K in T]: TLiteral<K>; }[T], UnionLast<{ [K in T]: TLiteral<K>; }[T]>>' and 'TSchema[]'.

145 export type TExtractTemplateLiteralResult<T extends string> = TUnionResult<Assert<UnionToTuple<{
                                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
146     [K in T]: TLiteral<K>;
    ~~~~~~~~~~~~~~~~~~~~~~~~~~
147 }[T]>, TSchema[]>>;
    ~~~~~~~~~~~~~~~~~~
node_modules/@sinclair/typebox/typebox.d.ts:380:102 - error TS2321: Excessive stack depth comparing types 'UnionToTuple<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>' and 'TLiteral<TLiteralValue>[]'.

380 export type TUnionTemplateLiteral<T extends TTemplateLiteral, S extends string = Static<T>> = Ensure<TUnionResult<Assert<UnionToTuple<{
                                                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
381     [K in S]: TLiteral<K>;
    ~~~~~~~~~~~~~~~~~~~~~~~~~~
382 }[S]>, TLiteral[]>>>;
    ~~~~~~~~~~~~~~~~~~~
node_modules/@types/jquery/misc.d.ts:7359:14 - error TS2717: Subsequent property declarations must have the same type.  Property 'toStringTag' must be of type 'unique symbol', but here has type 'symbol'.

7359     readonly toStringTag: symbol;
                  ~~~~~~~~~~~

  node_modules/@typescript/native-preview-darwin-arm64/lib/lib.es2015.symbol.wellknown.d.ts:74:14 - 'toStringTag' was also declared here.
    74     readonly toStringTag: unique symbol;
                    ~~~~~~~~~~~
node_modules/@types/jsdom/base.d.ts:194:18 - error TS2411: Property 'Infinity' of type 'number' is not assignable to 'number' index type 'Window'.

194         readonly ["Infinity"]: number;
                     ~~~~~~~~~~~~
node_modules/@types/jsdom/base.d.ts:195:18 - error TS2411: Property 'NaN' of type 'number' is not assignable to 'number' index type 'Window'.

195         readonly ["NaN"]: number;
                     ~~~~~~~

Found 6 errors in 3 files.

Errors  Files
     3  node_modules/@sinclair/typebox/typebox.d.ts:137
     1  node_modules/@types/jquery/misc.d.ts:7359
     2  node_modules/@types/jsdom/base.d.ts:194

Activity

ahejlsberg

ahejlsberg commented on May 28, 2025

@ahejlsberg
Member

Can you provide a short repro?

andyfleming

andyfleming commented on May 29, 2025

@andyfleming
Author
Andarist

Andarist commented on Jun 5, 2025

@Andarist
Contributor

@andyfleming u might have forgotten to push some files there

andyfleming

andyfleming commented on Jun 5, 2025

@andyfleming
Author

@andyfleming u might have forgotten to push some files there

@Andarist A fresh clone of that repository and running the commands in the README reproduces the errors for me.

Andarist

Andarist commented on Jun 6, 2025

@Andarist
Contributor

That's not what it was asked for here. A short repro would be a self-isolated one, preferably within a single file. It shouldn't rely on massive external packages

sinclairzx81

sinclairzx81 commented on Jun 10, 2025

@sinclairzx81

@Andarist @jakebailey Hello,

I have just happened across this issue. As per request, I have setup a repro specifically for TypeBox at the link below:

https://github.com/sinclairzx81/typescript-go-issue-929

I should note that the referenced TypeBox version 0.27.8 is extremely out of date. The issue can be resolved by updating TypeBox to the latest 0.34.33. Unfortunately, there is a very long tail of downstream libraries and tools that do take a dependency on older versions of Jest (which in turn take the dependency to 0.27.8). Given the dependency chains downstream, it's not entirely trivial for applications to just update to the latest versions to leverage TS native.

Happy to provide additional information if required.
S

Andarist

Andarist commented on Jun 10, 2025

@Andarist
Contributor

The best thing you could provide is a self-isolated repro of the problem. This one still depends on an external library, one that has a lot of unrelated (to the problem) types in it. Investigating such repro takes considerably more time than investigating a small one.

sinclairzx81

sinclairzx81 commented on Jun 10, 2025

@sinclairzx81

The best thing you could provide is a self-isolated repro of the problem. This one still depends on an external library, one that has a lot of unrelated (to the problem) types in it. Investigating such repro takes considerably more time than investigating a small one.

@Andarist Project has been updated to include only offending types. Library dependencies have been removed as per request.

https://github.com/sinclairzx81/typescript-go-issue-929

Andarist

Andarist commented on Jun 10, 2025

@Andarist
Contributor

This is great and way more actionable. Thanks ❤ I'll investigate this in the coming days

Andarist

Andarist commented on Jun 10, 2025

@Andarist
Contributor

My preliminary findings...

This is a type ordering issue. The type arguments are being normalized and thus simplified. getTrueTypeFromConditionalType in getSimplifiedConditionalType leads to relating [...UnionToTuple<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<...>>>>, UnionLast<...>] source to TLiteral<string>.

Here:

return r.isRelatedTo(r.c.getIndexTypeOfTypeEx(source, r.c.numberType, r.c.anyType), r.c.getIndexTypeOfTypeEx(target, r.c.numberType, r.c.anyType), RecursionFlagsBoth, reportErrors)

We can observe:

source // [...UnionToTuple<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<...>>>>, UnionLast<...>]
target // TLiteral<string>[]

And we enter isRelatedTo with their index info types:

originalSource // UnionLast<{ [K in S]: TLiteral<K>; }[S]> | UnionToTuple<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>[number]
originalTarget // TLiteral<string>

Buuut... in Corsa the order of this originalSource union is flipped. This ends up in eachTypeRelatedToType and the whole relation ends up returning Ternary.False (in Strada too) but in Corsa it relates against the first constituent, well, first. That leads to deep recursion when the constraint of this source gets related here:

// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
result = r.isRelatedToEx(constraint, target, RecursionFlagsSource, false /*reportErrors*/, nil /*headMessage*/, intersectionState)

We can observe there:

source // UnionToTuple<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>[number]
constraint // ([] | [...UnionToTuple<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>, UnionLast<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>])[number]
target // TLiteral<string>

When the compiler hits it again those can be observed:

source // UnionToTuple<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>, UnionLast<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>>>[number]
constraint // ([] | [...UnionToTuple<Exclude<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>, UnionLast<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>>>, UnionLast<Exclude<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>, UnionLast<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>>>>>, UnionLast<Exclude<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>, UnionLast<Exclude<{ [K in S]: TLiteral<K>; }[S], UnionLast<{ [K in S]: TLiteral<K>; }[S]>>>>>])[number]
target // TLiteral<string>

As we can see, the source side here just "grows" through recursive attempts at relating its constraint to the target.

So, in a sense, Strada was able to return early from this as it related a different source first. Note that all of this is just within the normalization/simplification of the source. It can't be simplified - but it has to relate types to check if it can or not. And that leads to the dreaded "Excessive stack depth comparing types"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Domain: Type CheckingRelated to type checking, grammar checkingType OrderingAn issue related to ordering of types

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @andyfleming@sinclairzx81@ahejlsberg@jakebailey@Andarist

        Issue actions

          Errors in @types/jsdom, @sinclair/typebox · Issue #929 · microsoft/typescript-go