Skip to content

Commit

Permalink
Fix compiler crash with object rest in catch binding
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed May 21, 2019
1 parent c71423e commit 2e57465
Show file tree
Hide file tree
Showing 18 changed files with 267 additions and 205 deletions.
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Expand Up @@ -2515,7 +2515,7 @@ namespace ts {
break;

default:
Debug.fail(Debug.showSyntaxKind(thisContainer));
Debug.failBadSyntaxKind(thisContainer);
}
}

Expand Down
11 changes: 8 additions & 3 deletions src/compiler/checker.ts
Expand Up @@ -5695,7 +5695,7 @@ namespace ts {
type = getTypeOfEnumMember(symbol);
}
else {
return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol));
return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol));
}

if (!popTypeResolution()) {
Expand Down Expand Up @@ -25536,7 +25536,7 @@ namespace ts {
case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591
return DeclarationSpaces.ExportValue;
default:
return Debug.fail(Debug.showSyntaxKind(d));
return Debug.failBadSyntaxKind(d);
}
}
}
Expand Down Expand Up @@ -30162,12 +30162,17 @@ namespace ts {
return undefined;
}

function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) {
return isBindingElement(symbol.valueDeclaration)
&& walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause;
}

function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean {
if (symbol.flags & SymbolFlags.BlockScoped && !isSourceFile(symbol.valueDeclaration)) {
const links = getSymbolLinks(symbol);
if (links.isDeclarationWithCollidingName === undefined) {
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
if (isStatementWithLocals(container)) {
if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) {
const nodeLinks = getNodeLinks(symbol.valueDeclaration);
if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) {
// redeclaration - always should be renamed
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/core.ts
Expand Up @@ -1750,7 +1750,7 @@ namespace ts {
}

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

Expand Down
180 changes: 180 additions & 0 deletions src/compiler/debug.ts
@@ -0,0 +1,180 @@
/* @internal */
namespace ts {
export namespace Debug {
export function formatSymbol(symbol: Symbol): string {
return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`;
}

/**
* Formats an enum value as a string for debugging and debug assertions.
*/
export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) {
const members = getEnumMembers(enumObject);
if (value === 0) {
return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0";
}
if (isFlags) {
let result = "";
let remainingFlags = value;
for (let i = members.length - 1; i >= 0 && remainingFlags !== 0; i--) {
const [enumValue, enumName] = members[i];
if (enumValue !== 0 && (remainingFlags & enumValue) === enumValue) {
remainingFlags &= ~enumValue;
result = `${enumName}${result ? "|" : ""}${result}`;
}
}
if (remainingFlags === 0) {
return result;
}
}
else {
for (const [enumValue, enumName] of members) {
if (enumValue === value) {
return enumName;
}
}
}
return value.toString();
}

function getEnumMembers(enumObject: any) {
const result: [number, string][] = [];
for (const name in enumObject) {
const value = enumObject[name];
if (typeof value === "number") {
result.push([value, name]);
}
}

return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0]));
}

export function formatSyntaxKind(kind: SyntaxKind | undefined): string {
return formatEnum(kind, (<any>ts).SyntaxKind, /*isFlags*/ false);
}

export function formatNodeFlags(flags: NodeFlags | undefined): string {
return formatEnum(flags, (<any>ts).NodeFlags, /*isFlags*/ true);
}

export function formatModifierFlags(flags: ModifierFlags | undefined): string {
return formatEnum(flags, (<any>ts).ModifierFlags, /*isFlags*/ true);
}

export function formatTransformFlags(flags: TransformFlags | undefined): string {
return formatEnum(flags, (<any>ts).TransformFlags, /*isFlags*/ true);
}

export function formatEmitFlags(flags: EmitFlags | undefined): string {
return formatEnum(flags, (<any>ts).EmitFlags, /*isFlags*/ true);
}

export function formatSymbolFlags(flags: SymbolFlags | undefined): string {
return formatEnum(flags, (<any>ts).SymbolFlags, /*isFlags*/ true);
}

export function formatTypeFlags(flags: TypeFlags | undefined): string {
return formatEnum(flags, (<any>ts).TypeFlags, /*isFlags*/ true);
}

export function formatObjectFlags(flags: ObjectFlags | undefined): string {
return formatEnum(flags, (<any>ts).ObjectFlags, /*isFlags*/ true);
}

export function failBadSyntaxKind(node: Node, message?: string): never {
return fail(
`${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`,
failBadSyntaxKind);
}

export const assertEachNode = shouldAssert(AssertionLevel.Normal)
? (nodes: Node[], test: (node: Node) => boolean, message?: string): void => assert(
test === undefined || every(nodes, test),
message || "Unexpected node.",
() => `Node array did not pass test '${getFunctionName(test)}'.`,
assertEachNode)
: noop;

export const assertNode = shouldAssert(AssertionLevel.Normal)
? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert(
test === undefined || test(node),
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node!.kind)} did not pass test '${getFunctionName(test!)}'.`,
assertNode)
: noop;

export const assertOptionalNode = shouldAssert(AssertionLevel.Normal)
? (node: Node, test: (node: Node) => boolean, message?: string): void => assert(
test === undefined || node === undefined || test(node),
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node.kind)} did not pass test '${getFunctionName(test)}'.`,
assertOptionalNode)
: noop;

export const assertOptionalToken = shouldAssert(AssertionLevel.Normal)
? (node: Node, kind: SyntaxKind, message?: string): void => assert(
kind === undefined || node === undefined || node.kind === kind,
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node.kind)} was not a '${formatSyntaxKind(kind)}' token.`,
assertOptionalToken)
: noop;

export const assertMissingNode = shouldAssert(AssertionLevel.Normal)
? (node: Node, message?: string): void => assert(
node === undefined,
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node.kind)} was unexpected'.`,
assertMissingNode)
: noop;

let isDebugInfoEnabled = false;

/**
* Injects debug information into frequently used types.
*/
export function enableDebugInfo() {
if (isDebugInfoEnabled) return;

// Add additional properties in debug mode to assist with debugging.
Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, {
__debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } }
});

Object.defineProperties(objectAllocator.getTypeConstructor().prototype, {
__debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } },
__debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((<ObjectType>this).objectFlags) : ""; } },
__debugTypeToString: { value(this: Type) { return this.checker.typeToString(this); } },
});

const nodeConstructors = [
objectAllocator.getNodeConstructor(),
objectAllocator.getIdentifierConstructor(),
objectAllocator.getTokenConstructor(),
objectAllocator.getSourceFileConstructor()
];

for (const ctor of nodeConstructors) {
if (!ctor.prototype.hasOwnProperty("__debugKind")) {
Object.defineProperties(ctor.prototype, {
__debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } },
__debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } },
__debugModifierFlags: { get(this: Node) { return formatModifierFlags(getModifierFlagsNoCache(this)); } },
__debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } },
__debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } },
__debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } },
__debugGetText: {
value(this: Node, includeTrivia?: boolean) {
if (nodeIsSynthesized(this)) return "";
const parseNode = getParseTreeNode(this);
const sourceFile = parseNode && getSourceFileOfNode(parseNode);
return sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : "";
}
}
});
}
}

isDebugInfoEnabled = true;
}
}
}
24 changes: 24 additions & 0 deletions src/compiler/transformers/es2018.ts
Expand Up @@ -77,6 +77,8 @@ namespace ts {
return visitObjectLiteralExpression(node as ObjectLiteralExpression);
case SyntaxKind.BinaryExpression:
return visitBinaryExpression(node as BinaryExpression, noDestructuringValue);
case SyntaxKind.CatchClause:
return visitCatchClause(node as CatchClause);
case SyntaxKind.VariableDeclaration:
return visitVariableDeclaration(node as VariableDeclaration);
case SyntaxKind.ForOfStatement:
Expand Down Expand Up @@ -272,6 +274,28 @@ namespace ts {
return visitEachChild(node, visitor, context);
}

function visitCatchClause(node: CatchClause) {
if (node.variableDeclaration &&
isBindingPattern(node.variableDeclaration.name) &&
node.variableDeclaration.name.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
const name = getGeneratedNameForNode(node.variableDeclaration.name);
const updatedDecl = updateVariableDeclaration(node.variableDeclaration, node.variableDeclaration.name, /*type*/ undefined, name);
const visitedBindings = flattenDestructuringBinding(updatedDecl, visitor, context, FlattenLevel.ObjectRest);
let block = visitNode(node.block, visitor, isBlock);
if (some(visitedBindings)) {
block = updateBlock(block, [
createVariableStatement(/*modifiers*/ undefined, visitedBindings),
...block.statements,
]);
}
return updateCatchClause(
node,
updateVariableDeclaration(node.variableDeclaration, name, /*type*/ undefined, /*initializer*/ undefined),
block);
}
return visitEachChild(node, visitor, context);
}

/**
* Visits a VariableDeclaration node with a binding pattern.
*
Expand Down
1 change: 1 addition & 0 deletions src/compiler/tsconfig.json
Expand Up @@ -9,6 +9,7 @@
"files": [
"core.ts",
"performance.ts",
"debug.ts",
"semver.ts",

"types.ts",
Expand Down

0 comments on commit 2e57465

Please sign in to comment.