Skip to content

Commit

Permalink
Merge pull request #960 from Microsoft/templates
Browse files Browse the repository at this point in the history
Support for ES6 Templates
  • Loading branch information
DanielRosenwasser committed Oct 31, 2014
2 parents 33cee0c + 3e8978f commit 0a97f5f
Show file tree
Hide file tree
Showing 304 changed files with 3,226 additions and 206 deletions.
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 => {
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) {
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.
//
// 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

0 comments on commit 0a97f5f

Please sign in to comment.