Skip to content

Commit 2e57465

Browse files
committed
Fix compiler crash with object rest in catch binding
1 parent c71423e commit 2e57465

File tree

18 files changed

+267
-205
lines changed

18 files changed

+267
-205
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2515,7 +2515,7 @@ namespace ts {
25152515
break;
25162516

25172517
default:
2518-
Debug.fail(Debug.showSyntaxKind(thisContainer));
2518+
Debug.failBadSyntaxKind(thisContainer);
25192519
}
25202520
}
25212521

src/compiler/checker.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5695,7 +5695,7 @@ namespace ts {
56955695
type = getTypeOfEnumMember(symbol);
56965696
}
56975697
else {
5698-
return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol));
5698+
return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol));
56995699
}
57005700

57015701
if (!popTypeResolution()) {
@@ -25536,7 +25536,7 @@ namespace ts {
2553625536
case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591
2553725537
return DeclarationSpaces.ExportValue;
2553825538
default:
25539-
return Debug.fail(Debug.showSyntaxKind(d));
25539+
return Debug.failBadSyntaxKind(d);
2554025540
}
2554125541
}
2554225542
}
@@ -30162,12 +30162,17 @@ namespace ts {
3016230162
return undefined;
3016330163
}
3016430164

30165+
function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) {
30166+
return isBindingElement(symbol.valueDeclaration)
30167+
&& walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause;
30168+
}
30169+
3016530170
function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean {
3016630171
if (symbol.flags & SymbolFlags.BlockScoped && !isSourceFile(symbol.valueDeclaration)) {
3016730172
const links = getSymbolLinks(symbol);
3016830173
if (links.isDeclarationWithCollidingName === undefined) {
3016930174
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
30170-
if (isStatementWithLocals(container)) {
30175+
if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) {
3017130176
const nodeLinks = getNodeLinks(symbol.valueDeclaration);
3017230177
if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) {
3017330178
// redeclaration - always should be renamed

src/compiler/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1750,7 +1750,7 @@ namespace ts {
17501750
}
17511751

17521752
export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never {
1753-
const detail = typeof member === "object" && "kind" in member && "pos" in member ? "SyntaxKind: " + showSyntaxKind(member as Node) : JSON.stringify(member);
1753+
const detail = typeof member === "object" && "kind" in member && "pos" in member && formatSyntaxKind ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member);
17541754
return fail(`${message} ${detail}`, stackCrawlMark || assertNever);
17551755
}
17561756

src/compiler/debug.ts

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/* @internal */
2+
namespace ts {
3+
export namespace Debug {
4+
export function formatSymbol(symbol: Symbol): string {
5+
return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`;
6+
}
7+
8+
/**
9+
* Formats an enum value as a string for debugging and debug assertions.
10+
*/
11+
export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) {
12+
const members = getEnumMembers(enumObject);
13+
if (value === 0) {
14+
return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0";
15+
}
16+
if (isFlags) {
17+
let result = "";
18+
let remainingFlags = value;
19+
for (let i = members.length - 1; i >= 0 && remainingFlags !== 0; i--) {
20+
const [enumValue, enumName] = members[i];
21+
if (enumValue !== 0 && (remainingFlags & enumValue) === enumValue) {
22+
remainingFlags &= ~enumValue;
23+
result = `${enumName}${result ? "|" : ""}${result}`;
24+
}
25+
}
26+
if (remainingFlags === 0) {
27+
return result;
28+
}
29+
}
30+
else {
31+
for (const [enumValue, enumName] of members) {
32+
if (enumValue === value) {
33+
return enumName;
34+
}
35+
}
36+
}
37+
return value.toString();
38+
}
39+
40+
function getEnumMembers(enumObject: any) {
41+
const result: [number, string][] = [];
42+
for (const name in enumObject) {
43+
const value = enumObject[name];
44+
if (typeof value === "number") {
45+
result.push([value, name]);
46+
}
47+
}
48+
49+
return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0]));
50+
}
51+
52+
export function formatSyntaxKind(kind: SyntaxKind | undefined): string {
53+
return formatEnum(kind, (<any>ts).SyntaxKind, /*isFlags*/ false);
54+
}
55+
56+
export function formatNodeFlags(flags: NodeFlags | undefined): string {
57+
return formatEnum(flags, (<any>ts).NodeFlags, /*isFlags*/ true);
58+
}
59+
60+
export function formatModifierFlags(flags: ModifierFlags | undefined): string {
61+
return formatEnum(flags, (<any>ts).ModifierFlags, /*isFlags*/ true);
62+
}
63+
64+
export function formatTransformFlags(flags: TransformFlags | undefined): string {
65+
return formatEnum(flags, (<any>ts).TransformFlags, /*isFlags*/ true);
66+
}
67+
68+
export function formatEmitFlags(flags: EmitFlags | undefined): string {
69+
return formatEnum(flags, (<any>ts).EmitFlags, /*isFlags*/ true);
70+
}
71+
72+
export function formatSymbolFlags(flags: SymbolFlags | undefined): string {
73+
return formatEnum(flags, (<any>ts).SymbolFlags, /*isFlags*/ true);
74+
}
75+
76+
export function formatTypeFlags(flags: TypeFlags | undefined): string {
77+
return formatEnum(flags, (<any>ts).TypeFlags, /*isFlags*/ true);
78+
}
79+
80+
export function formatObjectFlags(flags: ObjectFlags | undefined): string {
81+
return formatEnum(flags, (<any>ts).ObjectFlags, /*isFlags*/ true);
82+
}
83+
84+
export function failBadSyntaxKind(node: Node, message?: string): never {
85+
return fail(
86+
`${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`,
87+
failBadSyntaxKind);
88+
}
89+
90+
export const assertEachNode = shouldAssert(AssertionLevel.Normal)
91+
? (nodes: Node[], test: (node: Node) => boolean, message?: string): void => assert(
92+
test === undefined || every(nodes, test),
93+
message || "Unexpected node.",
94+
() => `Node array did not pass test '${getFunctionName(test)}'.`,
95+
assertEachNode)
96+
: noop;
97+
98+
export const assertNode = shouldAssert(AssertionLevel.Normal)
99+
? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert(
100+
test === undefined || test(node),
101+
message || "Unexpected node.",
102+
() => `Node ${formatSyntaxKind(node!.kind)} did not pass test '${getFunctionName(test!)}'.`,
103+
assertNode)
104+
: noop;
105+
106+
export const assertOptionalNode = shouldAssert(AssertionLevel.Normal)
107+
? (node: Node, test: (node: Node) => boolean, message?: string): void => assert(
108+
test === undefined || node === undefined || test(node),
109+
message || "Unexpected node.",
110+
() => `Node ${formatSyntaxKind(node.kind)} did not pass test '${getFunctionName(test)}'.`,
111+
assertOptionalNode)
112+
: noop;
113+
114+
export const assertOptionalToken = shouldAssert(AssertionLevel.Normal)
115+
? (node: Node, kind: SyntaxKind, message?: string): void => assert(
116+
kind === undefined || node === undefined || node.kind === kind,
117+
message || "Unexpected node.",
118+
() => `Node ${formatSyntaxKind(node.kind)} was not a '${formatSyntaxKind(kind)}' token.`,
119+
assertOptionalToken)
120+
: noop;
121+
122+
export const assertMissingNode = shouldAssert(AssertionLevel.Normal)
123+
? (node: Node, message?: string): void => assert(
124+
node === undefined,
125+
message || "Unexpected node.",
126+
() => `Node ${formatSyntaxKind(node.kind)} was unexpected'.`,
127+
assertMissingNode)
128+
: noop;
129+
130+
let isDebugInfoEnabled = false;
131+
132+
/**
133+
* Injects debug information into frequently used types.
134+
*/
135+
export function enableDebugInfo() {
136+
if (isDebugInfoEnabled) return;
137+
138+
// Add additional properties in debug mode to assist with debugging.
139+
Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, {
140+
__debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } }
141+
});
142+
143+
Object.defineProperties(objectAllocator.getTypeConstructor().prototype, {
144+
__debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } },
145+
__debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((<ObjectType>this).objectFlags) : ""; } },
146+
__debugTypeToString: { value(this: Type) { return this.checker.typeToString(this); } },
147+
});
148+
149+
const nodeConstructors = [
150+
objectAllocator.getNodeConstructor(),
151+
objectAllocator.getIdentifierConstructor(),
152+
objectAllocator.getTokenConstructor(),
153+
objectAllocator.getSourceFileConstructor()
154+
];
155+
156+
for (const ctor of nodeConstructors) {
157+
if (!ctor.prototype.hasOwnProperty("__debugKind")) {
158+
Object.defineProperties(ctor.prototype, {
159+
__debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } },
160+
__debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } },
161+
__debugModifierFlags: { get(this: Node) { return formatModifierFlags(getModifierFlagsNoCache(this)); } },
162+
__debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } },
163+
__debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } },
164+
__debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } },
165+
__debugGetText: {
166+
value(this: Node, includeTrivia?: boolean) {
167+
if (nodeIsSynthesized(this)) return "";
168+
const parseNode = getParseTreeNode(this);
169+
const sourceFile = parseNode && getSourceFileOfNode(parseNode);
170+
return sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : "";
171+
}
172+
}
173+
});
174+
}
175+
}
176+
177+
isDebugInfoEnabled = true;
178+
}
179+
}
180+
}

src/compiler/transformers/es2018.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ namespace ts {
7777
return visitObjectLiteralExpression(node as ObjectLiteralExpression);
7878
case SyntaxKind.BinaryExpression:
7979
return visitBinaryExpression(node as BinaryExpression, noDestructuringValue);
80+
case SyntaxKind.CatchClause:
81+
return visitCatchClause(node as CatchClause);
8082
case SyntaxKind.VariableDeclaration:
8183
return visitVariableDeclaration(node as VariableDeclaration);
8284
case SyntaxKind.ForOfStatement:
@@ -272,6 +274,28 @@ namespace ts {
272274
return visitEachChild(node, visitor, context);
273275
}
274276

277+
function visitCatchClause(node: CatchClause) {
278+
if (node.variableDeclaration &&
279+
isBindingPattern(node.variableDeclaration.name) &&
280+
node.variableDeclaration.name.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
281+
const name = getGeneratedNameForNode(node.variableDeclaration.name);
282+
const updatedDecl = updateVariableDeclaration(node.variableDeclaration, node.variableDeclaration.name, /*type*/ undefined, name);
283+
const visitedBindings = flattenDestructuringBinding(updatedDecl, visitor, context, FlattenLevel.ObjectRest);
284+
let block = visitNode(node.block, visitor, isBlock);
285+
if (some(visitedBindings)) {
286+
block = updateBlock(block, [
287+
createVariableStatement(/*modifiers*/ undefined, visitedBindings),
288+
...block.statements,
289+
]);
290+
}
291+
return updateCatchClause(
292+
node,
293+
updateVariableDeclaration(node.variableDeclaration, name, /*type*/ undefined, /*initializer*/ undefined),
294+
block);
295+
}
296+
return visitEachChild(node, visitor, context);
297+
}
298+
275299
/**
276300
* Visits a VariableDeclaration node with a binding pattern.
277301
*

src/compiler/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"files": [
1010
"core.ts",
1111
"performance.ts",
12+
"debug.ts",
1213
"semver.ts",
1314

1415
"types.ts",

0 commit comments

Comments
 (0)