Skip to content

tsc and tsserver have different ideas of excessively deep types #32573

Description

@AnyhowStep

TypeScript Version:

typescript-3.6.0-insiders.20190725

https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/37759/artifacts?artifactName=tgz&fileId=2A7E3F3E93DD6F833D63A63F3A7A21707B3F57B08FCA5C93A8F1848556D8141F02&fileName=/typescript-3.6.0-insiders.20190725.tgz

Taken from,
#32028 (comment)

Search Terms:

max instantiation count, max instantiation depth, tsc, tsserver

Code

I've reduced the 40-subproject monorepo to just small parts of 2 subprojects.

Here is a snippet of the problem,

/**
 *
 * ```json
 * //tsconfig-base.json
 * "disableSourceOfProjectReferenceRedirect": true,
 * ```
 *
 * ```json
 * //package.json
 * "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/37759/artifacts?artifactName=tgz&fileId=2A7E3F3E93DD6F833D63A63F3A7A21707B3F57B08FCA5C93A8F1848556D8141F02&fileName=/typescript-3.6.0-insiders.20190725.tgz"
 * ```
 *
 * -----
 *
 * VS Code: OK!
 * Type inference OK!
 *
 * -----
 *
 * `tsc` gives me,
 *
 * ```js
 * src/blah.ts:11:21 - error TS2589: Type instantiation is excessively deep and possibly infinite.
 *
 *  11 export const json = tm.deepMerge(
 *                         ~~~~~~~~~~~~~
 *  12     base.json,
 *     ~~~~~~~~~~~~~~
 * ...
 *  31     s.convertBlahType.toStr.BLAH
 *     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *  32 );
 *     ~
 * ```
 */
export const json = tm.deepMerge(
  /*snip*/
);

const x = json("", "");
if (x.foo != undefined) {
    if (x.foo.bar != undefined) {
        //Correctly infers `string`
        //So, 14M type instantiations is okay with VS code
        x.foo.bar.baz
    }
}

Expected behavior:

If tsc gets the error,

TS2589: Type instantiation is excessively deep and possibly infinite.

Then tsserver should also get the error

Actual behavior:

tsc gets the error, but tsserver does not (or at least VS code infers the type correctly without errors)

Playground Link:

-None-

Sheetal Nandi (@sheetalkamat)

The project involved contains code related to a company project.
So, I really do not want to upload it publicly, if possible.

I could mangle the variable names so that they're meaningless but if I can just email the parts of the project that are involved, that would be nice.

Repro steps:

  1. npm install
  2. npm run build
  3. See TS2589: Type instantiation is excessively deep and possibly infinite.
  4. Open VS code
  5. Navigate to file tsc says contains errors
  6. Notice no errors
  7. Play with return type of function
  8. Notice inference works correctly

Related Issues:

The merged PR that introduced the error,
#32079 (comment)

The build I am testing,
#32028

Also relevant, #29511
It seems like every few versions, there ends up being a difference between tsc and tsserver, regarding the max instantiation depth

Activity

  1. ajafff commented on Jul 26, 2019

    @ajafff
    Contributor

    This is probably an effect of the different check order. tsserver checks the currently open file first. That causes different evaluation order and caching for type instantiations.

  2. AnyhowStep commented on Jul 26, 2019

    @AnyhowStep
    ContributorAuthor

    Also, I'd like to note (again) that TS 3.5.1 builds this particular project in like 4-7 seconds (this includes the "problematic" file and lots of other files).
    So, 14M instantiations isn't really a problem.

    Klaus Meinhardt (@ajafff) Some versions are okay, though =P

  3. AnyhowStep commented on Jul 26, 2019

    @AnyhowStep
    ContributorAuthor

    In my repro,

    If I remove the file from subproject-b and build, it takes 1.41s.

    max count 1
    max count 5
    max count 30
    max count 33
    max count 51
    max count 98
    max count 123
    max count 198
    max count 243
    max count 353
    max count 1196
    max count 1501
    max count 3056
    max count 6626
    max count 21884
    max count 145426
    max count 276256
    max count 4925257
    Files:                        373
    Lines:                      14338
    Nodes:                      67837
    Identifiers:                21551
    Symbols:                    31339
    Types:                      17600
    Memory used:              119849K
    Assignability cache size:    7096
    Identity cache size:            0
    Subtype cache size:             0
    I/O Read time:              0.01s
    Parse time:                 0.01s
    Program time:               0.11s
    Bind time:                  0.02s
    Check time:                 0.55s
    transformTime time:         0.76s
    Source Map time:            0.01s
    commentTime time:           0.01s
    printTime time:             0.74s
    Emit time:                  0.74s
    I/O Write time:             0.01s
    Total time:                 1.41s
    

    If I add the offending file to a new subproject-c and build, it takes 1.10s.
    subproject-c references subproject-a and subproject-b,
    and only has one file.

    max count 5
    max count 30
    max count 32
    max count 37
    max count 44
    max count 45
    max count 51
    max count 98
    max count 123
    max count 198
    max count 353
    max count 746
    max count 12595
    Files:                       339
    Lines:                     14739
    Nodes:                     69534
    Identifiers:               21824
    Symbols:                   16229
    Types:                     14113
    Memory used:              55729K
    Assignability cache size:   3280
    Identity cache size:           0
    Subtype cache size:            0
    I/O Read time:             0.01s
    Parse time:                0.17s
    Program time:              0.36s
    Bind time:                 0.09s
    Check time:                0.50s
    transformTime time:        0.12s
    Source Map time:           0.00s
    commentTime time:          0.01s
    printTime time:            0.15s
    Emit time:                 0.15s
    I/O Write time:            0.00s
    Total time:                1.10s
    

    [Edit]
    It looks like the es5.d.ts file is causing the 4,925,257 instantiations?

  4. AnyhowStep commented on Jul 26, 2019

    @AnyhowStep
    ContributorAuthor

    Just for reference,

    I set the limit to 4,000,000,

            var instantiationCount = 0;
            var maxCount = 0;
            function checkMaxCount (node) {
                if (instantiationCount > maxCount) {
                    maxCount = instantiationCount;
                    if (node.declarationList) {
                        console.log("max count", instantiationCount, node);
                    }
                }
            }
                checkMaxCount(node);
                instantiationCount = 0;
                if (instantiationDepth === 50 || instantiationCount >= 4000000) {
                    error(currentNode, ts.Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
                    return errorType;
                }

    This is the node.declarationList.declarations when I set the limit to 4,000,000,

    [ Node {
        pos: 959,
        end: 971,
        kind: 238,
        id: 0,
        flags: 4194304,
        modifierFlagsCache: 536870912,
        transformFlags: 0,
        parent:
         Node {
           pos: 955,
           end: 971,
           kind: 239,
           id: 0,
           flags: 4194304,
           modifierFlagsCache: 0,
           transformFlags: 0,
           parent: [Node],
           original: undefined,
           declarations: [Circular] },
        original: undefined,
        name:
         Node {
           pos: 959,
           end: 963,
           kind: 73,
           id: 0,
           flags: 4194304,
           modifierFlagsCache: 0,
           transformFlags: 0,
           parent: [Circular],
           original: undefined,
           escapedText: 'NaN',
           flowNode: [Object] },
        type:
         Node {
           pos: 964,
           end: 971,
           kind: 136,
           id: 0,
           flags: 4194304,
           modifierFlagsCache: 0,
           transformFlags: 0,
           parent: [Circular],
           original: undefined },
        initializer: undefined,
        symbol:
         Symbol {
           flags: 1,
           escapedName: 'NaN',
           declarations: [Array],
           valueDeclaration: [Circular],
           id: undefined,
           mergeId: undefined,
           parent: undefined,
           exports: Map {} } },
      pos: 959,
      end: 971 ]

    So, NaN lives in es5.d.ts, I believe. Could the limit not be raised higher? =x


    When I print out the node itself,

    Node {
      pos: 0,
      end: 972,
      kind: 220,
      id: 0,
      flags: 4194304,
      modifierFlagsCache: 536870914,
      transformFlags: 0,
      parent:
       Node {
         pos: 0,
         end: 207082,
         kind: 285,
         id: 13293,
         flags: 4194336,
         modifierFlagsCache: 0,
         transformFlags: 0,
         parent: undefined,
         original: undefined,
         text:
          '/*! *****************************************************************************\nCopyright (c) Microsoft Corporation. All rights reserved. \nLicensed under the Apache License, Version 2.0 (the "License"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0  \n \nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, \nMERCHANTABLITY OR NON-INFRINGEMENT. \n \nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n\n\n\n/// <reference no-default-lib="true"/>\r\n\n\n/////////////////////////////\r\n/// ECMAScript APIs\r\n/////////////////////////////\r\n\r\ndeclare var NaN: number;\r\ndeclare var Infinity: number;\r\n\r\n/**\r\n  * Evaluates JavaScript code and executes it.\r\n  * @param x A String value that contains valid JavaScript code.\r\n  */\r\ndeclare function eval(x: string): any;\r\n\r\n/**\r\n  * Converts a string to an integer.\r\n  * @param s A string to convert into a number.\r\n  * @param radix A value between 2 and 36 that specifies the base of the number in numString.\r\n  * If this argument is not supplied, strings with a prefix of \'0x\
    /*snip*/
    s An object that contains one or more properties that specify comparison options.\r\n      */\r\n    toLocaleTimeString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string;\r\n}\r\n',
         bindDiagnostics: [],
         bindSuggestionDiagnostics: undefined,
         languageVersion: 99,
         fileName:
          '/home/anyhowstep/node-projects/14m-vs-5m/node_modules/typescript/lib/lib.es5.d.ts',
         languageVariant: 0,
         isDeclarationFile: true,
         scriptKind: 3,
         pragmas: Map { 'reference' => [Object] },
         checkJsDirective: undefined,
         referencedFiles: [],
         typeReferenceDirectives: [],
         libReferenceDirectives: [],
         amdDependencies: [],
         hasNoDefaultLib: true,
         statements:
          [ [Circular],
            [Node],
            [Node],
    /*snip*/
            [Node],
            ... 40 more items,
            pos: 0,
            end: 207080 ],
         endOfFileToken:
          Node {
            pos: 207080,
            end: 207082,
            kind: 1,
            id: 0,
            flags: 4194304,
            modifierFlagsCache: 0,
            transformFlags: 0,
            parent: [Circular],
            original: undefined },
         externalModuleIndicator: undefined,
         nodeCount: 13032,
         identifierCount: 4120,
         identifiers:
          Map {
            'NaN' => 'NaN',
            'Infinity' => 'Infinity',
    /*snip*/
  5. AnyhowStep commented on Jul 26, 2019

    @AnyhowStep
    ContributorAuthor

    Uhh...

    So, going back to the original project I have for work,
    I just fixed all of the "max instantiation depth" bugs I could find and this is happening,

    error TS2589: Type instantiation is excessively deep and possibly infinite.
    

    It doesn't even tell me what node is causing the problem!


    My process was this,

    1. Build
    2. Max depth/count error?
    3. Look at file and line of diagnostics.
    4. Fix
    5. Go to step 1

    However, now I get the max depth/count error and there is no file or line number.


    So, I added the maxCount() hack to tsc.js and kept the limit at 5,000,000 and ran tsc again...

    And it's the same es5.d.ts node!

    So, for some projects, it gives 4.9M instantiations. And for others, > 5M and craps out.

  6. AnyhowStep commented on Jul 26, 2019

    @AnyhowStep
    ContributorAuthor

    I made the limit 14 million.
    Then, I ran the build.

    It built fine.

    But the maxCount was...

    max count 13837110 Node {
      pos: 0,
      end: 972,
      kind: 220,
      id: 0,
      flags: 4194304,
      modifierFlagsCache: 536870914,
      transformFlags: 0,
      parent:
       Node {
         pos: 0,
         end: 207082,
         kind: 285,
         id: 31661,
         flags: 4194336,
         modifierFlagsCache: 0,
         transformFlags: 0,
         parent: undefined,
         original: undefined,
         text:
          '/*! *****************************************************************************\nCopyright (c) Microsoft Corporation. All rights reserved. \nLicensed under the Apache License, Version 2.0 (the "License"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0  \n \nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, \nMERCHANTABLITY OR NON-INFRINGEMENT. \n \nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n\n\n\n/// <reference no-default-lib="true"/>\r\n\n\n/////////////////////////////\r\n/// ECMAScript APIs\r\n/////////////////////////////\r\n\r\ndeclare var NaN: number;\r\ndeclare var Infinity: number;\r\n\r\n/**\r\n  * Evaluates JavaScript code and executes it.\r\n  * @param x A String value that contains valid JavaScript code.\r\n  */\r\ndeclare function eval(x: string): any;\r\n\r\n/**\r\n  * Converts a string to an integer.\r\n  * @param s A string to convert into a number.\r\n  * @param radix A value between 2 and 36 that specifies the base of the number in numString.\r\n  * If this argument is not supplied, strings with a prefix of \'0x\' are considered hexadecimal.\r\n  * All other strings are considered decimal.\r\n  */\r\ndeclare function parseInt(s: string, radix?: number): number;\r\n\r\n/**\r\n  * Converts a string to a floating-point number.\r\n  * @param string A string that contains a floating-point number.\r\n  */\r\ndeclare function parseFloat(string: string): number;\r\n\r\n/**\r\n  * Returns a Boolean value that indicates whether a value is the reserved value NaN (not a number).\r\n  * @param number A numeric value.\r\n  */\r\ndeclare function isNaN(number: number): boolean;\r\n\r\n/**\r\n  * Determines whether a supplied number is finite.\r\n  * @param number Any numeric value.\r\n  */\r\ndeclare function isFinite(number: number): boolean;\r\n\r\n/**\r\n  * Gets the unencoded version of an encoded Uniform Resource Identifier (URI).\r\n  * @param encodedURI A value representing an encoded URI.\r\n  */\r\ndeclare function decodeURI(encodedURI: string): string;\r\n\r\n/**\r\n  * Gets the unencoded version of an encoded component of a Uniform Resource Identifier (URI).\r\n  * @param encodedURIComponent A value representing an encoded URI component.\r\n  */\r\ndeclare function decodeURIComponent(encodedURIComponent: string): string;\r\n\r\n/**\r\n  * Encodes a text string as a valid Uniform Resource Identifier (URI)\r\n  * @param uri A value representing an encoded URI.\r\n  */\r\ndeclare function encodeURI(uri: string): string;\r\n\r\n/**\r\n  * Encodes a text string as a valid component of a Uniform Resource Identifier (URI).\r\n  * @param uriComponent A value representing an encoded URI component.\r\n  */\r\ndeclare function encodeURIComponent(uriComponent: string | number | boolean): string;\r\n\r\n/**\r\n  * Computes a new string in which certain characters have been replaced by a hexadecimal escape seq
    /*snip*/
    

    When I take the text and do text.substr(0, 972), I get,

    
    "/*! *****************************************************************************
    Copyright (c) Microsoft Corporation. All rights reserved. 
    Licensed under the Apache License, Version 2.0 (the "License"); you may not use
    this file except in compliance with the License. You may obtain a copy of the
    License at http://www.apache.org/licenses/LICENSE-2.0  
     
    THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
    WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
    MERCHANTABLITY OR NON-INFRINGEMENT. 
     
    See the Apache Version 2.0 License for specific language governing permissions
    and limitations under the License.
    ***************************************************************************** */
    
    
    
    /// <reference no-default-lib="true"/>
    
    
    /////////////////////////////
    /// ECMAScript APIs
    /////////////////////////////
    
    declare var NaN: number;"
    

    How does NaN give 13.8M instantiations!?

  7. RyanCavanaugh commented on Jul 26, 2019

    @RyanCavanaugh
    Member

    tsserver intrinsically doesn't have to do as much work to provide completion info as tsc does to ensure there are no errors in your project in a batch scenario. And, as, noted, if your code is extremely complex (it seems like this is common in your projects), the ordering in which types are inspected can results in errors in some cases and not others.

  8. AnyhowStep commented on Jul 26, 2019

    @AnyhowStep
    ContributorAuthor

    I changed it to, checkMaxCount(saveCurrentNode) and got,

    max count 1502701 undefined
    max count 2885368 undefined
    max count 13837110 undefined
    
  9. AnyhowStep commented on Jul 26, 2019

    @AnyhowStep
    ContributorAuthor

    Sorry for the spam =(

    I'm just anxious that my code will break with 3.6 and I won't be able to migrate my projects.
    So, I'm pretty desperate to find a solution that makes sense to me.


    I think one of my gripes with this situation is that I've now discovered I can get the max count error and not know how to fix it because tsc won't tell me where the error is.

    I'd like to stress again that in 3.5.1, these files compile in mere seconds. So it's not like having 14M, 16M, 20M instantiations is actually a real problem.

    And inspecting the node just tells me es5.d.ts.

    I can't just not use es5.d.ts and I can't fix es5.d.ts

    99% sure I just have the wrong idea and I'm inspecting the wrong things and drawing the wrong conclusion. But I still don't know where the count hits 13.8M


    What should I look out for when trying to find the 13.8M culprit? Any variables I should be monitoring besides node and currentNode?

  10. AnyhowStep commented on Aug 5, 2019

    @AnyhowStep
    ContributorAuthor

    Wesley Wigham (@weswigham) 's comment here jumped out at me,
    #32707 (comment)

    It probably indicates a codepath we neglect to set a currentNode on.

    Because it's related to this,
    #32573 (comment)

    I just fixed all of the "max instantiation depth" bugs I could find and this is happening,

    error TS2589: Type instantiation is excessively deep and possibly infinite.
    

    It doesn't even tell me what node is causing the problem!

    And this,
    #32573 (comment)

    I can get the max count error and not know how to fix it because tsc won't tell me where the error is.

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

    Needs InvestigationThis issue needs a team member to investigate its status.

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions