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

As operator #3201

Closed
wants to merge 12 commits into from
7 changes: 4 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7261,7 +7261,7 @@ module ts {
return getReturnTypeOfSignature(getResolvedSignature(node));
}

function checkTypeAssertion(node: TypeAssertion): Type {
function checkAssertion(node: AssertionExpression) {
let exprType = checkExpression(node.expression);
let targetType = getTypeFromTypeNode(node.type);
if (produceDiagnostics && targetType !== unknownType) {
Expand Down Expand Up @@ -8134,8 +8134,6 @@ module ts {
return checkCallExpression(<CallExpression>node);
case SyntaxKind.TaggedTemplateExpression:
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.TypeAssertionExpression:
return checkTypeAssertion(<TypeAssertion>node);
case SyntaxKind.ParenthesizedExpression:
return checkExpression((<ParenthesizedExpression>node).expression, contextualMapper);
case SyntaxKind.ClassExpression:
Expand All @@ -8145,6 +8143,9 @@ module ts {
return checkFunctionExpressionOrObjectLiteralMethod(<FunctionExpression>node, contextualMapper);
case SyntaxKind.TypeOfExpression:
return checkTypeOfExpression(<TypeOfExpression>node);
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
return checkAssertion(<AssertionExpression>node);
case SyntaxKind.DeleteExpression:
return checkDeleteExpression(<DeleteExpression>node);
case SyntaxKind.VoidExpression:
Expand Down
14 changes: 8 additions & 6 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1790,8 +1790,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
}

function skipParentheses(node: Expression): Expression {
while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression) {
node = (<ParenthesizedExpression | TypeAssertion>node).expression;
while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) {
node = (<ParenthesizedExpression | AssertionExpression>node).expression;
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to update parenthesizeForAccess as well.

}
return node;
}
Expand Down Expand Up @@ -1907,13 +1907,13 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {

function emitParenExpression(node: ParenthesizedExpression) {
if (!node.parent || node.parent.kind !== SyntaxKind.ArrowFunction) {
if (node.expression.kind === SyntaxKind.TypeAssertionExpression) {
let operand = (<TypeAssertion>node.expression).expression;
if (node.expression.kind === SyntaxKind.TypeAssertionExpression || node.expression.kind === SyntaxKind.AsExpression) {
let operand = (<AssertionExpression>node.expression).expression;

// Make sure we consider all nested cast expressions, e.g.:
// (<any><number><any>-A).x;
while (operand.kind == SyntaxKind.TypeAssertionExpression) {
operand = (<TypeAssertion>operand).expression;
while (operand.kind === SyntaxKind.TypeAssertionExpression || operand.kind === SyntaxKind.AsExpression) {
operand = (<AssertionExpression>operand).expression;
}

// We have an expression of the form: (<Type>SubExpr)
Expand Down Expand Up @@ -5840,6 +5840,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return emitTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.TypeAssertionExpression:
return emit((<TypeAssertion>node).expression);
case SyntaxKind.AsExpression:
return emit((<AsExpression>node).expression);
case SyntaxKind.ParenthesizedExpression:
return emitParenExpression(<ParenthesizedExpression>node);
case SyntaxKind.FunctionDeclaration:
Expand Down
26 changes: 25 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ module ts {
return visitNode(cbNode, (<BinaryExpression>node).left) ||
visitNode(cbNode, (<BinaryExpression>node).operatorToken) ||
visitNode(cbNode, (<BinaryExpression>node).right);
case SyntaxKind.AsExpression:
return visitNode(cbNode, (<AsExpression>node).expression) ||
visitNode(cbNode, (<AsExpression>node).type);
case SyntaxKind.ConditionalExpression:
return visitNode(cbNode, (<ConditionalExpression>node).condition) ||
visitNode(cbNode, (<ConditionalExpression>node).questionToken) ||
Expand Down Expand Up @@ -2818,7 +2821,20 @@ module ts {
break;
}

leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence));
if (token === SyntaxKind.AsKeyword) {
// Make sure we *do* perform ASI for constructs like this:
// var x = foo
// as (Bar)
// This should be parsed as an initialized variable, followed by a function call to 'as' with the argument 'Bar'
Copy link
Member

Choose a reason for hiding this comment

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

Also consider wrapping this

if (canParseSemicolon()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

just add a comment for this case.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you just check scanner.hasPrecedingLineBreak instead? I don't really like canParseSemicolon that much.

break;
} else {
Copy link
Member

Choose a reason for hiding this comment

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

else on the next line

nextToken();
leftOperand = makeAsExpression(leftOperand, parseType());
Copy link
Contributor

Choose a reason for hiding this comment

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

There is an ambiguity with union types worth calling out here:

x as Foo | Bar

This is intended to be a type assertion to a union type, and not a bitwise OR where the first operand is a type assertion. Seems like you do the right thing here, but just wanted to bring it up.

}
} else {
Copy link
Member

Choose a reason for hiding this comment

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

else on the next line.

Copy link
Member

Choose a reason for hiding this comment

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

else on the next line

leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence));
}
}

return leftOperand;
Expand Down Expand Up @@ -2855,6 +2871,7 @@ module ts {
case SyntaxKind.GreaterThanEqualsToken:
case SyntaxKind.InstanceOfKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.AsKeyword:
return 7;
case SyntaxKind.LessThanLessThanToken:
case SyntaxKind.GreaterThanGreaterThanToken:
Expand Down Expand Up @@ -2882,6 +2899,13 @@ module ts {
return finishNode(node);
}

function makeAsExpression(left: Expression, right: TypeNode): AsExpression {
let node = <AsExpression>createNode(SyntaxKind.AsExpression, left.pos);
node.expression = left;
node.type = right;
return finishNode(node);
}

function parsePrefixUnaryExpression() {
let node = <PrefixUnaryExpression>createNode(SyntaxKind.PrefixUnaryExpression);
node.operator = token;
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ module ts {
ClassExpression,
OmittedExpression,
ExpressionWithTypeArguments,
AsExpression,
// Misc
TemplateSpan,
SemicolonClassElement,
Expand Down Expand Up @@ -404,6 +405,8 @@ module ts {

export type DeclarationName = Identifier | LiteralExpression | ComputedPropertyName | BindingPattern;

export type AssertionExpression = TypeAssertion | AsExpression;

export interface Declaration extends Node {
_declarationBrand: any;
name?: DeclarationName;
Expand Down Expand Up @@ -669,6 +672,11 @@ module ts {
right: Expression;
}

export interface AsExpression extends Expression {
expression: Expression;
type: TypeNode;
}

export interface ConditionalExpression extends Expression {
condition: Expression;
questionToken: Node;
Expand Down
1 change: 1 addition & 0 deletions src/services/formatting/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ module ts.formatting {
switch (context.contextNode.kind) {
case SyntaxKind.BinaryExpression:
case SyntaxKind.ConditionalExpression:
case SyntaxKind.AsExpression:
return true;

// equals in binding elements: function foo([[x, y] = [1, 2]])
Expand Down
2 changes: 1 addition & 1 deletion src/services/formatting/tokenRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ module ts.formatting {
static AnyIncludingMultilineComments = TokenRange.FromTokens(TokenRange.Any.GetTokens().concat([SyntaxKind.MultiLineCommentTrivia]));
static Keywords = TokenRange.FromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword);
static BinaryOperators = TokenRange.FromRange(SyntaxKind.FirstBinaryOperator, SyntaxKind.LastBinaryOperator);
static BinaryKeywordOperators = TokenRange.FromTokens([SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword]);
static BinaryKeywordOperators = TokenRange.FromTokens([SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword]);
static UnaryPrefixOperators = TokenRange.FromTokens([SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken]);
static UnaryPrefixExpressions = TokenRange.FromTokens([SyntaxKind.NumericLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]);
static UnaryPreincrementExpressions = TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]);
Expand Down
18 changes: 9 additions & 9 deletions tests/baselines/reference/APISample_linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,22 @@ function delint(sourceFile) {
delintNode(sourceFile);
function delintNode(node) {
switch (node.kind) {
case 187 /* ForStatement */:
case 188 /* ForInStatement */:
case 186 /* WhileStatement */:
case 185 /* DoStatement */:
if (node.statement.kind !== 180 /* Block */) {
case 188 /* ForStatement */:
case 189 /* ForInStatement */:
case 187 /* WhileStatement */:
case 186 /* DoStatement */:
if (node.statement.kind !== 181 /* Block */) {
report(node, "A looping statement's contents should be wrapped in a block body.");
}
break;
case 184 /* IfStatement */:
case 185 /* IfStatement */:
var ifStatement = node;
if (ifStatement.thenStatement.kind !== 180 /* Block */) {
if (ifStatement.thenStatement.kind !== 181 /* Block */) {
report(ifStatement.thenStatement, "An if statement's contents should be wrapped in a block body.");
}
if (ifStatement.elseStatement &&
ifStatement.elseStatement.kind !== 180 /* Block */ &&
ifStatement.elseStatement.kind !== 184 /* IfStatement */) {
ifStatement.elseStatement.kind !== 181 /* Block */ &&
ifStatement.elseStatement.kind !== 185 /* IfStatement */) {
report(ifStatement.elseStatement, "An else statement's contents should be wrapped in a block body.");
}
break;
Expand Down
12 changes: 12 additions & 0 deletions tests/baselines/reference/asOperator1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//// [asOperator1.ts]
var as = 43;
var x = undefined as number;
var y = (null as string).length;
var z = Date as any as string;


//// [asOperator1.js]
var as = 43;
var x = undefined;
var y = null.length;
var z = Date;
15 changes: 15 additions & 0 deletions tests/baselines/reference/asOperator1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
=== tests/cases/conformance/expressions/asOperator/asOperator1.ts ===
var as = 43;
>as : Symbol(as, Decl(asOperator1.ts, 0, 3))

var x = undefined as number;
>x : Symbol(x, Decl(asOperator1.ts, 1, 3))

var y = (null as string).length;
>y : Symbol(y, Decl(asOperator1.ts, 2, 3))
>(null as string).length : Symbol(String.length, Decl(lib.d.ts, 414, 19))
>length : Symbol(String.length, Decl(lib.d.ts, 414, 19))

var z = Date as any as string;
>z : Symbol(z, Decl(asOperator1.ts, 3, 3))

20 changes: 20 additions & 0 deletions tests/baselines/reference/asOperator1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
=== tests/cases/conformance/expressions/asOperator/asOperator1.ts ===
var as = 43;
>as : number
>43 : number

var x = undefined as number;
>x : number
>undefined : any

var y = (null as string).length;
>y : number
>(null as string).length : number
>(null as string) : string
>null : null
>length : number

var z = Date as any as string;
>z : string
>Date : any

8 changes: 8 additions & 0 deletions tests/baselines/reference/asOperator2.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
tests/cases/conformance/expressions/asOperator/asOperator2.ts(1,9): error TS2352: Neither type 'number' nor type 'string' is assignable to the other.


==== tests/cases/conformance/expressions/asOperator/asOperator2.ts (1 errors) ====
var x = 23 as string;
~~~~~~~~~~~~
!!! error TS2352: Neither type 'number' nor type 'string' is assignable to the other.

6 changes: 6 additions & 0 deletions tests/baselines/reference/asOperator2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//// [asOperator2.ts]
var x = 23 as string;


//// [asOperator2.js]
var x = 23;
22 changes: 22 additions & 0 deletions tests/baselines/reference/asOperator3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//// [asOperator3.ts]
declare function tag(...x: any[]): any;

var a = `${123 + 456 as number}`;
var b = `leading ${123 + 456 as number}`;
var c = `${123 + 456 as number} trailing`;
var d = `Hello ${123} World` as string;
var e = `Hello` as string;
var f = 1 + `${1} end of string` as string;
var g = tag `Hello ${123} World` as string;
var h = tag `Hello` as string;

//// [asOperator3.js]
var a = "" + 123 + 456;
var b = "leading " + 123 + 456;
var c = 123 + 456 + " trailing";
var d = "Hello " + 123 + " World";
var e = "Hello";
var f = 1 + (1 + " end of string");
var g = (_a = ["Hello ", " World"], _a.raw = ["Hello ", " World"], tag(_a, 123));
var h = (_b = ["Hello"], _b.raw = ["Hello"], tag(_b));
var _a, _b;
31 changes: 31 additions & 0 deletions tests/baselines/reference/asOperator3.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
=== tests/cases/conformance/expressions/asOperator/asOperator3.ts ===
declare function tag(...x: any[]): any;
>tag : Symbol(tag, Decl(asOperator3.ts, 0, 0))
>x : Symbol(x, Decl(asOperator3.ts, 0, 21))

var a = `${123 + 456 as number}`;
>a : Symbol(a, Decl(asOperator3.ts, 2, 3))

var b = `leading ${123 + 456 as number}`;
>b : Symbol(b, Decl(asOperator3.ts, 3, 3))

var c = `${123 + 456 as number} trailing`;
>c : Symbol(c, Decl(asOperator3.ts, 4, 3))

var d = `Hello ${123} World` as string;
>d : Symbol(d, Decl(asOperator3.ts, 5, 3))

var e = `Hello` as string;
>e : Symbol(e, Decl(asOperator3.ts, 6, 3))

var f = 1 + `${1} end of string` as string;
>f : Symbol(f, Decl(asOperator3.ts, 7, 3))

var g = tag `Hello ${123} World` as string;
>g : Symbol(g, Decl(asOperator3.ts, 8, 3))
>tag : Symbol(tag, Decl(asOperator3.ts, 0, 0))

var h = tag `Hello` as string;
>h : Symbol(h, Decl(asOperator3.ts, 9, 3))
>tag : Symbol(tag, Decl(asOperator3.ts, 0, 0))

55 changes: 55 additions & 0 deletions tests/baselines/reference/asOperator3.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
=== tests/cases/conformance/expressions/asOperator/asOperator3.ts ===
declare function tag(...x: any[]): any;
>tag : (...x: any[]) => any
>x : any[]

var a = `${123 + 456 as number}`;
>a : string
>`${123 + 456 as number}` : string
>123 + 456 : number
>123 : number
>456 : number

var b = `leading ${123 + 456 as number}`;
>b : string
>`leading ${123 + 456 as number}` : string
>123 + 456 : number
>123 : number
>456 : number

var c = `${123 + 456 as number} trailing`;
>c : string
>`${123 + 456 as number} trailing` : string
>123 + 456 : number
>123 : number
>456 : number

var d = `Hello ${123} World` as string;
>d : string
>`Hello ${123} World` : string
>123 : number

var e = `Hello` as string;
>e : string
>`Hello` : string

var f = 1 + `${1} end of string` as string;
>f : string
>1 + `${1} end of string` : string
>1 : number
>`${1} end of string` : string
>1 : number

var g = tag `Hello ${123} World` as string;
>g : string
>tag `Hello ${123} World` : any
>tag : (...x: any[]) => any
>`Hello ${123} World` : string
>123 : number

var h = tag `Hello` as string;
>h : string
>tag `Hello` : any
>tag : (...x: any[]) => any
>`Hello` : string

Loading