Skip to content

Commit 8a1cd33

Browse files
authored
Use jsdoc casts (#17251)
* Allow jsdoc casts of parenthesized expressions * Feedback from #17211
1 parent de9a67f commit 8a1cd33

File tree

6 files changed

+382
-7
lines changed

6 files changed

+382
-7
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16256,15 +16256,19 @@ namespace ts {
1625616256
}
1625716257

1625816258
function checkAssertion(node: AssertionExpression) {
16259-
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(node.expression)));
16259+
return checkAssertionWorker(node, node.type, node.expression);
16260+
}
1626016261

16261-
checkSourceElement(node.type);
16262-
const targetType = getTypeFromTypeNode(node.type);
16262+
function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) {
16263+
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression, checkMode)));
16264+
16265+
checkSourceElement(type);
16266+
const targetType = getTypeFromTypeNode(type);
1626316267

1626416268
if (produceDiagnostics && targetType !== unknownType) {
1626516269
const widenedType = getWidenedType(exprType);
1626616270
if (!isTypeComparableTo(targetType, widenedType)) {
16267-
checkTypeComparableTo(exprType, targetType, node, Diagnostics.Type_0_cannot_be_converted_to_type_1);
16271+
checkTypeComparableTo(exprType, targetType, errNode, Diagnostics.Type_0_cannot_be_converted_to_type_1);
1626816272
}
1626916273
}
1627016274
return targetType;
@@ -17735,6 +17739,18 @@ namespace ts {
1773517739
return type;
1773617740
}
1773717741

17742+
function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
17743+
if (isInJavaScriptFile(node) && node.jsDoc) {
17744+
const typecasts = flatMap(node.jsDoc, doc => filter(doc.tags, tag => tag.kind === SyntaxKind.JSDocTypeTag));
17745+
if (typecasts && typecasts.length) {
17746+
// We should have already issued an error if there were multiple type jsdocs
17747+
const cast = typecasts[0] as JSDocTypeTag;
17748+
return checkAssertionWorker(cast, cast.typeExpression.type, node.expression, checkMode);
17749+
}
17750+
}
17751+
return checkExpression(node.expression, checkMode);
17752+
}
17753+
1773817754
function checkExpressionWorker(node: Expression, checkMode: CheckMode): Type {
1773917755
switch (node.kind) {
1774017756
case SyntaxKind.Identifier:
@@ -17774,7 +17790,7 @@ namespace ts {
1777417790
case SyntaxKind.TaggedTemplateExpression:
1777517791
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node);
1777617792
case SyntaxKind.ParenthesizedExpression:
17777-
return checkExpression((<ParenthesizedExpression>node).expression, checkMode);
17793+
return checkParenthesizedExpression(<ParenthesizedExpression>node, checkMode);
1777817794
case SyntaxKind.ClassExpression:
1777917795
return checkClassExpression(<ClassExpression>node);
1778017796
case SyntaxKind.FunctionExpression:

src/compiler/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4342,7 +4342,7 @@ namespace ts {
43424342
parseExpected(SyntaxKind.OpenParenToken);
43434343
node.expression = allowInAnd(parseExpression);
43444344
parseExpected(SyntaxKind.CloseParenToken);
4345-
return finishNode(node);
4345+
return addJSDocComment(finishNode(node));
43464346
}
43474347

43484348
function parseSpreadElement(): Expression {

src/compiler/utilities.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,8 @@ namespace ts {
640640
const commentRanges = (node.kind === SyntaxKind.Parameter ||
641641
node.kind === SyntaxKind.TypeParameter ||
642642
node.kind === SyntaxKind.FunctionExpression ||
643-
node.kind === SyntaxKind.ArrowFunction) ?
643+
node.kind === SyntaxKind.ArrowFunction ||
644+
node.kind === SyntaxKind.ParenthesizedExpression) ?
644645
concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
645646
getLeadingCommentRangesOfNodeFromText(node, text);
646647
// True if the comment starts with '/**' but not if it is '/**/'
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
tests/cases/conformance/jsdoc/b.js(4,13): error TS2352: Type 'number' cannot be converted to type 'string'.
2+
tests/cases/conformance/jsdoc/b.js(45,16): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeBase'.
3+
Property 'p' is missing in type 'SomeOther'.
4+
tests/cases/conformance/jsdoc/b.js(49,19): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeDerived'.
5+
Property 'x' is missing in type 'SomeOther'.
6+
tests/cases/conformance/jsdoc/b.js(51,17): error TS2352: Type 'SomeDerived' cannot be converted to type 'SomeOther'.
7+
Property 'q' is missing in type 'SomeDerived'.
8+
tests/cases/conformance/jsdoc/b.js(52,17): error TS2352: Type 'SomeBase' cannot be converted to type 'SomeOther'.
9+
Property 'q' is missing in type 'SomeBase'.
10+
tests/cases/conformance/jsdoc/b.js(58,1): error TS2322: Type '{ p: string | number | undefined; }' is not assignable to type 'SomeBase'.
11+
Types of property 'p' are incompatible.
12+
Type 'string | number | undefined' is not assignable to type 'number'.
13+
Type 'undefined' is not assignable to type 'number'.
14+
tests/cases/conformance/jsdoc/b.js(66,8): error TS2352: Type 'boolean' cannot be converted to type 'string | number'.
15+
tests/cases/conformance/jsdoc/b.js(66,15): error TS2304: Cannot find name 'numOrStr'.
16+
tests/cases/conformance/jsdoc/b.js(66,24): error TS1005: '}' expected.
17+
tests/cases/conformance/jsdoc/b.js(66,38): error TS2454: Variable 'numOrStr' is used before being assigned.
18+
tests/cases/conformance/jsdoc/b.js(67,2): error TS2322: Type 'string | number' is not assignable to type 'string'.
19+
Type 'number' is not assignable to type 'string'.
20+
tests/cases/conformance/jsdoc/b.js(67,8): error TS2454: Variable 'numOrStr' is used before being assigned.
21+
22+
23+
==== tests/cases/conformance/jsdoc/a.ts (0 errors) ====
24+
var W: string;
25+
26+
==== tests/cases/conformance/jsdoc/b.js (12 errors) ====
27+
// @ts-check
28+
var W = /** @type {string} */(/** @type {*} */ (4));
29+
30+
var W = /** @type {string} */(4); // Error
31+
~~~~~~~~~~~~~~
32+
!!! error TS2352: Type 'number' cannot be converted to type 'string'.
33+
34+
/** @type {*} */
35+
var a;
36+
37+
/** @type {string} */
38+
var s;
39+
40+
var a = /** @type {*} */("" + 4);
41+
var s = "" + /** @type {*} */(4);
42+
43+
class SomeBase {
44+
constructor() {
45+
this.p = 42;
46+
}
47+
}
48+
class SomeDerived extends SomeBase {
49+
constructor() {
50+
super();
51+
this.x = 42;
52+
}
53+
}
54+
class SomeOther {
55+
constructor() {
56+
this.q = 42;
57+
}
58+
}
59+
60+
function SomeFakeClass() {
61+
/** @type {string|number} */
62+
this.p = "bar";
63+
}
64+
65+
// Type assertion should check for assignability in either direction
66+
var someBase = new SomeBase();
67+
var someDerived = new SomeDerived();
68+
var someOther = new SomeOther();
69+
var someFakeClass = new SomeFakeClass();
70+
71+
someBase = /** @type {SomeBase} */(someDerived);
72+
someBase = /** @type {SomeBase} */(someBase);
73+
someBase = /** @type {SomeBase} */(someOther); // Error
74+
~~~~~~~~~~~~~~~~
75+
!!! error TS2352: Type 'SomeOther' cannot be converted to type 'SomeBase'.
76+
!!! error TS2352: Property 'p' is missing in type 'SomeOther'.
77+
78+
someDerived = /** @type {SomeDerived} */(someDerived);
79+
someDerived = /** @type {SomeDerived} */(someBase);
80+
someDerived = /** @type {SomeDerived} */(someOther); // Error
81+
~~~~~~~~~~~~~~~~~~~
82+
!!! error TS2352: Type 'SomeOther' cannot be converted to type 'SomeDerived'.
83+
!!! error TS2352: Property 'x' is missing in type 'SomeOther'.
84+
85+
someOther = /** @type {SomeOther} */(someDerived); // Error
86+
~~~~~~~~~~~~~~~~~
87+
!!! error TS2352: Type 'SomeDerived' cannot be converted to type 'SomeOther'.
88+
!!! error TS2352: Property 'q' is missing in type 'SomeDerived'.
89+
someOther = /** @type {SomeOther} */(someBase); // Error
90+
~~~~~~~~~~~~~~~~~
91+
!!! error TS2352: Type 'SomeBase' cannot be converted to type 'SomeOther'.
92+
!!! error TS2352: Property 'q' is missing in type 'SomeBase'.
93+
someOther = /** @type {SomeOther} */(someOther);
94+
95+
someFakeClass = someBase;
96+
someFakeClass = someDerived;
97+
98+
someBase = someFakeClass; // Error
99+
~~~~~~~~
100+
!!! error TS2322: Type '{ p: string | number | undefined; }' is not assignable to type 'SomeBase'.
101+
!!! error TS2322: Types of property 'p' are incompatible.
102+
!!! error TS2322: Type 'string | number | undefined' is not assignable to type 'number'.
103+
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
104+
someBase = /** @type {SomeBase} */(someFakeClass);
105+
106+
// Type assertion cannot be a type-predicate type
107+
/** @type {number | string} */
108+
var numOrStr;
109+
/** @type {string} */
110+
var str;
111+
if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
112+
~~~~~~~~~~~~~~~
113+
!!! error TS2352: Type 'boolean' cannot be converted to type 'string | number'.
114+
~~~~~~~~
115+
!!! error TS2304: Cannot find name 'numOrStr'.
116+
~~
117+
!!! error TS1005: '}' expected.
118+
~~~~~~~~
119+
!!! error TS2454: Variable 'numOrStr' is used before being assigned.
120+
str = numOrStr; // Error, no narrowing occurred
121+
~~~
122+
!!! error TS2322: Type 'string | number' is not assignable to type 'string'.
123+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
124+
~~~~~~~~
125+
!!! error TS2454: Variable 'numOrStr' is used before being assigned.
126+
}
127+
128+
129+
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//// [tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts] ////
2+
3+
//// [a.ts]
4+
var W: string;
5+
6+
//// [b.js]
7+
// @ts-check
8+
var W = /** @type {string} */(/** @type {*} */ (4));
9+
10+
var W = /** @type {string} */(4); // Error
11+
12+
/** @type {*} */
13+
var a;
14+
15+
/** @type {string} */
16+
var s;
17+
18+
var a = /** @type {*} */("" + 4);
19+
var s = "" + /** @type {*} */(4);
20+
21+
class SomeBase {
22+
constructor() {
23+
this.p = 42;
24+
}
25+
}
26+
class SomeDerived extends SomeBase {
27+
constructor() {
28+
super();
29+
this.x = 42;
30+
}
31+
}
32+
class SomeOther {
33+
constructor() {
34+
this.q = 42;
35+
}
36+
}
37+
38+
function SomeFakeClass() {
39+
/** @type {string|number} */
40+
this.p = "bar";
41+
}
42+
43+
// Type assertion should check for assignability in either direction
44+
var someBase = new SomeBase();
45+
var someDerived = new SomeDerived();
46+
var someOther = new SomeOther();
47+
var someFakeClass = new SomeFakeClass();
48+
49+
someBase = /** @type {SomeBase} */(someDerived);
50+
someBase = /** @type {SomeBase} */(someBase);
51+
someBase = /** @type {SomeBase} */(someOther); // Error
52+
53+
someDerived = /** @type {SomeDerived} */(someDerived);
54+
someDerived = /** @type {SomeDerived} */(someBase);
55+
someDerived = /** @type {SomeDerived} */(someOther); // Error
56+
57+
someOther = /** @type {SomeOther} */(someDerived); // Error
58+
someOther = /** @type {SomeOther} */(someBase); // Error
59+
someOther = /** @type {SomeOther} */(someOther);
60+
61+
someFakeClass = someBase;
62+
someFakeClass = someDerived;
63+
64+
someBase = someFakeClass; // Error
65+
someBase = /** @type {SomeBase} */(someFakeClass);
66+
67+
// Type assertion cannot be a type-predicate type
68+
/** @type {number | string} */
69+
var numOrStr;
70+
/** @type {string} */
71+
var str;
72+
if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
73+
str = numOrStr; // Error, no narrowing occurred
74+
}
75+
76+
77+
78+
79+
//// [a.js]
80+
var W;
81+
//// [b.js]
82+
var __extends = (this && this.__extends) || (function () {
83+
var extendStatics = Object.setPrototypeOf ||
84+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
85+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
86+
return function (d, b) {
87+
extendStatics(d, b);
88+
function __() { this.constructor = d; }
89+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
90+
};
91+
})();
92+
// @ts-check
93+
var W = ((4));
94+
var W = (4); // Error
95+
/** @type {*} */
96+
var a;
97+
/** @type {string} */
98+
var s;
99+
var a = ("" + 4);
100+
var s = "" + (4);
101+
var SomeBase = (function () {
102+
function SomeBase() {
103+
this.p = 42;
104+
}
105+
return SomeBase;
106+
}());
107+
var SomeDerived = (function (_super) {
108+
__extends(SomeDerived, _super);
109+
function SomeDerived() {
110+
var _this = _super.call(this) || this;
111+
_this.x = 42;
112+
return _this;
113+
}
114+
return SomeDerived;
115+
}(SomeBase));
116+
var SomeOther = (function () {
117+
function SomeOther() {
118+
this.q = 42;
119+
}
120+
return SomeOther;
121+
}());
122+
function SomeFakeClass() {
123+
/** @type {string|number} */
124+
this.p = "bar";
125+
}
126+
// Type assertion should check for assignability in either direction
127+
var someBase = new SomeBase();
128+
var someDerived = new SomeDerived();
129+
var someOther = new SomeOther();
130+
var someFakeClass = new SomeFakeClass();
131+
someBase = (someDerived);
132+
someBase = (someBase);
133+
someBase = (someOther); // Error
134+
someDerived = (someDerived);
135+
someDerived = (someBase);
136+
someDerived = (someOther); // Error
137+
someOther = (someDerived); // Error
138+
someOther = (someBase); // Error
139+
someOther = (someOther);
140+
someFakeClass = someBase;
141+
someFakeClass = someDerived;
142+
someBase = someFakeClass; // Error
143+
someBase = (someFakeClass);
144+
// Type assertion cannot be a type-predicate type
145+
/** @type {number | string} */
146+
var numOrStr;
147+
/** @type {string} */
148+
var str;
149+
if ((numOrStr === undefined)) {
150+
str = numOrStr; // Error, no narrowing occurred
151+
}

0 commit comments

Comments
 (0)