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

Use jsdoc casts #17251

Merged
merged 2 commits into from
Jul 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 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,18 @@ namespace ts {
return type;
}

function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
if (isInJavaScriptFile(node) && node.jsDoc) {
const typecasts = flatMap(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 +17790,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
129 changes: 129 additions & 0 deletions tests/baselines/reference/jsdocTypeTagCast.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
tests/cases/conformance/jsdoc/b.js(4,13): error TS2352: Type 'number' cannot be converted to type 'string'.
tests/cases/conformance/jsdoc/b.js(45,16): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeBase'.
Property 'p' is missing in type 'SomeOther'.
tests/cases/conformance/jsdoc/b.js(49,19): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeDerived'.
Property 'x' is missing in type 'SomeOther'.
tests/cases/conformance/jsdoc/b.js(51,17): error TS2352: Type 'SomeDerived' cannot be converted to type 'SomeOther'.
Property 'q' is missing in type 'SomeDerived'.
tests/cases/conformance/jsdoc/b.js(52,17): error TS2352: Type 'SomeBase' cannot be converted to type 'SomeOther'.
Property 'q' is missing in type 'SomeBase'.
tests/cases/conformance/jsdoc/b.js(58,1): error TS2322: Type '{ p: string | number | undefined; }' is not assignable to type 'SomeBase'.
Types of property 'p' are incompatible.
Type 'string | number | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.
tests/cases/conformance/jsdoc/b.js(66,8): error TS2352: Type 'boolean' cannot be converted to type 'string | number'.
tests/cases/conformance/jsdoc/b.js(66,15): error TS2304: Cannot find name 'numOrStr'.
tests/cases/conformance/jsdoc/b.js(66,24): error TS1005: '}' expected.
tests/cases/conformance/jsdoc/b.js(66,38): error TS2454: Variable 'numOrStr' is used before being assigned.
tests/cases/conformance/jsdoc/b.js(67,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(67,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 (12 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;
}
}

function SomeFakeClass() {
/** @type {string|number} */
this.p = "bar";
}

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

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);

someFakeClass = someBase;
someFakeClass = someDerived;

someBase = someFakeClass; // Error
~~~~~~~~
!!! error TS2322: Type '{ p: string | number | undefined; }' is not assignable to type 'SomeBase'.
!!! error TS2322: Types of property 'p' are incompatible.
!!! error TS2322: Type 'string | number | undefined' is not assignable to type 'number'.
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
someBase = /** @type {SomeBase} */(someFakeClass);

// 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.
}



151 changes: 151 additions & 0 deletions tests/baselines/reference/jsdocTypeTagCast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//// [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;
}
}

function SomeFakeClass() {
/** @type {string|number} */
this.p = "bar";
}

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

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);

someFakeClass = someBase;
someFakeClass = someDerived;

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

// 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;
}());
function SomeFakeClass() {
/** @type {string|number} */
this.p = "bar";
}
// Type assertion should check for assignability in either direction
var someBase = new SomeBase();
var someDerived = new SomeDerived();
var someOther = new SomeOther();
var someFakeClass = new SomeFakeClass();
someBase = (someDerived);
someBase = (someBase);
someBase = (someOther); // Error
someDerived = (someDerived);
someDerived = (someBase);
someDerived = (someOther); // Error
someOther = (someDerived); // Error
someOther = (someBase); // Error
someOther = (someOther);
someFakeClass = someBase;
someFakeClass = someDerived;
someBase = someFakeClass; // Error
someBase = (someFakeClass);
// 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
}
Loading