Skip to content

typesafe-joi blows up TypeScript 3.2+ #28873

Description

@hjkcai

TypeScript Version: 3.3.0-dev.20181205

Search Terms:

Maximum call stack size exceeded

Code Repo:

https://github.com/hjkcai/typesafe-joi

Reproduction:

mkdir typesafe-joi-issue && cd typesafe-joi-issue
yarn add typescript@next @types/node typesafe-joi joi
echo '{"compilerOptions":{"strict":true}}' > tsconfig.json
echo 'import * as Joi from "typesafe-joi"; console.log(Joi)' > index.ts
npx tsc -p .

Expected behavior:

tsc should work.

I admit that typesafe-joi uses lots of type recursion, but it works fine in TypeScript 3.1. After upgrading to TypeScript 3.2, the error comes.

Actual behavior:

tsc itself encountered 'Maximum call stack size exceeded' (No stack trace printed)

Activity

  1. j-oliveras commented on Dec 6, 2018

    @j-oliveras
    Contributor

    Without npx using command node_modules\.bin\tsc -p . I see the callstack:

    RangeError: Maximum call stack size exceeded
        at resolveNameHelper (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:26036:35)
        at resolveName (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:26034:20)
        at resolveEntityName (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:26728:26)
        at resolveTypeReferenceName (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31885:20)
        at getTypeFromTypeReference (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:32061:30)
        at getTypeFromTypeNode (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:33421:28)
        at getConstraintFromTypeParameter (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31747:72)
        at fillMissingTypeArguments (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31337:49)
        at getTypeFromClassOrInterfaceReference (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31837:78)
        at getTypeReferenceTypeWorker (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31950:24)
    

    Running node setting a stack-trace-limit to 100000 (to see the full stack trace) with node --stack-trace-limit=100000 node_modules\typescript\bin\tsc -p .: full stack trace (is to large to paste here).

    Seems that the recursion starts at call to getTypeFromTypeReference from checkTypeReferenceNode and the repetition is this callstack fragment:

        at getTypeFromTypeNode (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:33422:28)
        at getConstraintFromTypeParameter (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31748:72)
        at fillMissingTypeArguments (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31338:49)
        at getTypeFromClassOrInterfaceReference (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31838:78)
        at getTypeReferenceTypeWorker (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31951:24)
        at getTypeReferenceType (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:31893:24)
        at getTypeFromTypeReference (Z:\tmp\ts\node_modules\typescript\lib\tsc.js:32063:28)
    
  2. added
    BugA bug in TypeScript
    CrashFor flagging bugs which are compiler or service crashes or unclean exits, rather than bad output
    on Dec 6, 2018
  3. ahejlsberg commented on Dec 9, 2018

    @ahejlsberg
    Member

    The issue here is that AbstractSchema specifies itself as a constraint and relies on type argument defaults in that specification:

    interface AbstractSchema<Schema extends AbstractSchema = any, Value = any> extends JoiObject { ... }

    The use of default type arguments in combination with the recursive constraint causes an infinite loop. An easy fix for now is to specify the type arguments:

    interface AbstractSchema<Schema extends AbstractSchema<any, any> = any, Value = any> extends JoiObject { ... }
  4. ahejlsberg commented on Dec 9, 2018

    @ahejlsberg
    Member

    Wesley Wigham (@weswigham) Best I can tell the infinite loop is caused by fillMissingTypeArguments attempting to get the constraint of the type parameter, which leads back to the same type, which leads back to needing to add default type arguments, and down the rabbit hole we go. I think you were last person to work on this so I'm assigning it over to you.

  5. hjkcai commented on Dec 10, 2018

    @hjkcai
    Author

    Anders Hejlsberg (@ahejlsberg) I assume TypeScript will fill in the type arguments automatically, because AbstractSchema have default type arguments. I will change my code later. Thanks a lot!

  6. EvgenyMuryshkin commented on Dec 23, 2018

    @EvgenyMuryshkin

    I made local fix, you can try to apply and see if it helps
    EvgenyMuryshkin@9f88c2f

  7. ahejlsberg commented on Dec 24, 2018

    @ahejlsberg
    Member

    Evgeny Muryshkin (@EvgenyMuryshkin) Doh! You're right, it definitely should be calling getConstraintOfTypeParameter (which checks for circularities) and not getConstraintFromTypeParameter (which is a worker function that doesn't). Thanks for catching this, I will turn it into a PR.

  8. ahejlsberg commented on Dec 27, 2018

    @ahejlsberg
    Member

    Wesley Wigham (@weswigham) Ron Buckton (@rbuckton) Daniel Rosenwasser (@DanielRosenwasser) I'm thinking that all of the proposed solutions to this issue paper over the the real culprit: Type parameter defaults should only be allowed to reference previously declared type parameters (i.e. type parameters declared to the left). I alluded to this in my comment here. Such a rule would be analogous to JavaScript's restriction that parameter defaults can only reference parameters to the left, and it would make nonsensical constructs such as the following become errors:

    type Foo<T = T> = T[];
    type Bar<T = U, U = T> = [T, U];

    After all, when a type parameter default comes into play, we know that type arguments for that type parameter and type parameters to the right of it weren't specified, so there is nothing meaningful to reference. The OP issue is caused by us trying to come up with a meaningful default for a forward reference (and our answer effectively amounts to the constraint of the forwardly referenced type parameter), but I'm thinking it should just be an error (and we should just use the error type).

    I suppose you could make a weak argument that type parameter defaults in generic functions should be able to reference later type parameters because type inference might produce useful inferred types, but I have yet to see a real world example where that is useful.

    I have a branch that implements this restriction and I'm the process of running validating all of Definitely Typed on it. So far nothing seems to be affected.

  9. EvgenyMuryshkin commented on Dec 28, 2018

    @EvgenyMuryshkin

    We are using this project and I had to tweak type definition a bit after 3.2 migration
    Seems that there is a circular reference in types

    https://github.com/projectstorm/react-diagrams/blob/master/src/BaseEntity.ts

  10. hjkcai commented on Apr 7, 2019

    @hjkcai
    Author

    Hey guys, typesafe-joi make typescript crash again in #30794. Please help!

  11. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptCrashFor flagging bugs which are compiler or service crashes or unclean exits, rather than bad output

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions