microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

Home Page:https://www.typescriptlang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[API Question] Parentheses affects declaration emit. Intentional?

AnyhowStep opened this issue · comments

Consider the following snippet,

/*
    The below types are exactly the same.
    The only difference is that one is wrapped in parentheses.
    The other is not.
*/

type InvalidKeys<K extends string|number|symbol> = { [P in K]? : never };
type InvalidKeys2<K extends string|number|symbol> = (
    { [P in K]? : never }
);

type A<T> = (
    T & InvalidKeys<"a">
);
type A2<T> = (
    T & InvalidKeys2<"a">
);

/*
    type a = {
        x: number;
    } & InvalidKeys<"a">
 */
type a = A<{ x : number }>;
/*
    type a2 = {
        x: number;
    } & {
        a?: undefined;
    }
*/
type a2 = A2<{ x : number }>;

/*
    One parentheses is all you need to change the output
*/

Playground

I've noticed that, sometimes, the presence/absence of parentheses can change the emitted type declaration. Is this intentional? If so, are there set rules for it? Can this behaviour be relied upon?

There are times where one would want the type to be expanded. And other times where one would not want the type to be expanded.

Having control over it would be nice. (I know I've wanted control over it plenty of times).


I have encountered this discrepancy multiple times but this is the most recent example,
(that inspired me to finally make this issue).
#20863 (comment)

The InvalidKeys type is not expanded because I did not wrap the type in parentheses.


Obligatory @keithlayne , who hates unnecessary parentheses 🚎

I don't think this is intentional - we normally skip past parens in anywhere where we traverse structure, I'm guessing that when we assign an alias symbol for the mapped type, we're neglecting to do so.

It's worth noting though, that even if we patch this, the following:

type InvalidKeys3<K extends string|number|symbol> = {
    "0": { [P in K]? : never }
}["0"];

Is probably still going to work as a way to avoid giving the mapped type an alias without changing its meaning.

Ooooooo

I just learned a new trick!

If you put anything other than a constant in that index position, you best name that object you're indexing into, y'hear? I am not responsible for how ugly your printback types get once you start immediately switch-indexing on anonymous objects.

@weswigham @RyanCavanaugh

Could priority for fixing this "bug" be bumped up?
And maybe also merged into 3.5.1 after it's fixed?

I just realized that this parentheses-causing-expansion thing can actually cause really bad .d.ts output.

Bad enough that the project will emit .d.ts files fine...
But other projects using the .d.ts files will have a stack overflow!


/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:37580
        function getLateBoundSymbol(symbol) {
                                   ^
RangeError: Maximum call stack size exceeded
    at getLateBoundSymbol (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:37580:36)
    at getSymbolOfNode (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:33831:51)
    at appendTypeParameters (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:36781:99)
    at getOuterTypeParameters (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:36819:57)
    at getOuterTypeParameters (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:36812:51)
    at getOuterTypeParameters (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:36812:51)
    at getOuterTypeParameters (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:36812:51)
    at getOuterTypeParameters (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:36812:51)
    at getOuterTypeParameters (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:36812:51)
    at getOuterTypeParameters (/home/anyhowstep/node-projects/tsql/tsql/node_modules/typescript/lib/typescript.js:36812:51)

I almost always use parentheses for multi-line types,

export type IntersectUsedRef<
    U extends AnyRawExpr
> = (
    UsedRef<U> extends never ?
    IUsedRef<{}> :
    UsedRefUtil.Intersect<
        UsedRef<U>
    >
);

The above actually causes the output to be super long!
image

And if I try to use this emitted .d.ts file, I get the stack overflow problem!

It appears it keeps trying to resolve the same file over and over and over again,

/home/anyhowstep/node-projects/tsql/tsql/dist/index.d.ts
/home/anyhowstep/node-projects/tsql/tsql/dist/index.d.ts
node.argument.literal TokenObject {
  pos: 36129,
  end: 36136,
  flags: 4194304,
  modifierFlagsCache: 0,
  transformFlags: 0,
  parent:
   NodeObject {
     pos: 36129,
     end: 36136,
     flags: 4194304,
     modifierFlagsCache: 0,
     transformFlags: 0,
     parent:
      NodeObject {
        pos: 36121,
        end: 36402,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [NodeObject],
        kind: 184,
        argument: [Circular],
        qualifier: [IdentifierObject],
        typeArguments: [Array],
        id: 37465 },
     kind: 183,
     literal: [Circular] },
  kind: 10,
  text: '../..' } NodeObject {
  pos: 36121,
  end: 36402,
  flags: 4194304,
  modifierFlagsCache: 0,
  transformFlags: 0,
  parent:
   NodeObject {
     pos: 36054,
     end: 38678,
     flags: 4194304,
     modifierFlagsCache: 0,
     transformFlags: 0,
     parent:
      NodeObject {
        pos: 36053,
        end: 38679,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [NodeObject],
        kind: 178,
        type: [Circular] },
     kind: 176,
     checkType:
      NodeObject {
        pos: 36054,
        end: 36059,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [Circular],
        kind: 165,
        typeName: [IdentifierObject],
        id: 37469 },
     extendsType:
      NodeObject {
        pos: 36067,
        end: 36119,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [Circular],
        kind: 184,
        argument: [NodeObject],
        qualifier: [IdentifierObject],
        typeArguments: [Array],
        id: 37461 },
     trueType: [Circular],
     falseType:
      NodeObject {
        pos: 36404,
        end: 38678,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [Circular],
        kind: 176,
        checkType: [NodeObject],
        extendsType: [NodeObject],
        trueType: [NodeObject],
        falseType: [TokenObject] } },
  kind: 184,
  argument:
   NodeObject {
     pos: 36129,
     end: 36136,
     flags: 4194304,
     modifierFlagsCache: 0,
     transformFlags: 0,
     parent: [Circular],
     kind: 183,
     literal:
      TokenObject {
        pos: 36129,
        end: 36136,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [Circular],
        kind: 10,
        text: '../..' } },
  qualifier:
   IdentifierObject {
     pos: 36138,
     end: 36146,
     flags: 4194304,
     modifierFlagsCache: 0,
     transformFlags: 0,
     parent: [Circular],
     escapedText: 'IUsedRef',
     flowNode: { flags: 2 },
     id: 37466 },
  typeArguments:
   [ NodeObject {
       pos: 36147,
       end: 36401,
       flags: 4194304,
       modifierFlagsCache: 0,
       transformFlags: 0,
       parent: [Circular],
       kind: 176,
       checkType: [NodeObject],
       extendsType: [NodeObject],
       trueType: [NodeObject],
       falseType: [TokenObject],
       id: 37467 },
     pos: 36147,
     end: 36401 ],
  id: 37465 }
/home/anyhowstep/node-projects/tsql/tsql/dist/index.d.ts
/home/anyhowstep/node-projects/tsql/tsql/dist/index.d.ts
node.argument.literal TokenObject {
  pos: 36129,
  end: 36136,
  flags: 4194304,
  modifierFlagsCache: 0,
  transformFlags: 0,
  parent:
   NodeObject {
     pos: 36129,
     end: 36136,
     flags: 4194304,
     modifierFlagsCache: 0,
     transformFlags: 0,
     parent:
      NodeObject {
        pos: 36121,
        end: 36402,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [NodeObject],
        kind: 184,
        argument: [Circular],
        qualifier: [IdentifierObject],
        typeArguments: [Array],
        id: 37465 },
     kind: 183,
     literal: [Circular] },
  kind: 10,
  text: '../..' } NodeObject {
  pos: 36121,
  end: 36402,
  flags: 4194304,
  modifierFlagsCache: 0,
  transformFlags: 0,
  parent:
   NodeObject {
     pos: 36054,
     end: 38678,
     flags: 4194304,
     modifierFlagsCache: 0,
     transformFlags: 0,
     parent:
      NodeObject {
        pos: 36053,
        end: 38679,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [NodeObject],
        kind: 178,
        type: [Circular] },
     kind: 176,
     checkType:
      NodeObject {
        pos: 36054,
        end: 36059,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [Circular],
        kind: 165,
        typeName: [IdentifierObject],
        id: 37469 },
     extendsType:
      NodeObject {
        pos: 36067,
        end: 36119,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [Circular],
        kind: 184,
        argument: [NodeObject],
        qualifier: [IdentifierObject],
        typeArguments: [Array],
        id: 37461 },
     trueType: [Circular],
     falseType:
      NodeObject {
        pos: 36404,
        end: 38678,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [Circular],
        kind: 176,
        checkType: [NodeObject],
        extendsType: [NodeObject],
        trueType: [NodeObject],
        falseType: [TokenObject] } },
  kind: 184,
  argument:
   NodeObject {
     pos: 36129,
     end: 36136,
     flags: 4194304,
     modifierFlagsCache: 0,
     transformFlags: 0,
     parent: [Circular],
     kind: 183,
     literal:
      TokenObject {
        pos: 36129,
        end: 36136,
        flags: 4194304,
        modifierFlagsCache: 0,
        transformFlags: 0,
        parent: [Circular],
        kind: 10,
        text: '../..' } },
  qualifier:
   IdentifierObject {
     pos: 36138,
     end: 36146,
     flags: 4194304,
     modifierFlagsCache: 0,
     transformFlags: 0,
     parent: [Circular],
     escapedText: 'IUsedRef',
     flowNode: { flags: 2 },
     id: 37466 },
  typeArguments:
   [ NodeObject {
       pos: 36147,
       end: 36401,
       flags: 4194304,
       modifierFlagsCache: 0,
       transformFlags: 0,
       parent: [Circular],
       kind: 176,
       checkType: [NodeObject],
       extendsType: [NodeObject],
       trueType: [NodeObject],
       falseType: [TokenObject],
       id: 37467 },
     pos: 36147,
     end: 36401 ],
  id: 37465 }

^ Multiply the above output by, like, a billion


However, if I change it to,

//Notice the lack of parentheses
export type IntersectUsedRef<
    U extends AnyRawExpr
> = 
    UsedRef<U> extends never ?
    IUsedRef<{}> :
    UsedRefUtil.Intersect<
        UsedRef<U>
    >
;

I get a MUCH shorter output,
image

Notice how both output a "mere" 225 lines.
But the difference in the length of the 224th line is huuuuuge


I thought it was cool that parentheses would cause output to be expanded but I regret that now, lol.
It's terrible because I always uses parentheses and almost never intend for it to be expanded!

Also, I think this parentheses thing might partially be the cause of my other emit problems like in,
#31824

Or maybe even,
microsoft/TypeScript-Website#38

Because, well, I always use parentheses for complex type aliases.


@keithlayne Look! A world where unnecessary parentheses crashes your code!
Your dream world! 🚎

I didn't even need 400+ parentheses this time!
All it took was just one.


The compiler when I add one parentheses,
image

I am One Parentheses Man.