Skip to content

Commit

Permalink
Merge pull request #18300 from Microsoft/correctlyCacheTaggedTemplates
Browse files Browse the repository at this point in the history
Correctly cache tagged template objects in modules
  • Loading branch information
DanielRosenwasser authored Oct 3, 2017
2 parents 5c7a3d0 + 8fd638c commit 301c90c
Show file tree
Hide file tree
Showing 47 changed files with 729 additions and 175 deletions.
4 changes: 4 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16821,6 +16821,9 @@ namespace ts {
}

function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
if (languageVersion < ScriptTarget.ES2015) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
}
return getReturnTypeOfSignature(getResolvedSignature(node));
}

Expand Down Expand Up @@ -24217,6 +24220,7 @@ namespace ts {
case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator";
case ExternalEmitHelpers.AsyncValues: return "__asyncValues";
case ExternalEmitHelpers.ExportStar: return "__exportStar";
case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject";
default: Debug.fail("Unrecognized helper");
}
}
Expand Down
21 changes: 13 additions & 8 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3877,15 +3877,15 @@ namespace ts {
* @param expression The Expression node.
*/
export function parenthesizeForNew(expression: Expression): LeftHandSideExpression {
const emittedExpression = skipPartiallyEmittedExpressions(expression);
switch (emittedExpression.kind) {
const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true);
switch (leftmostExpr.kind) {
case SyntaxKind.CallExpression:
return createParen(expression);

case SyntaxKind.NewExpression:
return (<NewExpression>emittedExpression).arguments
? <LeftHandSideExpression>expression
: createParen(expression);
return !(leftmostExpr as NewExpression).arguments
? createParen(expression)
: <LeftHandSideExpression>expression;
}

return parenthesizeForAccess(expression);
Expand Down Expand Up @@ -3966,7 +3966,7 @@ namespace ts {
}
}

const leftmostExpressionKind = getLeftmostExpression(emittedExpression).kind;
const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind;
if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) {
return setTextRange(createParen(expression), expression);
}
Expand Down Expand Up @@ -4012,7 +4012,7 @@ namespace ts {
}
}

function getLeftmostExpression(node: Expression): Expression {
function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) {
while (true) {
switch (node.kind) {
case SyntaxKind.PostfixUnaryExpression:
Expand All @@ -4028,6 +4028,10 @@ namespace ts {
continue;

case SyntaxKind.CallExpression:
if (stopAtCallExpressions) {
return node;
}
// falls through
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.PropertyAccessExpression:
node = (<CallExpression | PropertyAccessExpression | ElementAccessExpression>node).expression;
Expand All @@ -4040,10 +4044,11 @@ namespace ts {

return node;
}

}

export function parenthesizeConciseBody(body: ConciseBody): ConciseBody {
if (!isBlock(body) && getLeftmostExpression(body).kind === SyntaxKind.ObjectLiteralExpression) {
if (!isBlock(body) && getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression) {
return setTextRange(createParen(<Expression>body), body);
}

Expand Down
73 changes: 59 additions & 14 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,13 @@ namespace ts {
let currentSourceFile: SourceFile;
let currentText: string;
let hierarchyFacts: HierarchyFacts;
let taggedTemplateStringDeclarations: VariableDeclaration[];

function recordTaggedTemplateString(temp: Identifier) {
taggedTemplateStringDeclarations = append(
taggedTemplateStringDeclarations,
createVariableDeclaration(temp));
}

/**
* Used to track if we are emitting body of the converted loop
Expand Down Expand Up @@ -307,6 +314,7 @@ namespace ts {

currentSourceFile = undefined;
currentText = undefined;
taggedTemplateStringDeclarations = undefined;
hierarchyFacts = HierarchyFacts.None;
return visited;
}
Expand Down Expand Up @@ -520,6 +528,11 @@ namespace ts {
addCaptureThisForNodeIfNeeded(statements, node);
statementOffset = addCustomPrologue(statements, node.statements, statementOffset, visitor);
addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset));
if (taggedTemplateStringDeclarations) {
statements.push(
createVariableStatement(/*modifiers*/ undefined,
createVariableDeclarationList(taggedTemplateStringDeclarations)));
}
addRange(statements, endLexicalEnvironment());
exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None);
return updateSourceFileNode(
Expand Down Expand Up @@ -3636,11 +3649,10 @@ namespace ts {
// Visit the tag expression
const tag = visitNode(node.tag, visitor, isExpression);

// Allocate storage for the template site object
const temp = createTempVariable(hoistVariableDeclaration);

// Build up the template arguments and the raw and cooked strings for the template.
const templateArguments: Expression[] = [temp];
// We start out with 'undefined' for the first argument and revisit later
// to avoid walking over the template string twice and shifting all our arguments over after the fact.
const templateArguments: Expression[] = [undefined];
const cookedStrings: Expression[] = [];
const rawStrings: Expression[] = [];
const template = node.template;
Expand All @@ -3658,16 +3670,25 @@ namespace ts {
}
}

// NOTE: The parentheses here is entirely optional as we are now able to auto-
// parenthesize when rebuilding the tree. This should be removed in a
// future version. It is here for now to match our existing emit.
return createParen(
inlineExpressions([
createAssignment(temp, createArrayLiteral(cookedStrings)),
createAssignment(createPropertyAccess(temp, "raw"), createArrayLiteral(rawStrings)),
createCall(tag, /*typeArguments*/ undefined, templateArguments)
])
);
const helperCall = createTemplateObjectHelper(context, createArrayLiteral(cookedStrings), createArrayLiteral(rawStrings));

// Create a variable to cache the template object if we're in a module.
// Do not do this in the global scope, as any variable we currently generate could conflict with
// variables from outside of the current compilation. In the future, we can revisit this behavior.
if (isExternalModule(currentSourceFile)) {
const tempVar = createTempVariable(recordTaggedTemplateString);
templateArguments[0] = createLogicalOr(
tempVar,
createAssignment(
tempVar,
helperCall)
);
}
else {
templateArguments[0] = helperCall;
}

return createCall(tag, /*typeArguments*/ undefined, templateArguments);
}

/**
Expand Down Expand Up @@ -4036,6 +4057,18 @@ namespace ts {
);
}

function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) {
context.requestEmitHelper(templateObjectHelper);
return createCall(
getHelperName("__makeTemplateObject"),
/*typeArguments*/ undefined,
[
cooked,
raw
]
);
}

const extendsHelper: EmitHelper = {
name: "typescript:extends",
scoped: false,
Expand All @@ -4052,4 +4085,16 @@ namespace ts {
};
})();`
};

const templateObjectHelper: EmitHelper = {
name: "typescript:makeTemplateObject",
scoped: false,
priority: 0,
text: `
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};`
};

}
37 changes: 19 additions & 18 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4316,22 +4316,25 @@ namespace ts {
*/
/* @internal */
export const enum ExternalEmitHelpers {
Extends = 1 << 0, // __extends (used by the ES2015 class transformation)
Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations)
Rest = 1 << 2, // __rest (used by ESNext object rest transformation)
Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation)
Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation)
Param = 1 << 5, // __param (used by TypeScript decorators transformation)
Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation)
Generator = 1 << 7, // __generator (used by ES2015 generator transformation)
Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations)
Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation)
Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations)
Await = 1 << 11, // __await (used by ES2017 async generator transformation)
AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation)
AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation)
AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation)
ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation)
Extends = 1 << 0, // __extends (used by the ES2015 class transformation)
Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations)
Rest = 1 << 2, // __rest (used by ESNext object rest transformation)
Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation)
Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation)
Param = 1 << 5, // __param (used by TypeScript decorators transformation)
Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation)
Generator = 1 << 7, // __generator (used by ES2015 generator transformation)
Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations)
Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation)
Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations)
Await = 1 << 11, // __await (used by ES2017 async generator transformation)
AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation)
AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation)
AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation)
ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation)
MakeTemplateObject = 1 << 16, // __makeTemplateObject (used for constructing template string array objects)
FirstEmitHelper = Extends,
LastEmitHelper = MakeTemplateObject,

// Helpers included by ES2015 for..of
ForOfIncludes = Values,
Expand All @@ -4348,8 +4351,6 @@ namespace ts {
// Helpers included by ES2015 spread
SpreadIncludes = Read | Spread,

FirstEmitHelper = Extends,
LastEmitHelper = ExportStar
}

export const enum EmitHint {
Expand Down
25 changes: 25 additions & 0 deletions src/harness/unittests/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,31 @@ namespace ts {
createSourceFile("source.ts", "", ScriptTarget.ES2015)
));

printsCorrectly("newExpressionWithPropertyAccessOnCallExpression", {}, printer => printer.printNode(
EmitHint.Unspecified,
createNew(
createPropertyAccess(
createCall(
createIdentifier("f"), /*typeArguments*/ undefined, /*argumentsArray*/ undefined),
"x"),
/*typeArguments*/ undefined,
/*argumentsArray*/ undefined
),
createSourceFile("source.ts", "", ScriptTarget.ESNext))
);

printsCorrectly("newExpressionOnConditionalExpression", {}, printer => printer.printNode(
EmitHint.Unspecified,
createNew(
createConditional(
createIdentifier("x"), createToken(SyntaxKind.QuestionToken),
createIdentifier("y"), createToken(SyntaxKind.ColonToken),
createIdentifier("z")),
/*typeArguments*/ undefined,
/*argumentsArray*/ undefined
),
createSourceFile("source.ts", "", ScriptTarget.ESNext))
);

printsCorrectly("emptyGlobalAugmentation", {}, printer => printer.printNode(
EmitHint.Unspecified,
Expand Down
9 changes: 6 additions & 3 deletions tests/baselines/reference/asOperator3.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ var g = tag `Hello ${123} World` as string;
var h = tag `Hello` as string;

//// [asOperator3.js]
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
var a = "" + (123 + 456);
var b = "leading " + (123 + 456);
var c = 123 + 456 + " trailing";
var d = "Hello " + 123 + " World";
var e = "Hello";
var f = 1 + (1 + " end of string");
var g = (_a = ["Hello ", " World"], _a.raw = ["Hello ", " World"], tag(_a, 123));
var h = (_b = ["Hello"], _b.raw = ["Hello"], tag(_b));
var _a, _b;
var g = tag(__makeTemplateObject(["Hello ", " World"], ["Hello ", " World"]), 123);
var h = tag(__makeTemplateObject(["Hello"], ["Hello"]));
7 changes: 5 additions & 2 deletions tests/baselines/reference/asOperatorASI.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ as(Foo); // should emit


//// [asOperatorASI.js]
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
var Foo = /** @class */ (function () {
function Foo() {
}
return Foo;
}());
// Example 1
var x = 10;
(_a = ["Hello world"], _a.raw = ["Hello world"], as(_a)); // should not error
as(__makeTemplateObject(["Hello world"], ["Hello world"])); // should not error
// Example 2
var y = 20;
as(Foo); // should emit
var _a;
26 changes: 26 additions & 0 deletions tests/baselines/reference/importHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class C {
method(@dec x: number) {
}
}

function id<T>(x: T) {
return x;
}

export const result = id`hello world`;

//// [script.ts]
class A { }
Expand All @@ -23,6 +29,12 @@ class C {
method(@dec x: number) {
}
}

function id<T>(x: T) {
return x;
}

const result = id`hello world`;

//// [tslib.d.ts]
export declare function __extends(d: Function, b: Function): void;
Expand All @@ -31,6 +43,7 @@ export declare function __decorate(decorators: Function[], target: any, key?: st
export declare function __param(paramIndex: number, decorator: Function): Function;
export declare function __metadata(metadataKey: any, metadataValue: any): Function;
export declare function __awaiter(thisArg: any, _arguments: any, P: Function, generator: Function): any;
export declare function __makeTemplateObject(cooked: string[], raw: string[]): TemplateStringsArray;


//// [external.js]
Expand Down Expand Up @@ -67,6 +80,11 @@ var C = /** @class */ (function () {
], C);
return C;
}());
function id(x) {
return x;
}
exports.result = id(_a || (_a = tslib_1.__makeTemplateObject(["hello world"], ["hello world"])));
var _a;
//// [script.js]
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
Expand All @@ -78,6 +96,10 @@ var __extends = (this && this.__extends) || (function () {
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
Expand Down Expand Up @@ -118,3 +140,7 @@ var C = /** @class */ (function () {
], C);
return C;
}());
function id(x) {
return x;
}
var result = id(__makeTemplateObject(["hello world"], ["hello world"]));
Loading

0 comments on commit 301c90c

Please sign in to comment.