Repository navigation
[API Question] Parentheses affects declaration emit. Intentional? #32824
Description
Activity
- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Aug 14, 2019 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.
Reacted by AnyhowStep and Avi NerenbergOoooooo
I just learned a new trick!
Reacted by Avi NerenbergIf 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.
Reacted by AnyhowStep, resynth1943 and Avi NerenbergReacted by AnyhowStep, resynth1943 and Avi NerenbergWesley Wigham (@weswigham) Ryan Cavanaugh (@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.tsoutput.Bad enough that the project will emit
.d.tsfiles fine...
But other projects using the.d.tsfiles 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!

And if I try to use this emitted
.d.tsfile, 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> > ;
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,
#31824Or maybe even,
microsoft/TypeScript-Website#38Because, well, I always use parentheses for complex type aliases.
Keith Layne (@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,

I am One Parentheses Man.
- addedBugA bug in TypeScriptA bug in TypeScriptand removedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Aug 15, 2019 - locked as resolved and limited conversation to collaborators
on Oct 21, 2025

Consider the following snippet,
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
InvalidKeystype is not expanded because I did not wrap the type in parentheses.Obligatory Keith Layne (@keithlayne) , who hates unnecessary parentheses 🚎