Skip to content

Commit 8947825

Browse files
authored
Defer type comparability check for assertions (#53261)
1 parent d5fd34b commit 8947825

10 files changed

+287
-10
lines changed

src/compiler/checker.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ import {
763763
JSDocSatisfiesTag,
764764
JSDocSignature,
765765
JSDocTemplateTag,
766+
JSDocTypeAssertion,
766767
JSDocTypedefTag,
767768
JSDocTypeExpression,
768769
JSDocTypeLiteral,
@@ -1039,7 +1040,6 @@ import {
10391040
TypeReferenceSerializationKind,
10401041
TypeReferenceType,
10411042
TypeVariable,
1042-
UnaryExpression,
10431043
unescapeLeadingUnderscores,
10441044
UnionOrIntersectionType,
10451045
UnionOrIntersectionTypeNode,
@@ -34358,14 +34358,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3435834358
return getReturnTypeOfSignature(signature);
3435934359
}
3436034360

34361-
function checkAssertion(node: AssertionExpression) {
34361+
function checkAssertion(node: AssertionExpression, checkMode: CheckMode | undefined) {
3436234362
if (node.kind === SyntaxKind.TypeAssertionExpression) {
3436334363
const file = getSourceFileOfNode(node);
3436434364
if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) {
3436534365
grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead);
3436634366
}
3436734367
}
34368-
return checkAssertionWorker(node, node.type, node.expression);
34368+
return checkAssertionWorker(node, checkMode);
3436934369
}
3437034370

3437134371
function isValidConstAssertionArgument(node: Node): boolean {
@@ -34396,16 +34396,42 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3439634396
return false;
3439734397
}
3439834398

34399-
function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) {
34400-
let exprType = checkExpression(expression, checkMode);
34399+
function checkAssertionWorker(node: JSDocTypeAssertion | AssertionExpression, checkMode: CheckMode | undefined) {
34400+
const { type, expression } = getAssertionTypeAndExpression(node);
34401+
const exprType = checkExpression(expression, checkMode);
3440134402
if (isConstTypeReference(type)) {
3440234403
if (!isValidConstAssertionArgument(expression)) {
3440334404
error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals);
3440434405
}
3440534406
return getRegularTypeOfLiteralType(exprType);
3440634407
}
3440734408
checkSourceElement(type);
34408-
exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType));
34409+
checkNodeDeferred(node);
34410+
return getTypeFromTypeNode(type);
34411+
}
34412+
34413+
function getAssertionTypeAndExpression(node: JSDocTypeAssertion | AssertionExpression) {
34414+
let type: TypeNode;
34415+
let expression: Expression;
34416+
switch (node.kind) {
34417+
case SyntaxKind.AsExpression:
34418+
case SyntaxKind.TypeAssertionExpression:
34419+
type = node.type;
34420+
expression = node.expression;
34421+
break;
34422+
case SyntaxKind.ParenthesizedExpression:
34423+
type = getJSDocTypeAssertionType(node);
34424+
expression = node.expression;
34425+
break;
34426+
}
34427+
34428+
return { type, expression };
34429+
}
34430+
34431+
function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) {
34432+
const { type, expression } = getAssertionTypeAndExpression(node);
34433+
const errNode = isParenthesizedExpression(node) ? type : node;
34434+
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression)));
3440934435
const targetType = getTypeFromTypeNode(type);
3441034436
if (!isErrorType(targetType)) {
3441134437
addLazyDiagnostic(() => {
@@ -34416,7 +34442,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3441634442
}
3441734443
});
3441834444
}
34419-
return targetType;
3442034445
}
3442134446

3442234447
function checkNonNullChain(node: NonNullChain) {
@@ -37662,8 +37687,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3766237687
return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode);
3766337688
}
3766437689
if (isJSDocTypeAssertion(node)) {
37665-
const type = getJSDocTypeAssertionType(node);
37666-
return checkAssertionWorker(type, type, node.expression, checkMode);
37690+
return checkAssertionWorker(node, checkMode);
3766737691
}
3766837692
}
3766937693
return checkExpression(node.expression, checkMode);
@@ -37744,7 +37768,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3774437768
return checkTypeOfExpression(node as TypeOfExpression);
3774537769
case SyntaxKind.TypeAssertionExpression:
3774637770
case SyntaxKind.AsExpression:
37747-
return checkAssertion(node as AssertionExpression);
37771+
return checkAssertion(node as AssertionExpression, checkMode);
3774837772
case SyntaxKind.NonNullExpression:
3774937773
return checkNonNullAssertion(node as NonNullExpression);
3775037774
case SyntaxKind.ExpressionWithTypeArguments:
@@ -44848,6 +44872,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4484844872
case SyntaxKind.JsxElement:
4484944873
checkJsxElementDeferred(node as JsxElement);
4485044874
break;
44875+
case SyntaxKind.TypeAssertionExpression:
44876+
case SyntaxKind.AsExpression:
44877+
case SyntaxKind.ParenthesizedExpression:
44878+
checkAssertionDeferred(node as AssertionExpression | JSDocTypeAssertion);
4485144879
}
4485244880
currentNode = saveCurrentNode;
4485344881
tracing?.pop();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [classVarianceCircularity.ts]
2+
// Issue #52813
3+
4+
function f() {
5+
const b = new Bar();
6+
// Uncomment to create error
7+
console.log(b.Value);
8+
}
9+
10+
class Bar<T> {
11+
num!: number;
12+
// Or swap these two lines
13+
Field: number = (this as Bar<any>).num;
14+
Value = (this as Bar<any>).num;
15+
}
16+
17+
//// [classVarianceCircularity.js]
18+
"use strict";
19+
// Issue #52813
20+
function f() {
21+
var b = new Bar();
22+
// Uncomment to create error
23+
console.log(b.Value);
24+
}
25+
var Bar = /** @class */ (function () {
26+
function Bar() {
27+
// Or swap these two lines
28+
this.Field = this.num;
29+
this.Value = this.num;
30+
}
31+
return Bar;
32+
}());
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/classVarianceCircularity.ts ===
2+
// Issue #52813
3+
4+
function f() {
5+
>f : Symbol(f, Decl(classVarianceCircularity.ts, 0, 0))
6+
7+
const b = new Bar();
8+
>b : Symbol(b, Decl(classVarianceCircularity.ts, 3, 9))
9+
>Bar : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
10+
11+
// Uncomment to create error
12+
console.log(b.Value);
13+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
14+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
15+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
16+
>b.Value : Symbol(Bar.Value, Decl(classVarianceCircularity.ts, 11, 43))
17+
>b : Symbol(b, Decl(classVarianceCircularity.ts, 3, 9))
18+
>Value : Symbol(Bar.Value, Decl(classVarianceCircularity.ts, 11, 43))
19+
}
20+
21+
class Bar<T> {
22+
>Bar : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
23+
>T : Symbol(T, Decl(classVarianceCircularity.ts, 8, 10))
24+
25+
num!: number;
26+
>num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))
27+
28+
// Or swap these two lines
29+
Field: number = (this as Bar<any>).num;
30+
>Field : Symbol(Bar.Field, Decl(classVarianceCircularity.ts, 9, 17))
31+
>(this as Bar<any>).num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))
32+
>this : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
33+
>Bar : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
34+
>num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))
35+
36+
Value = (this as Bar<any>).num;
37+
>Value : Symbol(Bar.Value, Decl(classVarianceCircularity.ts, 11, 43))
38+
>(this as Bar<any>).num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))
39+
>this : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
40+
>Bar : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
41+
>num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))
42+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
=== tests/cases/compiler/classVarianceCircularity.ts ===
2+
// Issue #52813
3+
4+
function f() {
5+
>f : () => void
6+
7+
const b = new Bar();
8+
>b : Bar<unknown>
9+
>new Bar() : Bar<unknown>
10+
>Bar : typeof Bar
11+
12+
// Uncomment to create error
13+
console.log(b.Value);
14+
>console.log(b.Value) : void
15+
>console.log : (...data: any[]) => void
16+
>console : Console
17+
>log : (...data: any[]) => void
18+
>b.Value : number
19+
>b : Bar<unknown>
20+
>Value : number
21+
}
22+
23+
class Bar<T> {
24+
>Bar : Bar<T>
25+
26+
num!: number;
27+
>num : number
28+
29+
// Or swap these two lines
30+
Field: number = (this as Bar<any>).num;
31+
>Field : number
32+
>(this as Bar<any>).num : number
33+
>(this as Bar<any>) : Bar<any>
34+
>this as Bar<any> : Bar<any>
35+
>this : this
36+
>num : number
37+
38+
Value = (this as Bar<any>).num;
39+
>Value : number
40+
>(this as Bar<any>).num : number
41+
>(this as Bar<any>) : Bar<any>
42+
>this as Bar<any> : Bar<any>
43+
>this : this
44+
>num : number
45+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
tests/cases/compiler/classVarianceResolveCircularity.ts(5,5): error TS7022: 'Value' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
2+
3+
4+
==== tests/cases/compiler/classVarianceResolveCircularity.ts (1 errors) ====
5+
// Issue #52813
6+
7+
class Bar<T> {
8+
num!: number; // Swap to remove error
9+
Value = callme(this).num;
10+
~~~~~
11+
!!! error TS7022: 'Value' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
12+
Field: number = callme(this).num;
13+
}
14+
declare function callme(x: Bar<any>): Bar<any>;
15+
declare function callme(x: object): string;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [classVarianceResolveCircularity.ts]
2+
// Issue #52813
3+
4+
class Bar<T> {
5+
num!: number; // Swap to remove error
6+
Value = callme(this).num;
7+
Field: number = callme(this).num;
8+
}
9+
declare function callme(x: Bar<any>): Bar<any>;
10+
declare function callme(x: object): string;
11+
12+
//// [classVarianceResolveCircularity.js]
13+
"use strict";
14+
// Issue #52813
15+
var Bar = /** @class */ (function () {
16+
function Bar() {
17+
this.Value = callme(this).num;
18+
this.Field = callme(this).num;
19+
}
20+
return Bar;
21+
}());
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/compiler/classVarianceResolveCircularity.ts ===
2+
// Issue #52813
3+
4+
class Bar<T> {
5+
>Bar : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
6+
>T : Symbol(T, Decl(classVarianceResolveCircularity.ts, 2, 10))
7+
8+
num!: number; // Swap to remove error
9+
>num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))
10+
11+
Value = callme(this).num;
12+
>Value : Symbol(Bar.Value, Decl(classVarianceResolveCircularity.ts, 3, 17))
13+
>callme(this).num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))
14+
>callme : Symbol(callme, Decl(classVarianceResolveCircularity.ts, 6, 1), Decl(classVarianceResolveCircularity.ts, 7, 47))
15+
>this : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
16+
>num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))
17+
18+
Field: number = callme(this).num;
19+
>Field : Symbol(Bar.Field, Decl(classVarianceResolveCircularity.ts, 4, 29))
20+
>callme(this).num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))
21+
>callme : Symbol(callme, Decl(classVarianceResolveCircularity.ts, 6, 1), Decl(classVarianceResolveCircularity.ts, 7, 47))
22+
>this : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
23+
>num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))
24+
}
25+
declare function callme(x: Bar<any>): Bar<any>;
26+
>callme : Symbol(callme, Decl(classVarianceResolveCircularity.ts, 6, 1), Decl(classVarianceResolveCircularity.ts, 7, 47))
27+
>x : Symbol(x, Decl(classVarianceResolveCircularity.ts, 7, 24))
28+
>Bar : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
29+
>Bar : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
30+
31+
declare function callme(x: object): string;
32+
>callme : Symbol(callme, Decl(classVarianceResolveCircularity.ts, 6, 1), Decl(classVarianceResolveCircularity.ts, 7, 47))
33+
>x : Symbol(x, Decl(classVarianceResolveCircularity.ts, 8, 24))
34+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/compiler/classVarianceResolveCircularity.ts ===
2+
// Issue #52813
3+
4+
class Bar<T> {
5+
>Bar : Bar<T>
6+
7+
num!: number; // Swap to remove error
8+
>num : number
9+
10+
Value = callme(this).num;
11+
>Value : any
12+
>callme(this).num : number
13+
>callme(this) : Bar<any>
14+
>callme : { (x: Bar<any>): Bar<any>; (x: object): string; }
15+
>this : this
16+
>num : number
17+
18+
Field: number = callme(this).num;
19+
>Field : number
20+
>callme(this).num : number
21+
>callme(this) : Bar<any>
22+
>callme : { (x: Bar<any>): Bar<any>; (x: object): string; }
23+
>this : this
24+
>num : number
25+
}
26+
declare function callme(x: Bar<any>): Bar<any>;
27+
>callme : { (x: Bar<any>): Bar<any>; (x: object): string; }
28+
>x : Bar<any>
29+
30+
declare function callme(x: object): string;
31+
>callme : { (x: Bar<any>): Bar<any>; (x: object): string; }
32+
>x : object
33+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @strict: true
2+
3+
// Issue #52813
4+
5+
function f() {
6+
const b = new Bar();
7+
// Uncomment to create error
8+
console.log(b.Value);
9+
}
10+
11+
class Bar<T> {
12+
num!: number;
13+
// Or swap these two lines
14+
Field: number = (this as Bar<any>).num;
15+
Value = (this as Bar<any>).num;
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @strict: true
2+
3+
// Issue #52813
4+
5+
class Bar<T> {
6+
num!: number; // Swap to remove error
7+
Value = callme(this).num;
8+
Field: number = callme(this).num;
9+
}
10+
declare function callme(x: Bar<any>): Bar<any>;
11+
declare function callme(x: object): string;

0 commit comments

Comments
 (0)