Lower instanceof in CocoJS; implement IsInst in CocoIL

This commit is contained in:
joeduffy 2017-04-20 17:38:15 -07:00
parent 3f25aca7dd
commit aa44b46608
5 changed files with 313 additions and 29 deletions

View file

@ -14,7 +14,8 @@ import {Script} from "./script";
const defaultExport: string = "default"; // the ES6 default export name.
// A mapping from TypeScript binary operator to CocoIL AST operator.
// A mapping from TypeScript binary operator to CocoIL AST operator. Note that InstanceOf is not a binary operator;
// it is instead lowered to a specific CocoIL IsInstExpression.
let binaryOperators = new Map<ts.SyntaxKind, ast.BinaryOperator>([
// Arithmetic
[ ts.SyntaxKind.PlusToken, "+" ],
@ -63,7 +64,6 @@ let binaryOperators = new Map<ts.SyntaxKind, ast.BinaryOperator>([
// Intrinsics
// TODO: [ ts.SyntaxKind.InKeyword, "in" ],
// TODO: [ ts.SyntaxKind.InstanceOfKeyword, "instanceof" ],
]);
// A mapping from TypeScript unary prefix operator to CocoIL AST operator.
@ -2577,32 +2577,50 @@ export class Transformer {
}
}
private async transformBinaryOperatorExpression(node: ts.BinaryExpression): Promise<ast.BinaryOperatorExpression> {
// A few operators aren't faithfully emulated; in those cases, log warnings.
if (log.v(3)) {
switch (node.operatorToken.kind) {
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
case ts.SyntaxKind.EqualsEqualsEqualsToken:
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
log.out(3).info(
`ECMAScript operator '${ts.SyntaxKind[node.operatorToken.kind]}' not supported; ` +
`until pulumi/coconut#50 is implemented, be careful about subtle behavioral differences`,
);
break;
default:
break;
}
private async transformBinaryOperatorExpression(node: ts.BinaryExpression): Promise<ast.Expression> {
if (node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
// If this is an instanceof operator, the RHS is going to be a constructor for a type. The
// IsInstExpression that this lowers to needs a type token, so fetch that out.
let rhsSym: ts.Symbol = this.checker().getSymbolAtLocation(node.right);
contract.assert(!!rhsSym);
let rhsType: tokens.TypeToken | undefined = await this.resolveTokenFromSymbol(rhsSym);
contract.assert(!!rhsType);
return <ast.IsInstExpression>{
kind: ast.isInstExpressionKind,
expression: await this.transformExpression(node.left),
type: <ast.TypeToken>{
kind: ast.typeTokenKind,
tok: rhsType!,
},
};
}
else {
// A few operators aren't faithfully emulated; in those cases, log warnings.
if (log.v(3)) {
switch (node.operatorToken.kind) {
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
case ts.SyntaxKind.EqualsEqualsEqualsToken:
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
log.out(3).info(
`ECMAScript operator '${ts.SyntaxKind[node.operatorToken.kind]}' not supported; ` +
`until pulumi/coconut#50 is implemented, be careful about subtle behavioral differences`,
);
break;
default:
break;
}
}
let operator: ast.BinaryOperator | undefined = binaryOperators.get(node.operatorToken.kind);
contract.assert(!!operator, `Expected binary operator for: ${ts.SyntaxKind[node.operatorToken.kind]}`);
return this.withLocation(node, <ast.BinaryOperatorExpression>{
kind: ast.binaryOperatorExpressionKind,
operator: operator,
left: await this.transformExpression(node.left),
right: await this.transformExpression(node.right),
});
let operator: ast.BinaryOperator | undefined = binaryOperators.get(node.operatorToken.kind);
contract.assert(!!operator, `Expected binary operator for: ${ts.SyntaxKind[node.operatorToken.kind]}`);
return this.withLocation(node, <ast.BinaryOperatorExpression>{
kind: ast.binaryOperatorExpressionKind,
operator: operator,
left: await this.transformExpression(node.left),
right: await this.transformExpression(node.right),
});
}
}
private async transformBinarySequenceExpression(node: ts.BinaryExpression): Promise<ast.SequenceExpression> {

View file

@ -75,6 +75,103 @@
}
}
},
"C": {
"kind": "Class",
"name": {
"kind": "Identifier",
"ident": "C",
"loc": {
"file": "index.ts",
"start": {
"line": 12,
"column": 7
},
"end": {
"line": 12,
"column": 8
}
}
},
"members": {},
"abstract": false,
"loc": {
"file": "index.ts",
"start": {
"line": 12,
"column": 1
},
"end": {
"line": 12,
"column": 11
}
}
},
"c": {
"kind": "ModuleProperty",
"name": {
"kind": "Identifier",
"ident": "c",
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 5
},
"end": {
"line": 13,
"column": 6
}
}
},
"type": {
"kind": "TypeToken",
"tok": "dynamic",
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 5
},
"end": {
"line": 13,
"column": 21
}
}
}
},
"isc": {
"kind": "ModuleProperty",
"name": {
"kind": "Identifier",
"ident": "isc",
"loc": {
"file": "index.ts",
"start": {
"line": 14,
"column": 5
},
"end": {
"line": 14,
"column": 8
}
}
},
"type": {
"kind": "TypeToken",
"tok": "bool",
"loc": {
"file": "index.ts",
"start": {
"line": 14,
"column": 5
},
"end": {
"line": 14,
"column": 36
}
}
}
},
".init": {
"kind": "ModuleMethod",
"name": {
@ -273,6 +370,155 @@
"column": 20
}
}
},
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "basic/casts:index:c",
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 5
},
"end": {
"line": 13,
"column": 6
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 14
},
"end": {
"line": 13,
"column": 21
}
}
},
"operator": "=",
"right": {
"kind": "NewExpression",
"type": {
"kind": "TypeToken",
"tok": "basic/casts:index:C",
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 18
},
"end": {
"line": 13,
"column": 19
}
}
},
"arguments": [],
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 14
},
"end": {
"line": 13,
"column": 21
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 14
},
"end": {
"line": 13,
"column": 21
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 13,
"column": 14
},
"end": {
"line": 13,
"column": 21
}
}
},
{
"kind": "ExpressionStatement",
"expression": {
"kind": "BinaryOperatorExpression",
"left": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "basic/casts:index:isc",
"loc": {
"file": "index.ts",
"start": {
"line": 14,
"column": 5
},
"end": {
"line": 14,
"column": 8
}
}
}
},
"operator": "=",
"right": {
"kind": "IsInstExpression",
"expression": {
"kind": "LoadLocationExpression",
"name": {
"kind": "Token",
"tok": "basic/casts:index:c",
"loc": {
"file": "index.ts",
"start": {
"line": 14,
"column": 21
},
"end": {
"line": 14,
"column": 22
}
}
},
"loc": {
"file": "index.ts",
"start": {
"line": 14,
"column": 21
},
"end": {
"line": 14,
"column": 22
}
}
},
"type": {
"kind": "TypeToken",
"tok": "basic/casts:index:C"
}
}
}
}
]
}
@ -296,7 +542,7 @@
"column": 1
},
"end": {
"line": 12,
"line": 16,
"column": 1
}
}

View file

@ -9,3 +9,7 @@ let b: any = <any>a; // ok.
// TODO: a way to baseline expected runtime failures.
// let d: number = <number>b; // dynamically rejected.
class C {}
let c: any = new C();
let isc: boolean = (c instanceof C);

View file

@ -99,6 +99,8 @@ func (a *astBinder) After(node ast.Node) {
a.checkBinaryOperatorExpression(n)
case *ast.CastExpression:
a.checkCastExpression(n)
case *ast.IsInstExpression:
a.checkIsInstExpression(n)
case *ast.TypeOfExpression:
a.checkTypeOfExpression(n)
case *ast.ConditionalExpression:
@ -685,6 +687,11 @@ func (a *astBinder) checkCastExpression(node *ast.CastExpression) {
a.b.ctx.RegisterType(node, to)
}
func (a *astBinder) checkIsInstExpression(node *ast.IsInstExpression) {
// An isinst produces a bool indicating whether the expression is of the target type.
a.b.ctx.RegisterType(node, types.Bool)
}
func (a *astBinder) checkTypeOfExpression(node *ast.TypeOfExpression) {
// A typeof produces a string representation of the expression's type.
a.b.ctx.RegisterType(node, types.String)

View file

@ -2006,8 +2006,17 @@ func (e *evaluator) evalCastExpression(node *ast.CastExpression) (*rt.Object, *r
}
func (e *evaluator) evalIsInstExpression(node *ast.IsInstExpression) (*rt.Object, *rt.Unwind) {
contract.Failf("Evaluation of %v nodes not yet implemented", reflect.TypeOf(node))
return nil, nil
// Evaluate the underlying expression.
obj, uw := e.evalExpression(node.Expression)
if uw != nil {
return nil, uw
}
// Now check the type and produce a bool object indicating whether the type is good.
from := obj.Type()
to := e.ctx.LookupType(node.Type)
isinst := types.CanConvert(from, to)
return e.alloc.NewBool(node, isinst), nil
}
func (e *evaluator) evalTypeOfExpression(node *ast.TypeOfExpression) (*rt.Object, *rt.Unwind) {