Skip to content

Commit 301c90c

Browse files
Merge pull request #18300 from Microsoft/correctlyCacheTaggedTemplates
Correctly cache tagged template objects in modules
2 parents 5c7a3d0 + 8fd638c commit 301c90c

File tree

47 files changed

+729
-175
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+729
-175
lines changed

src/compiler/checker.ts

+4
Original file line numberDiff line numberDiff line change
@@ -16821,6 +16821,9 @@ namespace ts {
1682116821
}
1682216822

1682316823
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
16824+
if (languageVersion < ScriptTarget.ES2015) {
16825+
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
16826+
}
1682416827
return getReturnTypeOfSignature(getResolvedSignature(node));
1682516828
}
1682616829

@@ -24217,6 +24220,7 @@ namespace ts {
2421724220
case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator";
2421824221
case ExternalEmitHelpers.AsyncValues: return "__asyncValues";
2421924222
case ExternalEmitHelpers.ExportStar: return "__exportStar";
24223+
case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject";
2422024224
default: Debug.fail("Unrecognized helper");
2422124225
}
2422224226
}

src/compiler/factory.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -3877,15 +3877,15 @@ namespace ts {
38773877
* @param expression The Expression node.
38783878
*/
38793879
export function parenthesizeForNew(expression: Expression): LeftHandSideExpression {
3880-
const emittedExpression = skipPartiallyEmittedExpressions(expression);
3881-
switch (emittedExpression.kind) {
3880+
const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true);
3881+
switch (leftmostExpr.kind) {
38823882
case SyntaxKind.CallExpression:
38833883
return createParen(expression);
38843884

38853885
case SyntaxKind.NewExpression:
3886-
return (<NewExpression>emittedExpression).arguments
3887-
? <LeftHandSideExpression>expression
3888-
: createParen(expression);
3886+
return !(leftmostExpr as NewExpression).arguments
3887+
? createParen(expression)
3888+
: <LeftHandSideExpression>expression;
38893889
}
38903890

38913891
return parenthesizeForAccess(expression);
@@ -3966,7 +3966,7 @@ namespace ts {
39663966
}
39673967
}
39683968

3969-
const leftmostExpressionKind = getLeftmostExpression(emittedExpression).kind;
3969+
const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind;
39703970
if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) {
39713971
return setTextRange(createParen(expression), expression);
39723972
}
@@ -4012,7 +4012,7 @@ namespace ts {
40124012
}
40134013
}
40144014

4015-
function getLeftmostExpression(node: Expression): Expression {
4015+
function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) {
40164016
while (true) {
40174017
switch (node.kind) {
40184018
case SyntaxKind.PostfixUnaryExpression:
@@ -4028,6 +4028,10 @@ namespace ts {
40284028
continue;
40294029

40304030
case SyntaxKind.CallExpression:
4031+
if (stopAtCallExpressions) {
4032+
return node;
4033+
}
4034+
// falls through
40314035
case SyntaxKind.ElementAccessExpression:
40324036
case SyntaxKind.PropertyAccessExpression:
40334037
node = (<CallExpression | PropertyAccessExpression | ElementAccessExpression>node).expression;
@@ -4040,10 +4044,11 @@ namespace ts {
40404044

40414045
return node;
40424046
}
4047+
40434048
}
40444049

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

src/compiler/transformers/es2015.ts

+59-14
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,13 @@ namespace ts {
279279
let currentSourceFile: SourceFile;
280280
let currentText: string;
281281
let hierarchyFacts: HierarchyFacts;
282+
let taggedTemplateStringDeclarations: VariableDeclaration[];
283+
284+
function recordTaggedTemplateString(temp: Identifier) {
285+
taggedTemplateStringDeclarations = append(
286+
taggedTemplateStringDeclarations,
287+
createVariableDeclaration(temp));
288+
}
282289

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

308315
currentSourceFile = undefined;
309316
currentText = undefined;
317+
taggedTemplateStringDeclarations = undefined;
310318
hierarchyFacts = HierarchyFacts.None;
311319
return visited;
312320
}
@@ -520,6 +528,11 @@ namespace ts {
520528
addCaptureThisForNodeIfNeeded(statements, node);
521529
statementOffset = addCustomPrologue(statements, node.statements, statementOffset, visitor);
522530
addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset));
531+
if (taggedTemplateStringDeclarations) {
532+
statements.push(
533+
createVariableStatement(/*modifiers*/ undefined,
534+
createVariableDeclarationList(taggedTemplateStringDeclarations)));
535+
}
523536
addRange(statements, endLexicalEnvironment());
524537
exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None);
525538
return updateSourceFileNode(
@@ -3636,11 +3649,10 @@ namespace ts {
36363649
// Visit the tag expression
36373650
const tag = visitNode(node.tag, visitor, isExpression);
36383651

3639-
// Allocate storage for the template site object
3640-
const temp = createTempVariable(hoistVariableDeclaration);
3641-
36423652
// Build up the template arguments and the raw and cooked strings for the template.
3643-
const templateArguments: Expression[] = [temp];
3653+
// We start out with 'undefined' for the first argument and revisit later
3654+
// to avoid walking over the template string twice and shifting all our arguments over after the fact.
3655+
const templateArguments: Expression[] = [undefined];
36443656
const cookedStrings: Expression[] = [];
36453657
const rawStrings: Expression[] = [];
36463658
const template = node.template;
@@ -3658,16 +3670,25 @@ namespace ts {
36583670
}
36593671
}
36603672

3661-
// NOTE: The parentheses here is entirely optional as we are now able to auto-
3662-
// parenthesize when rebuilding the tree. This should be removed in a
3663-
// future version. It is here for now to match our existing emit.
3664-
return createParen(
3665-
inlineExpressions([
3666-
createAssignment(temp, createArrayLiteral(cookedStrings)),
3667-
createAssignment(createPropertyAccess(temp, "raw"), createArrayLiteral(rawStrings)),
3668-
createCall(tag, /*typeArguments*/ undefined, templateArguments)
3669-
])
3670-
);
3673+
const helperCall = createTemplateObjectHelper(context, createArrayLiteral(cookedStrings), createArrayLiteral(rawStrings));
3674+
3675+
// Create a variable to cache the template object if we're in a module.
3676+
// Do not do this in the global scope, as any variable we currently generate could conflict with
3677+
// variables from outside of the current compilation. In the future, we can revisit this behavior.
3678+
if (isExternalModule(currentSourceFile)) {
3679+
const tempVar = createTempVariable(recordTaggedTemplateString);
3680+
templateArguments[0] = createLogicalOr(
3681+
tempVar,
3682+
createAssignment(
3683+
tempVar,
3684+
helperCall)
3685+
);
3686+
}
3687+
else {
3688+
templateArguments[0] = helperCall;
3689+
}
3690+
3691+
return createCall(tag, /*typeArguments*/ undefined, templateArguments);
36713692
}
36723693

36733694
/**
@@ -4036,6 +4057,18 @@ namespace ts {
40364057
);
40374058
}
40384059

4060+
function createTemplateObjectHelper(context: TransformationContext, cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) {
4061+
context.requestEmitHelper(templateObjectHelper);
4062+
return createCall(
4063+
getHelperName("__makeTemplateObject"),
4064+
/*typeArguments*/ undefined,
4065+
[
4066+
cooked,
4067+
raw
4068+
]
4069+
);
4070+
}
4071+
40394072
const extendsHelper: EmitHelper = {
40404073
name: "typescript:extends",
40414074
scoped: false,
@@ -4052,4 +4085,16 @@ namespace ts {
40524085
};
40534086
})();`
40544087
};
4088+
4089+
const templateObjectHelper: EmitHelper = {
4090+
name: "typescript:makeTemplateObject",
4091+
scoped: false,
4092+
priority: 0,
4093+
text: `
4094+
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
4095+
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
4096+
return cooked;
4097+
};`
4098+
};
4099+
40554100
}

src/compiler/types.ts

+19-18
Original file line numberDiff line numberDiff line change
@@ -4316,22 +4316,25 @@ namespace ts {
43164316
*/
43174317
/* @internal */
43184318
export const enum ExternalEmitHelpers {
4319-
Extends = 1 << 0, // __extends (used by the ES2015 class transformation)
4320-
Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations)
4321-
Rest = 1 << 2, // __rest (used by ESNext object rest transformation)
4322-
Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation)
4323-
Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation)
4324-
Param = 1 << 5, // __param (used by TypeScript decorators transformation)
4325-
Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation)
4326-
Generator = 1 << 7, // __generator (used by ES2015 generator transformation)
4327-
Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations)
4328-
Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation)
4329-
Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations)
4330-
Await = 1 << 11, // __await (used by ES2017 async generator transformation)
4331-
AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation)
4332-
AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation)
4333-
AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation)
4334-
ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation)
4319+
Extends = 1 << 0, // __extends (used by the ES2015 class transformation)
4320+
Assign = 1 << 1, // __assign (used by Jsx and ESNext object spread transformations)
4321+
Rest = 1 << 2, // __rest (used by ESNext object rest transformation)
4322+
Decorate = 1 << 3, // __decorate (used by TypeScript decorators transformation)
4323+
Metadata = 1 << 4, // __metadata (used by TypeScript decorators transformation)
4324+
Param = 1 << 5, // __param (used by TypeScript decorators transformation)
4325+
Awaiter = 1 << 6, // __awaiter (used by ES2017 async functions transformation)
4326+
Generator = 1 << 7, // __generator (used by ES2015 generator transformation)
4327+
Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations)
4328+
Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation)
4329+
Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations)
4330+
Await = 1 << 11, // __await (used by ES2017 async generator transformation)
4331+
AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation)
4332+
AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation)
4333+
AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation)
4334+
ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation)
4335+
MakeTemplateObject = 1 << 16, // __makeTemplateObject (used for constructing template string array objects)
4336+
FirstEmitHelper = Extends,
4337+
LastEmitHelper = MakeTemplateObject,
43354338

43364339
// Helpers included by ES2015 for..of
43374340
ForOfIncludes = Values,
@@ -4348,8 +4351,6 @@ namespace ts {
43484351
// Helpers included by ES2015 spread
43494352
SpreadIncludes = Read | Spread,
43504353

4351-
FirstEmitHelper = Extends,
4352-
LastEmitHelper = ExportStar
43534354
}
43544355

43554356
export const enum EmitHint {

src/harness/unittests/printer.ts

+25
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,31 @@ namespace ts {
110110
createSourceFile("source.ts", "", ScriptTarget.ES2015)
111111
));
112112

113+
printsCorrectly("newExpressionWithPropertyAccessOnCallExpression", {}, printer => printer.printNode(
114+
EmitHint.Unspecified,
115+
createNew(
116+
createPropertyAccess(
117+
createCall(
118+
createIdentifier("f"), /*typeArguments*/ undefined, /*argumentsArray*/ undefined),
119+
"x"),
120+
/*typeArguments*/ undefined,
121+
/*argumentsArray*/ undefined
122+
),
123+
createSourceFile("source.ts", "", ScriptTarget.ESNext))
124+
);
125+
126+
printsCorrectly("newExpressionOnConditionalExpression", {}, printer => printer.printNode(
127+
EmitHint.Unspecified,
128+
createNew(
129+
createConditional(
130+
createIdentifier("x"), createToken(SyntaxKind.QuestionToken),
131+
createIdentifier("y"), createToken(SyntaxKind.ColonToken),
132+
createIdentifier("z")),
133+
/*typeArguments*/ undefined,
134+
/*argumentsArray*/ undefined
135+
),
136+
createSourceFile("source.ts", "", ScriptTarget.ESNext))
137+
);
113138

114139
printsCorrectly("emptyGlobalAugmentation", {}, printer => printer.printNode(
115140
EmitHint.Unspecified,

tests/baselines/reference/asOperator3.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ var g = tag `Hello ${123} World` as string;
1111
var h = tag `Hello` as string;
1212

1313
//// [asOperator3.js]
14+
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
15+
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
16+
return cooked;
17+
};
1418
var a = "" + (123 + 456);
1519
var b = "leading " + (123 + 456);
1620
var c = 123 + 456 + " trailing";
1721
var d = "Hello " + 123 + " World";
1822
var e = "Hello";
1923
var f = 1 + (1 + " end of string");
20-
var g = (_a = ["Hello ", " World"], _a.raw = ["Hello ", " World"], tag(_a, 123));
21-
var h = (_b = ["Hello"], _b.raw = ["Hello"], tag(_b));
22-
var _a, _b;
24+
var g = tag(__makeTemplateObject(["Hello ", " World"], ["Hello ", " World"]), 123);
25+
var h = tag(__makeTemplateObject(["Hello"], ["Hello"]));

tests/baselines/reference/asOperatorASI.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ as(Foo); // should emit
1212

1313

1414
//// [asOperatorASI.js]
15+
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
16+
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
17+
return cooked;
18+
};
1519
var Foo = /** @class */ (function () {
1620
function Foo() {
1721
}
1822
return Foo;
1923
}());
2024
// Example 1
2125
var x = 10;
22-
(_a = ["Hello world"], _a.raw = ["Hello world"], as(_a)); // should not error
26+
as(__makeTemplateObject(["Hello world"], ["Hello world"])); // should not error
2327
// Example 2
2428
var y = 20;
2529
as(Foo); // should emit
26-
var _a;

tests/baselines/reference/importHelpers.js

+26
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ class C {
1111
method(@dec x: number) {
1212
}
1313
}
14+
15+
function id<T>(x: T) {
16+
return x;
17+
}
18+
19+
export const result = id`hello world`;
1420

1521
//// [script.ts]
1622
class A { }
@@ -23,6 +29,12 @@ class C {
2329
method(@dec x: number) {
2430
}
2531
}
32+
33+
function id<T>(x: T) {
34+
return x;
35+
}
36+
37+
const result = id`hello world`;
2638

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

3548

3649
//// [external.js]
@@ -67,6 +80,11 @@ var C = /** @class */ (function () {
6780
], C);
6881
return C;
6982
}());
83+
function id(x) {
84+
return x;
85+
}
86+
exports.result = id(_a || (_a = tslib_1.__makeTemplateObject(["hello world"], ["hello world"])));
87+
var _a;
7088
//// [script.js]
7189
var __extends = (this && this.__extends) || (function () {
7290
var extendStatics = Object.setPrototypeOf ||
@@ -78,6 +96,10 @@ var __extends = (this && this.__extends) || (function () {
7896
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
7997
};
8098
})();
99+
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
100+
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
101+
return cooked;
102+
};
81103
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
82104
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
83105
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -118,3 +140,7 @@ var C = /** @class */ (function () {
118140
], C);
119141
return C;
120142
}());
143+
function id(x) {
144+
return x;
145+
}
146+
var result = id(__makeTemplateObject(["hello world"], ["hello world"]));

0 commit comments

Comments
 (0)