Skip to content

Commit 0222211

Browse files
authored
Support for/of variables in assertion checking (#37432)
* Support for/of variables in assertion checking * Integrate with "dotted name" logic * Add tests
1 parent c8e43d8 commit 0222211

File tree

6 files changed

+325
-63
lines changed

6 files changed

+325
-63
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7266,7 +7266,7 @@ namespace ts {
72667266
// [Symbol.iterator] or next). This may be because we accessed properties from anyType,
72677267
// or it may have led to an error inside getElementTypeOfIterable.
72687268
const forOfStatement = declaration.parent.parent;
7269-
return checkRightHandSideOfForOf(forOfStatement.expression, forOfStatement.awaitModifier) || anyType;
7269+
return checkRightHandSideOfForOf(forOfStatement) || anyType;
72707270
}
72717271

72727272
if (isBindingPattern(declaration.parent)) {
@@ -19204,7 +19204,7 @@ namespace ts {
1920419204
case SyntaxKind.ForInStatement:
1920519205
return stringType;
1920619206
case SyntaxKind.ForOfStatement:
19207-
return checkRightHandSideOfForOf((<ForOfStatement>parent).expression, (<ForOfStatement>parent).awaitModifier) || errorType;
19207+
return checkRightHandSideOfForOf(<ForOfStatement>parent) || errorType;
1920819208
case SyntaxKind.BinaryExpression:
1920919209
return getAssignedTypeOfBinaryExpression(<BinaryExpression>parent);
1921019210
case SyntaxKind.DeleteExpression:
@@ -19248,7 +19248,7 @@ namespace ts {
1924819248
return stringType;
1924919249
}
1925019250
if (node.parent.parent.kind === SyntaxKind.ForOfStatement) {
19251-
return checkRightHandSideOfForOf(node.parent.parent.expression, node.parent.parent.awaitModifier) || errorType;
19251+
return checkRightHandSideOfForOf(node.parent.parent) || errorType;
1925219252
}
1925319253
return errorType;
1925419254
}
@@ -19513,23 +19513,31 @@ namespace ts {
1951319513
return isLengthPushOrUnshift || isElementAssignment;
1951419514
}
1951519515

19516-
function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration | undefined) {
19517-
return !!(declaration && (
19518-
declaration.kind === SyntaxKind.VariableDeclaration || declaration.kind === SyntaxKind.Parameter ||
19516+
function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration) {
19517+
return (declaration.kind === SyntaxKind.VariableDeclaration || declaration.kind === SyntaxKind.Parameter ||
1951919518
declaration.kind === SyntaxKind.PropertyDeclaration || declaration.kind === SyntaxKind.PropertySignature) &&
19520-
getEffectiveTypeAnnotationNode(declaration as VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature));
19519+
!!getEffectiveTypeAnnotationNode(declaration as VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature);
1952119520
}
1952219521

1952319522
function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) {
1952419523
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) {
1952519524
return getTypeOfSymbol(symbol);
1952619525
}
1952719526
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
19528-
if (isDeclarationWithExplicitTypeAnnotation(symbol.valueDeclaration)) {
19529-
return getTypeOfSymbol(symbol);
19530-
}
19531-
if (diagnostic && symbol.valueDeclaration) {
19532-
addRelatedInfo(diagnostic, createDiagnosticForNode(symbol.valueDeclaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol)));
19527+
const declaration = symbol.valueDeclaration;
19528+
if (declaration) {
19529+
if (isDeclarationWithExplicitTypeAnnotation(declaration)) {
19530+
return getTypeOfSymbol(symbol);
19531+
}
19532+
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
19533+
const expressionType = getTypeOfDottedName(declaration.parent.parent.expression, /*diagnostic*/ undefined);
19534+
if (expressionType) {
19535+
return getForOfIterationType(declaration.parent.parent, expressionType);
19536+
}
19537+
}
19538+
if (diagnostic) {
19539+
addRelatedInfo(diagnostic, createDiagnosticForNode(declaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol)));
19540+
}
1953319541
}
1953419542
}
1953519543
}
@@ -31585,7 +31593,7 @@ namespace ts {
3158531593
}
3158631594
else {
3158731595
const varExpr = node.initializer;
31588-
const iteratedType = checkRightHandSideOfForOf(node.expression, node.awaitModifier);
31596+
const iteratedType = checkRightHandSideOfForOf(node);
3158931597

3159031598
// There may be a destructuring assignment on the left side
3159131599
if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) {
@@ -31677,10 +31685,13 @@ namespace ts {
3167731685
}
3167831686
}
3167931687

31680-
function checkRightHandSideOfForOf(rhsExpression: Expression, awaitModifier: AwaitKeywordToken | undefined): Type {
31681-
const expressionType = checkNonNullExpression(rhsExpression);
31682-
const use = awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf;
31683-
return checkIteratedTypeOrElementType(use, expressionType, undefinedType, rhsExpression);
31688+
function checkRightHandSideOfForOf(statement: ForOfStatement): Type {
31689+
return getForOfIterationType(statement, checkNonNullExpression(statement.expression));
31690+
}
31691+
31692+
function getForOfIterationType(statement: ForOfStatement, expressionType: Type) {
31693+
const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf;
31694+
return checkIteratedTypeOrElementType(use, expressionType, undefinedType, statement.expression);
3168431695
}
3168531696

3168631697
function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type {
@@ -35046,7 +35057,7 @@ namespace ts {
3504635057
// for ( { a } of elems) {
3504735058
// }
3504835059
if (expr.parent.kind === SyntaxKind.ForOfStatement) {
35049-
const iteratedType = checkRightHandSideOfForOf((<ForOfStatement>expr.parent).expression, (<ForOfStatement>expr.parent).awaitModifier);
35060+
const iteratedType = checkRightHandSideOfForOf(<ForOfStatement>expr.parent);
3505035061
return checkDestructuringAssignment(expr, iteratedType || errorType);
3505135062
}
3505235063
// If this is from "for" initializer

tests/baselines/reference/assertionTypePredicates1.errors.txt

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(43,9): error TS7
33
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(87,9): error TS7027: Unreachable code detected.
44
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(122,9): error TS7027: Unreachable code detected.
55
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(137,9): error TS7027: Unreachable code detected.
6-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(143,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
7-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(144,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
8-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(145,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
9-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(148,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
10-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(149,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
11-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(150,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
12-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(151,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
13-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(156,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
14-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(158,5): error TS2776: Assertions require the call target to be an identifier or qualified name.
15-
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(160,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
6+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(153,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
7+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(154,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
8+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(155,37): error TS1228: A type predicate is only allowed in return type position for functions and methods.
9+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(158,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
10+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(159,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
11+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(160,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
12+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(161,15): error TS1228: A type predicate is only allowed in return type position for functions and methods.
13+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(166,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
14+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(168,5): error TS2776: Assertions require the call target to be an identifier or qualified name.
15+
tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(170,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
1616

1717

1818
==== tests/cases/conformance/controlFlow/assertionTypePredicates1.ts (15 errors) ====
@@ -166,6 +166,16 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(160,5): error TS
166166
}
167167
}
168168

169+
function f11(items: Test[]) {
170+
for (let item of items) {
171+
if (item.isTest2()) {
172+
item.z;
173+
}
174+
item.assertIsTest2();
175+
item.z;
176+
}
177+
}
178+
169179
// Invalid constructs
170180

171181
declare let Q1: new (x: unknown) => x is string;
@@ -198,7 +208,7 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(160,5): error TS
198208
assert(typeof x === "string"); // Error
199209
~~~~~~
200210
!!! error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
201-
!!! related TS2782 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:155:11: 'assert' needs an explicit type annotation.
211+
!!! related TS2782 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:165:11: 'assert' needs an explicit type annotation.
202212
const a = [assert];
203213
a[0](typeof x === "string"); // Error
204214
~~~~
@@ -207,8 +217,26 @@ tests/cases/conformance/controlFlow/assertionTypePredicates1.ts(160,5): error TS
207217
t1.assert(typeof x === "string"); // Error
208218
~~~~~~~~~
209219
!!! error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
210-
!!! related TS2782 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:159:11: 't1' needs an explicit type annotation.
220+
!!! related TS2782 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:169:11: 't1' needs an explicit type annotation.
211221
const t2: Test = new Test();
212222
t2.assert(typeof x === "string");
213223
}
224+
225+
// Repro from #35940
226+
227+
interface Thing {
228+
good: boolean;
229+
isGood(): asserts this is GoodThing;
230+
}
231+
232+
interface GoodThing {
233+
good: true;
234+
}
235+
236+
function example1(things: Thing[]) {
237+
for (let thing of things) {
238+
thing.isGood();
239+
thing.good;
240+
}
241+
}
214242

tests/baselines/reference/assertionTypePredicates1.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ class Derived extends Test {
139139
}
140140
}
141141

142+
function f11(items: Test[]) {
143+
for (let item of items) {
144+
if (item.isTest2()) {
145+
item.z;
146+
}
147+
item.assertIsTest2();
148+
item.z;
149+
}
150+
}
151+
142152
// Invalid constructs
143153

144154
declare let Q1: new (x: unknown) => x is string;
@@ -162,6 +172,24 @@ function f20(x: unknown) {
162172
const t2: Test = new Test();
163173
t2.assert(typeof x === "string");
164174
}
175+
176+
// Repro from #35940
177+
178+
interface Thing {
179+
good: boolean;
180+
isGood(): asserts this is GoodThing;
181+
}
182+
183+
interface GoodThing {
184+
good: true;
185+
}
186+
187+
function example1(things: Thing[]) {
188+
for (let thing of things) {
189+
thing.isGood();
190+
thing.good;
191+
}
192+
}
165193

166194

167195
//// [assertionTypePredicates1.js]
@@ -319,6 +347,16 @@ var Derived = /** @class */ (function (_super) {
319347
};
320348
return Derived;
321349
}(Test));
350+
function f11(items) {
351+
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
352+
var item = items_1[_i];
353+
if (item.isTest2()) {
354+
item.z;
355+
}
356+
item.assertIsTest2();
357+
item.z;
358+
}
359+
}
322360
function f20(x) {
323361
var assert = function (value) { };
324362
assert(typeof x === "string"); // Error
@@ -329,6 +367,13 @@ function f20(x) {
329367
var t2 = new Test();
330368
t2.assert(typeof x === "string");
331369
}
370+
function example1(things) {
371+
for (var _i = 0, things_1 = things; _i < things_1.length; _i++) {
372+
var thing = things_1[_i];
373+
thing.isGood();
374+
thing.good;
375+
}
376+
}
332377

333378

334379
//// [assertionTypePredicates1.d.ts]
@@ -362,6 +407,7 @@ declare class Derived extends Test {
362407
foo(x: unknown): void;
363408
baz(x: number): void;
364409
}
410+
declare function f11(items: Test[]): void;
365411
declare let Q1: new (x: unknown) => x is string;
366412
declare let Q2: new (x: boolean) => asserts x;
367413
declare let Q3: new (x: unknown) => asserts x is string;
@@ -372,3 +418,11 @@ declare class Wat {
372418
set p2(x: asserts this is string);
373419
}
374420
declare function f20(x: unknown): void;
421+
interface Thing {
422+
good: boolean;
423+
isGood(): asserts this is GoodThing;
424+
}
425+
interface GoodThing {
426+
good: true;
427+
}
428+
declare function example1(things: Thing[]): void;

0 commit comments

Comments
 (0)