Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for ES6 Templates #960

Merged
merged 21 commits into from
Oct 31, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
518a5d3
Rudimentary template support, excluding tagging.
DanielRosenwasser Oct 16, 2014
c0893e1
Added tests and baselines for templates.
DanielRosenwasser Oct 22, 2014
b704f19
Merge branch 'letAndConst' into templates
DanielRosenwasser Oct 23, 2014
e709628
Added support for tagged template strings, updated baselines.
DanielRosenwasser Oct 23, 2014
0d1a46d
Better test coverage for templates in object literals.
DanielRosenwasser Oct 23, 2014
a5b77c6
Added tests for tagged templates. Some should fail when we do typeche…
DanielRosenwasser Oct 23, 2014
799609c
Tests for tagged templates.
DanielRosenwasser Oct 24, 2014
c03dc10
Added syntactic classification for templates; also made 'spans' a Nod…
DanielRosenwasser Oct 25, 2014
7fad769
Merge branch 'master' into templates
DanielRosenwasser Oct 27, 2014
4aafe1d
Addressed CR feedback.
DanielRosenwasser Oct 27, 2014
8786d30
Changed Debug.assert's first parameter to a boolean.
DanielRosenwasser Oct 27, 2014
d45fb77
Renamed certain functions in the parser to more accurately reflect be…
DanielRosenwasser Oct 27, 2014
64097a3
Missed a use when fixing Debug.assert
DanielRosenwasser Oct 27, 2014
aabfebd
Fixed emit for parenthesized template expressions.
DanielRosenwasser Oct 27, 2014
b8535d3
Omit empty template tail literals.
DanielRosenwasser Oct 27, 2014
d522c88
Merge branch 'master' into templates
DanielRosenwasser Oct 29, 2014
ead3c1b
Disabled completion list entries in template literal parts for the LS.
DanielRosenwasser Oct 28, 2014
35cf95c
Merge branch 'master' into templates
DanielRosenwasser Oct 29, 2014
76c0381
Merge branch 'master' into templates
DanielRosenwasser Oct 30, 2014
63340a0
Addressed CR feedback.
DanielRosenwasser Oct 31, 2014
3e8978f
Merge branch 'master' into templates
DanielRosenwasser Oct 31, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
46 changes: 37 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ module ts {
if (result.flags & SymbolFlags.BlockScopedVariable) {
// Block-scoped variables cannot be used before their definition
var declaration = forEach(result.declarations, d => d.flags & NodeFlags.BlockScoped ? d : undefined);
Debug.assert(declaration, "Block-scoped variable declaration is undefined");
Debug.assert(declaration !== undefined, "Block-scoped variable declaration is undefined");
var declarationSourceFile = getSourceFileOfNode(declaration);
var referenceSourceFile = getSourceFileOfNode(errorLocation);
if (declarationSourceFile === referenceSourceFile) {
Expand Down Expand Up @@ -472,7 +472,7 @@ module ts {
function getSymbolOfPartOfRightHandSideOfImport(entityName: EntityName, importDeclaration?: ImportDeclaration): Symbol {
if (!importDeclaration) {
importDeclaration = getAncestor(entityName, SyntaxKind.ImportDeclaration);
Debug.assert(importDeclaration);
Debug.assert(importDeclaration !== undefined);
}
// There are three things we might try to look for. In the following examples,
// the search term is enclosed in |...|:
Expand Down Expand Up @@ -3334,7 +3334,6 @@ module ts {
}
if (reportErrors) {
headMessage = headMessage || Diagnostics.Type_0_is_not_assignable_to_type_1;
Debug.assert(headMessage);
reportError(headMessage, typeToString(source), typeToString(target));
}
return Ternary.False;
Expand Down Expand Up @@ -4912,7 +4911,7 @@ module ts {
}
return createArrayType(getUnionType(elementTypes));
}

function isNumericName(name: string) {
// The intent of numeric names is that
// - they are names with text in a numeric form, and that
Expand All @@ -4937,7 +4936,7 @@ module ts {
// with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively.
return (+name).toString() === name;
}

function checkObjectLiteral(node: ObjectLiteral, contextualMapper?: TypeMapper): Type {
var members = node.symbol.members;
var properties: SymbolTable = {};
Expand Down Expand Up @@ -5660,6 +5659,13 @@ module ts {
return getReturnTypeOfSignature(signature);
}

function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
// TODO (drosen): Make sure substitutions are assignable to the tag's arguments.
checkExpression(node.tag);
checkExpression(node.template);
return anyType;
}

function checkTypeAssertion(node: TypeAssertion): Type {
var exprType = checkExpression(node.operand);
var targetType = getTypeFromTypeNode(node.type);
Expand Down Expand Up @@ -6170,6 +6176,19 @@ module ts {
return getUnionType([type1, type2]);
}

function checkTemplateExpression(node: TemplateExpression): Type {
// We just want to check each expressions, but we are unconcerned with
// the type of each expression, as any value may be coerced into a string.
// It is worth asking whether this is what we really want though.
// A place where we actually *are* concerned with the expressions' types are
// in tagged templates.
forEach((<TemplateExpression>node).templateSpans, templateSpan => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explain why this is correct. I believe it's because we rewrite this to string addition, and string addition allows the other side to be of any type. So we don't need to check for things like void types, etc. But it would be good to comment this bit.

checkExpression(templateSpan.expression);
});

return stringType;
}

function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper?: TypeMapper): Type {
var saveContextualType = node.contextualType;
node.contextualType = contextualType;
Expand Down Expand Up @@ -6223,7 +6242,10 @@ module ts {
return booleanType;
case SyntaxKind.NumericLiteral:
return numberType;
case SyntaxKind.TemplateExpression:
return checkTemplateExpression(<TemplateExpression>node);
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
return stringType;
case SyntaxKind.RegularExpressionLiteral:
return globalRegExpType;
Expand All @@ -6240,6 +6262,8 @@ module ts {
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
return checkCallExpression(<CallExpression>node);
case SyntaxKind.TaggedTemplateExpression:
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.TypeAssertion:
return checkTypeAssertion(<TypeAssertion>node);
case SyntaxKind.ParenExpression:
Expand Down Expand Up @@ -7549,17 +7573,17 @@ module ts {
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor;
}
else {
Debug.assert(derived.flags & SymbolFlags.Property);
Debug.assert((derived.flags & SymbolFlags.Property) !== 0);
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property;
}
}
else if (base.flags & SymbolFlags.Property) {
Debug.assert(derived.flags & SymbolFlags.Method);
Debug.assert((derived.flags & SymbolFlags.Method) !== 0);
errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function;
}
else {
Debug.assert(base.flags & SymbolFlags.Accessor);
Debug.assert(derived.flags & SymbolFlags.Method);
Debug.assert((base.flags & SymbolFlags.Accessor) !== 0);
Debug.assert((derived.flags & SymbolFlags.Method) !== 0);
errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function;
}

Expand Down Expand Up @@ -8016,6 +8040,7 @@ module ts {
case SyntaxKind.IndexedAccess:
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
case SyntaxKind.TaggedTemplateExpression:
case SyntaxKind.TypeAssertion:
case SyntaxKind.ParenExpression:
case SyntaxKind.PrefixOperator:
Expand Down Expand Up @@ -8293,6 +8318,9 @@ module ts {
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
return (<CallExpression>parent).typeArguments && (<CallExpression>parent).typeArguments.indexOf(node) >= 0;
case SyntaxKind.TaggedTemplateExpression:
// TODO (drosen): TaggedTemplateExpressions may eventually support type arguments.
return false;
}
}

Expand Down
21 changes: 14 additions & 7 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ module ts {
[index: string]: T;
}

export enum Comparison {
LessThan = -1,
EqualTo = 0,
GreaterThan = 1
}

export interface StringSet extends Map<any> { }

export function forEach<T, U>(array: T[], callback: (element: T) => U): U {
Expand Down Expand Up @@ -93,6 +99,7 @@ module ts {
export function concatenate<T>(array1: T[], array2: T[]): T[] {
if (!array2 || !array2.length) return array1;
if (!array1 || !array1.length) return array2;

return array1.concat(array2);
}

Expand Down Expand Up @@ -326,11 +333,11 @@ module ts {
};
}

export function compareValues<T>(a: T, b: T): number {
if (a === b) return 0;
if (a === undefined) return -1;
if (b === undefined) return 1;
return a < b ? -1 : 1;
export function compareValues<T>(a: T, b: T): Comparison {
if (a === b) return Comparison.EqualTo;
if (a === undefined) return Comparison.LessThan;
if (b === undefined) return Comparison.GreaterThan;
return a < b ? Comparison.LessThan : Comparison.GreaterThan;
}

function getDiagnosticFilename(diagnostic: Diagnostic): string {
Expand All @@ -355,7 +362,7 @@ module ts {
var previousDiagnostic = diagnostics[0];
for (var i = 1; i < diagnostics.length; i++) {
var currentDiagnostic = diagnostics[i];
var isDupe = compareDiagnostics(currentDiagnostic, previousDiagnostic) === 0;
var isDupe = compareDiagnostics(currentDiagnostic, previousDiagnostic) === Comparison.EqualTo;
if (!isDupe) {
newDiagnostics.push(currentDiagnostic);
previousDiagnostic = currentDiagnostic;
Expand Down Expand Up @@ -644,7 +651,7 @@ module ts {
return currentAssertionLevel >= level;
}

export function assert(expression: any, message?: string, verboseDebugInfo?: () => string): void {
export function assert(expression: boolean, message?: string, verboseDebugInfo?: () => string): void {
if (!expression) {
var verboseDebugString = "";
if (verboseDebugInfo) {
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ module ts {
const_declarations_can_only_be_declared_inside_a_block: { code: 1156, category: DiagnosticCategory.Error, key: "'const' declarations can only be declared inside a block." },
let_declarations_can_only_be_declared_inside_a_block: { code: 1157, category: DiagnosticCategory.Error, key: "'let' declarations can only be declared inside a block." },
Aliased_type_cannot_be_an_object_type_literal_Use_an_interface_declaration_instead: { code: 1158, category: DiagnosticCategory.Error, key: "Aliased type cannot be an object type literal. Use an interface declaration instead." },
Invalid_template_literal_expected: { code: 1159, category: DiagnosticCategory.Error, key: "Invalid template literal; expected '}'" },
Tagged_templates_are_only_available_when_targeting_ECMAScript_6_and_higher: { code: 1160, category: DiagnosticCategory.Error, key: "Tagged templates are only available when targeting ECMAScript 6 and higher." },
Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." },
Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor: { code: 2301, category: DiagnosticCategory.Error, key: "Initializer of instance member variable '{0}' cannot reference identifier '{1}' declared in the constructor." },
Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." },
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,14 @@
"category": "Error",
"code": 1158
},
"Invalid template literal; expected '}'": {
"category": "Error",
"code": 1159
},
"Tagged templates are only available when targeting ECMAScript 6 and higher.": {
"category": "Error",
"code": 1160
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
132 changes: 129 additions & 3 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,14 +786,123 @@ module ts {
}
}

function emitLiteral(node: LiteralExpression) {
var text = getSourceTextOfLocalNode(node);
if (node.kind === SyntaxKind.StringLiteral && compilerOptions.sourceMap) {
function emitLiteral(node: LiteralExpression): void {
var text = getLiteralText();

if (compilerOptions.sourceMap && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) {
writer.writeLiteral(text);
}
else {
write(text);
}

function getLiteralText() {
if (compilerOptions.target < ScriptTarget.ES6 && isTemplateLiteralKind(node.kind)) {
return getTemplateLiteralAsStringLiteral(node)
}

return getSourceTextOfLocalNode(node);
}
}

function getTemplateLiteralAsStringLiteral(node: LiteralExpression): string {
return '"' + escapeString(node.text) + '"';
}

function emitTemplateExpression(node: TemplateExpression): void {
// In ES6 mode and above, we can simply emit each portion of a template in order, but in
// ES3 & ES5 we must convert the template expression into a series of string concatenations.
if (compilerOptions.target >= ScriptTarget.ES6) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explain that in ES6 we don't need to do anything. But that in ES5 we'll convert things according. Explain the type of conversion you're trying to do.

forEachChild(node, emit);
return;
}

Debug.assert(node.parent.kind !== SyntaxKind.TaggedTemplateExpression);

var templateNeedsParens = isExpression(node.parent)
&& node.parent.kind !== SyntaxKind.ParenExpression
&& comparePrecedenceToBinaryPlus(node.parent) !== Comparison.LessThan;

if (templateNeedsParens) {
write("(");
}

emitLiteral(node.head);

forEach(node.templateSpans, templateSpan => {
// Check if the expression has operands and binds its operands less closely than binary '+'.
// If it does, we need to wrap the expression in parentheses. Otherwise, something like
// `abc${ 1 << 2}`
// becomes
// "abc" + 1 << 2 + ""
// which is really
// ("abc" + 1) << (2 + "")
// rather than
// "abc" + (1 << 2) + ""
var needsParens = templateSpan.expression.kind !== SyntaxKind.ParenExpression
&& comparePrecedenceToBinaryPlus(templateSpan.expression) !== Comparison.GreaterThan;

write(" + ");

if (needsParens) {
write("(");
}
emit(templateSpan.expression);
if (needsParens) {
write(")");
}

// Only emit if the literal is non-empty.
// The binary '+' operator is left-associative, so the first string concatenation will force
// the result up to this point to be a string. Emitting a '+ ""' has no semantic effect.
if (templateSpan.literal.text.length !== 0) {
write(" + ")
emitLiteral(templateSpan.literal);
}
});

if (templateNeedsParens) {
write(")");
}

/**
* Returns whether the expression has lesser, greater,
* or equal precedence to the binary '+' operator
*/
function comparePrecedenceToBinaryPlus(expression: Expression): Comparison {
// All binary expressions have lower precedence than '+' apart from '*', '/', and '%'.
// All unary operators have a higher precedence apart from yield.
// Arrow functions and conditionals have a lower precedence,
// although we convert the former into regular function expressions in ES5 mode,
// and in ES6 mode this function won't get called anyway.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add assert that you're in ES5 mode or lower.

//
// TODO (drosen): Note that we need to account for the upcoming 'yield' and
// spread ('...') unary operators that are anticipated for ES6.
Debug.assert(compilerOptions.target <= ScriptTarget.ES5);
switch (expression.kind) {
case SyntaxKind.BinaryExpression:
switch ((<BinaryExpression>expression).operator) {
case SyntaxKind.AsteriskToken:
case SyntaxKind.SlashToken:
case SyntaxKind.PercentToken:
return Comparison.GreaterThan;
case SyntaxKind.PlusToken:
return Comparison.EqualTo;
default:
return Comparison.LessThan;
}
case SyntaxKind.ConditionalExpression:
return Comparison.LessThan;
default:
return Comparison.GreaterThan;
}
}

}

function emitTemplateSpan(span: TemplateSpan) {
emit(span.expression);
emit(span.literal);
}

// This function specifically handles numeric/string literals for enum and accessor 'identifiers'.
Expand Down Expand Up @@ -977,6 +1086,13 @@ module ts {
}
}

function emitTaggedTemplateExpression(node: TaggedTemplateExpression): void {
Debug.assert(compilerOptions.target >= ScriptTarget.ES6, "Trying to emit a tagged template in pre-ES6 mode.");
emit(node.tag);
write(" ");
emit(node.template);
}

function emitParenExpression(node: ParenExpression) {
if (node.expression.kind === SyntaxKind.TypeAssertion) {
var operand = (<TypeAssertion>node.expression).operand;
Expand Down Expand Up @@ -2085,7 +2201,15 @@ module ts {
case SyntaxKind.NumericLiteral:
case SyntaxKind.StringLiteral:
case SyntaxKind.RegularExpressionLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateHead:
case SyntaxKind.TemplateMiddle:
case SyntaxKind.TemplateTail:
return emitLiteral(<LiteralExpression>node);
case SyntaxKind.TemplateExpression:
return emitTemplateExpression(<TemplateExpression>node);
case SyntaxKind.TemplateSpan:
return emitTemplateSpan(<TemplateSpan>node);
case SyntaxKind.QualifiedName:
return emitPropertyAccess(<QualifiedName>node);
case SyntaxKind.ArrayLiteral:
Expand All @@ -2102,6 +2226,8 @@ module ts {
return emitCallExpression(<CallExpression>node);
case SyntaxKind.NewExpression:
return emitNewExpression(<NewExpression>node);
case SyntaxKind.TaggedTemplateExpression:
return emitTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.TypeAssertion:
return emit((<TypeAssertion>node).operand);
case SyntaxKind.ParenExpression:
Expand Down
Loading