Skip to content

Commit

Permalink
Adds a prototype implementation of the bind ('::') operator
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Jul 9, 2015
1 parent aaf0f78 commit 8521632
Show file tree
Hide file tree
Showing 14 changed files with 636 additions and 31 deletions.
42 changes: 40 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ namespace ts {
let emitParam = false;
let emitAwaiter = false;
let emitGenerator = false;
let emitBind = false;

let resolutionTargets: Object[] = [];
let resolutionResults: boolean[] = [];
Expand Down Expand Up @@ -6178,14 +6179,14 @@ namespace ts {
if (needToCaptureLexicalThis) {
captureLexicalThis(node, container);
}

if (isClassLike(container.parent)) {
let symbol = getSymbolOfNode(container.parent);
return container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : getDeclaredTypeOfSymbol(symbol);
}
return anyType;
}

function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean {
for (let n = node; n && n !== constructorDecl; n = n.parent) {
if (n.kind === SyntaxKind.Parameter) {
Expand Down Expand Up @@ -8765,6 +8766,33 @@ namespace ts {
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
return getReturnTypeOfSignature(getResolvedSignature(node));
}

function checkBindExpression(node: BindExpression): Type {
emitBind = true;

if (!node.baseExpression
&& node.targetExpression.kind !== SyntaxKind.PropertyAccessExpression
&& node.targetExpression.kind !== SyntaxKind.ElementAccessExpression) {
error(node, Diagnostics.The_target_of_a_bind_expression_without_a_left_hand_side_must_be_a_property_or_element_access);
return unknownType;
}

let baseType = node.baseExpression ? checkExpression(node.baseExpression) : undefined;
let targetType = checkExpression(node.targetExpression);
let apparentType = getApparentType(targetType);
if (apparentType === unknownType) {
return unknownType;
}

let callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
let constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
if (!isTypeAny(targetType) && callSignatures.length === 0 && constructSignatures.length === 0) {
error(node, Diagnostics.Cannot_bind_an_expression_whose_type_lacks_a_call_or_construct_signature);
return unknownType;
}

return targetType;
}

function checkAssertion(node: AssertionExpression) {
let exprType = checkExpression(node.expression);
Expand Down Expand Up @@ -9813,6 +9841,8 @@ namespace ts {
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
return checkCallExpression(<CallExpression>node);
case SyntaxKind.BindExpression:
return checkBindExpression(<BindExpression>node);
case SyntaxKind.TaggedTemplateExpression:
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.ParenthesizedExpression:
Expand Down Expand Up @@ -13235,6 +13265,7 @@ namespace ts {
case SyntaxKind.ConditionalExpression:
case SyntaxKind.SpreadElementExpression:
case SyntaxKind.YieldExpression:
case SyntaxKind.BindExpression:
case SyntaxKind.Block:
case SyntaxKind.ModuleBlock:
case SyntaxKind.VariableStatement:
Expand Down Expand Up @@ -13298,6 +13329,9 @@ namespace ts {
emitExtends = false;
emitDecorate = false;
emitParam = false;
emitAwaiter = false;
emitGenerator = false;
emitBind = false;
potentialThisCollisions.length = 0;

forEach(node.statements, checkSourceElement);
Expand Down Expand Up @@ -13331,6 +13365,10 @@ namespace ts {
if (emitGenerator || (emitAwaiter && languageVersion < ScriptTarget.ES6)) {
links.flags |= NodeCheckFlags.EmitGenerator;
}

if (emitBind) {
links.flags |= NodeCheckFlags.EmitBind;
}

links.flags |= NodeCheckFlags.TypeChecked;
}
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 @@ -414,6 +414,8 @@ namespace ts {
The_arguments_object_cannot_be_referenced_in_an_async_arrow_function_Consider_using_a_standard_async_function_expression: { code: 2522, category: DiagnosticCategory.Error, key: "The 'arguments' object cannot be referenced in an async arrow function. Consider using a standard async function expression." },
yield_expressions_cannot_be_used_in_a_parameter_initializer: { code: 2523, category: DiagnosticCategory.Error, key: "'yield' expressions cannot be used in a parameter initializer." },
await_expressions_cannot_be_used_in_a_parameter_initializer: { code: 2524, category: DiagnosticCategory.Error, key: "'await' expressions cannot be used in a parameter initializer." },
Cannot_bind_an_expression_whose_type_lacks_a_call_or_construct_signature: { code: 2525, category: DiagnosticCategory.Error, key: "Cannot bind an expression whose type lacks a call or construct signature." },
The_target_of_a_bind_expression_without_a_left_hand_side_must_be_a_property_or_element_access: { code: 2526, category: DiagnosticCategory.Error, key: "The target of a bind expression without a left-hand side must be a property or element access." },
JSX_element_attributes_type_0_must_be_an_object_type: { code: 2600, category: DiagnosticCategory.Error, key: "JSX element attributes type '{0}' must be an object type." },
The_return_type_of_a_JSX_element_constructor_must_return_an_object_type: { code: 2601, category: DiagnosticCategory.Error, key: "The return type of a JSX element constructor must return an object type." },
JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist: { code: 2602, category: DiagnosticCategory.Error, key: "JSX element implicitly has type 'any' because the global type 'JSX.Element' does not exist." },
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,14 @@
"category": "Error",
"code": 2524
},
"Cannot bind an expression whose type lacks a call or construct signature.": {
"category": "Error",
"code": 2525
},
"The target of a bind expression without a left-hand side must be a property or element access.": {
"category": "Error",
"code": 2526
},
"JSX element attributes type '{0}' must be an object type.": {
"category": "Error",
"code": 2600
Expand Down
140 changes: 134 additions & 6 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ var __extends = (this && this.__extends) || function (d, b) {
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};`;

// emit output for the __bind helper function
const bindHelper = `
var __bind = (this && this.__bind) || function (b, t) {
var d;
return d = function() { return b.apply(t, arguments); }, d.prototype = b.prototype, d;
};`;

// emit output for the __decorate helper function
const decorateHelper = `
Expand Down Expand Up @@ -147,6 +154,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
let decorateEmitted = false;
let paramEmitted = false;
let awaiterEmitted = false;
let bindEmitted = false;
let tempFlags = 0;
let tempVariables: Identifier[];
let tempParameters: Identifier[];
Expand Down Expand Up @@ -1442,6 +1450,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
case SyntaxKind.WhileStatement:
case SyntaxKind.WithStatement:
case SyntaxKind.YieldExpression:
case SyntaxKind.BindExpression:
return true;
case SyntaxKind.BindingElement:
case SyntaxKind.EnumMember:
Expand Down Expand Up @@ -2156,22 +2165,32 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
}

function emitCallExpression(node: CallExpression) {
if (languageVersion < ScriptTarget.ES6 && hasSpreadElement(node.arguments)) {
let expression = node.expression;
if (expression.kind === SyntaxKind.BindExpression) {
if ((<BindExpression>expression).baseExpression !== undefined) {
emitBindExpression(<BindExpression>expression, node.arguments);
return;
}
else {
expression = (<BindExpression>expression).targetExpression;
}
}
else if (languageVersion < ScriptTarget.ES6 && hasSpreadElement(node.arguments)) {
emitCallWithSpread(node);
return;
}
let superCall = false;
if (node.expression.kind === SyntaxKind.SuperKeyword) {
emitSuper(node.expression);
if (expression.kind === SyntaxKind.SuperKeyword) {
emitSuper(expression);
superCall = true;
}
else {
emit(node.expression);
superCall = node.expression.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.expression).expression.kind === SyntaxKind.SuperKeyword;
emit(expression);
superCall = expression.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>expression).expression.kind === SyntaxKind.SuperKeyword;
}
if (superCall && languageVersion < ScriptTarget.ES6) {
write(".call(");
emitThis(node.expression);
emitThis(expression);
if (node.arguments.length) {
write(", ");
emitCommaList(node.arguments);
Expand Down Expand Up @@ -2236,6 +2255,108 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
emitDownlevelTaggedTemplate(node);
}
}

function createCallCall(expression: Expression, thisArgument: Expression, _arguments: NodeArray<Expression>) {
let callIdentifier = <Identifier>createSynthesizedNode(SyntaxKind.Identifier);
callIdentifier.text = "call";

let callExpr = createCallExpression(
createPropertyAccessExpression(expression, callIdentifier),
[thisArgument, ..._arguments]);

return callExpr;
}

function createBindCall(expression: Expression, thisArgument: Expression) {
if (languageVersion < ScriptTarget.ES5) {
let bindIdentifier = <Identifier>createSynthesizedNode(SyntaxKind.Identifier);
bindIdentifier.text = "__bind";

let callExpr = createCallExpression(
bindIdentifier,
[parenthesizeForAccess(expression), thisArgument]);

return callExpr;
}
else {
let bindIdentifier = <Identifier>createSynthesizedNode(SyntaxKind.Identifier);
bindIdentifier.text = "bind";

let callExpr = createCallExpression(
createPropertyAccessExpression(expression, bindIdentifier),
[thisArgument]);

return callExpr;
}
}

function createCallExpression(expression: Expression, _arguments: Expression[]) {
let callExpr = <CallExpression>createSynthesizedNode(SyntaxKind.CallExpression);
callExpr.expression = parenthesizeForAccess(expression);
callExpr.arguments = <NodeArray<Expression>>createSynthesizedNodeArray();
for (let argument of _arguments) {
callExpr.arguments.push(argument);
}
return callExpr;
}

function createAssignmentExpression(left: Expression, right: Expression): BinaryExpression {
let assignExpression = <BinaryExpression>createSynthesizedNode(SyntaxKind.BinaryExpression);
assignExpression.left = left;
assignExpression.operatorToken = createSynthesizedNode(SyntaxKind.EqualsToken);
assignExpression.right = right;
return assignExpression;
}

function createCommaExpression(left: Expression, right: Expression): BinaryExpression {
let commaExpression = <BinaryExpression>createSynthesizedNode(SyntaxKind.BinaryExpression);
commaExpression.left = left;
commaExpression.operatorToken = createSynthesizedNode(SyntaxKind.CommaToken);
commaExpression.right = right;
return commaExpression;
}

function emitBindExpression(node: BindExpression, _arguments?: NodeArray<Expression>): void {
let { baseExpression, targetExpression } = node;
let boundExpression: Expression;
let thisArgument: Expression;
if (baseExpression) {
let tempVariable = createAndRecordTempVariable(TempFlags.Auto);
boundExpression = createCommaExpression(
createAssignmentExpression(tempVariable, baseExpression),
targetExpression
);
thisArgument = tempVariable;
}
else if (targetExpression.kind === SyntaxKind.PropertyAccessExpression) {
let tempVariable = createAndRecordTempVariable(TempFlags.Auto);
let { expression, name } = <PropertyAccessExpression>targetExpression;
boundExpression = createPropertyAccessExpression(
createAssignmentExpression(tempVariable, expression),
name
);
thisArgument = tempVariable;
}
else if (targetExpression.kind === SyntaxKind.ElementAccessExpression) {
let tempVariable = createAndRecordTempVariable(TempFlags.Auto);
let { expression, argumentExpression } = <ElementAccessExpression>targetExpression;
boundExpression = createElementAccessExpression(
createAssignmentExpression(tempVariable, expression),
argumentExpression
);
thisArgument = tempVariable;
}
else {
emit(targetExpression);
return;
}

let callExpression = _arguments
? createCallCall(boundExpression, thisArgument, _arguments)
: createBindCall(boundExpression, thisArgument);

emit(callExpression);
}

function emitParenExpression(node: ParenthesizedExpression) {
// If the node is synthesized, it means the emitter put the parentheses there,
Expand Down Expand Up @@ -6232,6 +6353,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
writeLines(extendsHelper);
extendsEmitted = true;
}

if ((languageVersion < ScriptTarget.ES5) && (!bindEmitted && resolver.getNodeCheckFlags(node) & NodeCheckFlags.EmitBind)) {
writeLines(bindHelper);
bindEmitted = true;
}

if (!decorateEmitted && resolver.getNodeCheckFlags(node) & NodeCheckFlags.EmitDecorate) {
writeLines(decorateHelper);
Expand Down Expand Up @@ -6416,6 +6542,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
return emitNewExpression(<NewExpression>node);
case SyntaxKind.TaggedTemplateExpression:
return emitTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.BindExpression:
return emitBindExpression(<BindExpression>node);
case SyntaxKind.TypeAssertionExpression:
return emit((<TypeAssertion>node).expression);
case SyntaxKind.AsExpression:
Expand Down
Loading

0 comments on commit 8521632

Please sign in to comment.