Skip to content

Commit

Permalink
Allow jsdoc casts of parenthesized expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
weswigham committed Jul 17, 2017
1 parent de9a67f commit b5ec445
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 7 deletions.
28 changes: 23 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16256,15 +16256,19 @@ namespace ts {
}

function checkAssertion(node: AssertionExpression) {
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(node.expression)));
return checkAssertionWorker(node, node.type, node.expression);
}

checkSourceElement(node.type);
const targetType = getTypeFromTypeNode(node.type);
function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) {
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression, checkMode)));

checkSourceElement(type);
const targetType = getTypeFromTypeNode(type);

if (produceDiagnostics && targetType !== unknownType) {
const widenedType = getWidenedType(exprType);
if (!isTypeComparableTo(targetType, widenedType)) {
checkTypeComparableTo(exprType, targetType, node, Diagnostics.Type_0_cannot_be_converted_to_type_1);
checkTypeComparableTo(exprType, targetType, errNode, Diagnostics.Type_0_cannot_be_converted_to_type_1);
}
}
return targetType;
Expand Down Expand Up @@ -17735,6 +17739,20 @@ namespace ts {
return type;
}

function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
if (isInJavaScriptFile(node)) {
if (node.jsDoc) {
const typecasts = flatten<JSDocTag>(map(node.jsDoc, doc => filter(doc.tags, tag => tag.kind === SyntaxKind.JSDocTypeTag)));
if (typecasts && typecasts.length) {
// We should have already issued an error if there were multiple type jsdocs
const cast = typecasts[0] as JSDocTypeTag;
return checkAssertionWorker(cast, cast.typeExpression.type, node.expression, checkMode);
}
}
}
return checkExpression(node.expression, checkMode);
}

function checkExpressionWorker(node: Expression, checkMode: CheckMode): Type {
switch (node.kind) {
case SyntaxKind.Identifier:
Expand Down Expand Up @@ -17774,7 +17792,7 @@ namespace ts {
case SyntaxKind.TaggedTemplateExpression:
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.ParenthesizedExpression:
return checkExpression((<ParenthesizedExpression>node).expression, checkMode);
return checkParenthesizedExpression(<ParenthesizedExpression>node, checkMode);
case SyntaxKind.ClassExpression:
return checkClassExpression(<ClassExpression>node);
case SyntaxKind.FunctionExpression:
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4342,7 +4342,7 @@ namespace ts {
parseExpected(SyntaxKind.OpenParenToken);
node.expression = allowInAnd(parseExpression);
parseExpected(SyntaxKind.CloseParenToken);
return finishNode(node);
return addJSDocComment(finishNode(node));
}

function parseSpreadElement(): Expression {
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,8 @@ namespace ts {
const commentRanges = (node.kind === SyntaxKind.Parameter ||
node.kind === SyntaxKind.TypeParameter ||
node.kind === SyntaxKind.FunctionExpression ||
node.kind === SyntaxKind.ArrowFunction) ?
node.kind === SyntaxKind.ArrowFunction ||
node.kind === SyntaxKind.ParenthesizedExpression) ?
concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
getLeadingCommentRangesOfNodeFromText(node, text);
// True if the comment starts with '/**' but not if it is '/**/'
Expand Down
108 changes: 108 additions & 0 deletions tests/baselines/reference/jsdocTypeTagCast.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
tests/cases/conformance/jsdoc/b.js(4,13): error TS2352: Type 'number' cannot be converted to type 'string'.
tests/cases/conformance/jsdoc/b.js(39,16): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeBase'.
Property 'p' is missing in type 'SomeOther'.
tests/cases/conformance/jsdoc/b.js(43,19): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeDerived'.
Property 'x' is missing in type 'SomeOther'.
tests/cases/conformance/jsdoc/b.js(45,17): error TS2352: Type 'SomeDerived' cannot be converted to type 'SomeOther'.
Property 'q' is missing in type 'SomeDerived'.
tests/cases/conformance/jsdoc/b.js(46,17): error TS2352: Type 'SomeBase' cannot be converted to type 'SomeOther'.
Property 'q' is missing in type 'SomeBase'.
tests/cases/conformance/jsdoc/b.js(54,8): error TS2352: Type 'boolean' cannot be converted to type 'string | number'.
tests/cases/conformance/jsdoc/b.js(54,15): error TS2304: Cannot find name 'numOrStr'.
tests/cases/conformance/jsdoc/b.js(54,24): error TS1005: '}' expected.
tests/cases/conformance/jsdoc/b.js(54,38): error TS2454: Variable 'numOrStr' is used before being assigned.
tests/cases/conformance/jsdoc/b.js(55,2): error TS2322: Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsdoc/b.js(55,8): error TS2454: Variable 'numOrStr' is used before being assigned.


==== tests/cases/conformance/jsdoc/a.ts (0 errors) ====
var W: string;

==== tests/cases/conformance/jsdoc/b.js (11 errors) ====
// @ts-check
var W = /** @type {string} */(/** @type {*} */ (4));

var W = /** @type {string} */(4); // Error
~~~~~~~~~~~~~~
!!! error TS2352: Type 'number' cannot be converted to type 'string'.

/** @type {*} */
var a;

/** @type {string} */
var s;

var a = /** @type {*} */("" + 4);
var s = "" + /** @type {*} */(4);

class SomeBase {
constructor() {
this.p = 42;
}
}
class SomeDerived extends SomeBase {
constructor() {
super();
this.x = 42;
}
}
class SomeOther {
constructor() {
this.q = 42;
}
}

// Type assertion should check for assignability in either direction
var someBase = new SomeBase();
var someDerived = new SomeDerived();
var someOther = new SomeOther();

someBase = /** @type {SomeBase} */(someDerived);
someBase = /** @type {SomeBase} */(someBase);
someBase = /** @type {SomeBase} */(someOther); // Error
~~~~~~~~~~~~~~~~
!!! error TS2352: Type 'SomeOther' cannot be converted to type 'SomeBase'.
!!! error TS2352: Property 'p' is missing in type 'SomeOther'.

someDerived = /** @type {SomeDerived} */(someDerived);
someDerived = /** @type {SomeDerived} */(someBase);
someDerived = /** @type {SomeDerived} */(someOther); // Error
~~~~~~~~~~~~~~~~~~~
!!! error TS2352: Type 'SomeOther' cannot be converted to type 'SomeDerived'.
!!! error TS2352: Property 'x' is missing in type 'SomeOther'.

someOther = /** @type {SomeOther} */(someDerived); // Error
~~~~~~~~~~~~~~~~~
!!! error TS2352: Type 'SomeDerived' cannot be converted to type 'SomeOther'.
!!! error TS2352: Property 'q' is missing in type 'SomeDerived'.
someOther = /** @type {SomeOther} */(someBase); // Error
~~~~~~~~~~~~~~~~~
!!! error TS2352: Type 'SomeBase' cannot be converted to type 'SomeOther'.
!!! error TS2352: Property 'q' is missing in type 'SomeBase'.
someOther = /** @type {SomeOther} */(someOther);

// Type assertion cannot be a type-predicate type
/** @type {number | string} */
var numOrStr;
/** @type {string} */
var str;
if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
~~~~~~~~~~~~~~~
!!! error TS2352: Type 'boolean' cannot be converted to type 'string | number'.
~~~~~~~~
!!! error TS2304: Cannot find name 'numOrStr'.
~~
!!! error TS1005: '}' expected.
~~~~~~~~
!!! error TS2454: Variable 'numOrStr' is used before being assigned.
str = numOrStr; // Error, no narrowing occurred
~~~
!!! error TS2322: Type 'string | number' is not assignable to type 'string'.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
~~~~~~~~
!!! error TS2454: Variable 'numOrStr' is used before being assigned.
}



130 changes: 130 additions & 0 deletions tests/baselines/reference/jsdocTypeTagCast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//// [tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts] ////

//// [a.ts]
var W: string;

//// [b.js]
// @ts-check
var W = /** @type {string} */(/** @type {*} */ (4));

var W = /** @type {string} */(4); // Error

/** @type {*} */
var a;

/** @type {string} */
var s;

var a = /** @type {*} */("" + 4);
var s = "" + /** @type {*} */(4);

class SomeBase {
constructor() {
this.p = 42;
}
}
class SomeDerived extends SomeBase {
constructor() {
super();
this.x = 42;
}
}
class SomeOther {
constructor() {
this.q = 42;
}
}

// Type assertion should check for assignability in either direction
var someBase = new SomeBase();
var someDerived = new SomeDerived();
var someOther = new SomeOther();

someBase = /** @type {SomeBase} */(someDerived);
someBase = /** @type {SomeBase} */(someBase);
someBase = /** @type {SomeBase} */(someOther); // Error

someDerived = /** @type {SomeDerived} */(someDerived);
someDerived = /** @type {SomeDerived} */(someBase);
someDerived = /** @type {SomeDerived} */(someOther); // Error

someOther = /** @type {SomeOther} */(someDerived); // Error
someOther = /** @type {SomeOther} */(someBase); // Error
someOther = /** @type {SomeOther} */(someOther);

// Type assertion cannot be a type-predicate type
/** @type {number | string} */
var numOrStr;
/** @type {string} */
var str;
if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
str = numOrStr; // Error, no narrowing occurred
}




//// [a.js]
var W;
//// [b.js]
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
// @ts-check
var W = ((4));
var W = (4); // Error
/** @type {*} */
var a;
/** @type {string} */
var s;
var a = ("" + 4);
var s = "" + (4);
var SomeBase = (function () {
function SomeBase() {
this.p = 42;
}
return SomeBase;
}());
var SomeDerived = (function (_super) {
__extends(SomeDerived, _super);
function SomeDerived() {
var _this = _super.call(this) || this;
_this.x = 42;
return _this;
}
return SomeDerived;
}(SomeBase));
var SomeOther = (function () {
function SomeOther() {
this.q = 42;
}
return SomeOther;
}());
// Type assertion should check for assignability in either direction
var someBase = new SomeBase();
var someDerived = new SomeDerived();
var someOther = new SomeOther();
someBase = (someDerived);
someBase = (someBase);
someBase = (someOther); // Error
someDerived = (someDerived);
someDerived = (someBase);
someDerived = (someOther); // Error
someOther = (someDerived); // Error
someOther = (someBase); // Error
someOther = (someOther);
// Type assertion cannot be a type-predicate type
/** @type {number | string} */
var numOrStr;
/** @type {string} */
var str;
if ((numOrStr === undefined)) {
str = numOrStr; // Error, no narrowing occurred
}
66 changes: 66 additions & 0 deletions tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// @allowJS: true
// @suppressOutputPathCheck: true
// @strictNullChecks: true

// @filename: a.ts
var W: string;

// @filename: b.js
// @ts-check
var W = /** @type {string} */(/** @type {*} */ (4));

var W = /** @type {string} */(4); // Error

/** @type {*} */
var a;

/** @type {string} */
var s;

var a = /** @type {*} */("" + 4);
var s = "" + /** @type {*} */(4);

class SomeBase {
constructor() {
this.p = 42;
}
}
class SomeDerived extends SomeBase {
constructor() {
super();
this.x = 42;
}
}
class SomeOther {
constructor() {
this.q = 42;
}
}

// Type assertion should check for assignability in either direction
var someBase = new SomeBase();
var someDerived = new SomeDerived();
var someOther = new SomeOther();

someBase = /** @type {SomeBase} */(someDerived);
someBase = /** @type {SomeBase} */(someBase);
someBase = /** @type {SomeBase} */(someOther); // Error

someDerived = /** @type {SomeDerived} */(someDerived);
someDerived = /** @type {SomeDerived} */(someBase);
someDerived = /** @type {SomeDerived} */(someOther); // Error

someOther = /** @type {SomeOther} */(someDerived); // Error
someOther = /** @type {SomeOther} */(someBase); // Error
someOther = /** @type {SomeOther} */(someOther);

// Type assertion cannot be a type-predicate type
/** @type {number | string} */
var numOrStr;
/** @type {string} */
var str;
if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
str = numOrStr; // Error, no narrowing occurred
}


0 comments on commit b5ec445

Please sign in to comment.