microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.82k stars 12.46k forks source link

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

Open AnyhowStep opened 5 years ago

AnyhowStep commented 5 years ago

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, https://github.com/microsoft/TypeScript/pull/32028#issuecomment-515234327

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-

@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, https://github.com/microsoft/TypeScript/pull/32079#issuecomment-515334873

The build I am testing, https://github.com/microsoft/TypeScript/pull/32028

Also relevant, https://github.com/microsoft/TypeScript/issues/29511 It seems like every few versions, there ends up being a difference between tsc and tsserver, regarding the max instantiation depth

ajafff commented 5 years ago

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.

AnyhowStep commented 5 years ago

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.

@ajafff Some versions are okay, though =P

AnyhowStep commented 5 years ago

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?

AnyhowStep commented 5 years ago

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*/
AnyhowStep commented 5 years ago

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.

AnyhowStep commented 5 years ago

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!?

RyanCavanaugh commented 5 years ago

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.

AnyhowStep commented 5 years ago

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

max count 1502701 undefined
max count 2885368 undefined
max count 13837110 undefined
AnyhowStep commented 5 years ago

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?

AnyhowStep commented 5 years ago

@weswigham 's comment here jumped out at me, https://github.com/microsoft/TypeScript/issues/32707#issuecomment-518102872

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

Because it's related to this, https://github.com/microsoft/TypeScript/issues/32573#issuecomment-515527272

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, https://github.com/microsoft/TypeScript/issues/32573#issuecomment-515532751

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