Skip to content

Defer type comparability check for assertions #53261

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

Merged
merged 12 commits into from
Mar 23, 2023
48 changes: 38 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,7 @@ import {
JSDocSatisfiesTag,
JSDocSignature,
JSDocTemplateTag,
JSDocTypeAssertion,
JSDocTypedefTag,
JSDocTypeExpression,
JSDocTypeLiteral,
Expand Down Expand Up @@ -1037,7 +1038,6 @@ import {
TypeReferenceSerializationKind,
TypeReferenceType,
TypeVariable,
UnaryExpression,
unescapeLeadingUnderscores,
UnionOrIntersectionType,
UnionOrIntersectionTypeNode,
Expand Down Expand Up @@ -34267,14 +34267,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getReturnTypeOfSignature(signature);
}

function checkAssertion(node: AssertionExpression) {
function checkAssertion(node: AssertionExpression, checkMode: CheckMode | undefined) {
if (node.kind === SyntaxKind.TypeAssertionExpression) {
const file = getSourceFileOfNode(node);
if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) {
grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead);
}
}
return checkAssertionWorker(node, node.type, node.expression);
return checkAssertionWorker(node, checkMode);
}

function isValidConstAssertionArgument(node: Node): boolean {
Expand Down Expand Up @@ -34305,16 +34305,42 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return false;
}

function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) {
let exprType = checkExpression(expression, checkMode);
function checkAssertionWorker(node: JSDocTypeAssertion | AssertionExpression, checkMode: CheckMode | undefined) {
const { type, expression } = getAssertionTypeAndExpression(node);
const exprType = checkExpression(expression, checkMode);
if (isConstTypeReference(type)) {
if (!isValidConstAssertionArgument(expression)) {
error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals);
}
return getRegularTypeOfLiteralType(exprType);
}
checkSourceElement(type);
exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType));
checkNodeDeferred(node);
return getTypeFromTypeNode(type);
}

function getAssertionTypeAndExpression(node: JSDocTypeAssertion | AssertionExpression) {
let type: TypeNode;
let expression: Expression;
switch (node.kind) {
case SyntaxKind.AsExpression:
case SyntaxKind.TypeAssertionExpression:
type = node.type;
expression = node.expression;
break;
case SyntaxKind.ParenthesizedExpression:
type = getJSDocTypeAssertionType(node);
expression = node.expression;
break;
}

return { type, expression };
}

function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) {
const { type, expression } = getAssertionTypeAndExpression(node);
const errNode = isParenthesizedExpression(node) ? type : node;
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression)));
const targetType = getTypeFromTypeNode(type);
if (!isErrorType(targetType)) {
addLazyDiagnostic(() => {
Expand All @@ -34325,7 +34351,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
});
}
return targetType;
}

function checkNonNullChain(node: NonNullChain) {
Expand Down Expand Up @@ -37554,8 +37579,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode);
}
if (isJSDocTypeAssertion(node)) {
const type = getJSDocTypeAssertionType(node);
return checkAssertionWorker(type, type, node.expression, checkMode);
return checkAssertionWorker(node, checkMode);
}
}
return checkExpression(node.expression, checkMode);
Expand Down Expand Up @@ -37636,7 +37660,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return checkTypeOfExpression(node as TypeOfExpression);
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
return checkAssertion(node as AssertionExpression);
return checkAssertion(node as AssertionExpression, checkMode);
case SyntaxKind.NonNullExpression:
return checkNonNullAssertion(node as NonNullExpression);
case SyntaxKind.ExpressionWithTypeArguments:
Expand Down Expand Up @@ -44736,6 +44760,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.JsxElement:
checkJsxElementDeferred(node as JsxElement);
break;
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
case SyntaxKind.ParenthesizedExpression:
checkAssertionDeferred(node as AssertionExpression | JSDocTypeAssertion);
}
currentNode = saveCurrentNode;
tracing?.pop();
Expand Down
32 changes: 32 additions & 0 deletions tests/baselines/reference/classVarianceCircularity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//// [classVarianceCircularity.ts]
// Issue #52813

function f() {
const b = new Bar();
// Uncomment to create error
console.log(b.Value);
}

class Bar<T> {
num!: number;
// Or swap these two lines
Field: number = (this as Bar<any>).num;
Value = (this as Bar<any>).num;
}

//// [classVarianceCircularity.js]
"use strict";
// Issue #52813
function f() {
var b = new Bar();
// Uncomment to create error
console.log(b.Value);
}
var Bar = /** @class */ (function () {
function Bar() {
// Or swap these two lines
this.Field = this.num;
this.Value = this.num;
}
return Bar;
}());
42 changes: 42 additions & 0 deletions tests/baselines/reference/classVarianceCircularity.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
=== tests/cases/compiler/classVarianceCircularity.ts ===
// Issue #52813

function f() {
>f : Symbol(f, Decl(classVarianceCircularity.ts, 0, 0))

const b = new Bar();
>b : Symbol(b, Decl(classVarianceCircularity.ts, 3, 9))
>Bar : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))

// Uncomment to create error
console.log(b.Value);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>b.Value : Symbol(Bar.Value, Decl(classVarianceCircularity.ts, 11, 43))
>b : Symbol(b, Decl(classVarianceCircularity.ts, 3, 9))
>Value : Symbol(Bar.Value, Decl(classVarianceCircularity.ts, 11, 43))
}

class Bar<T> {
>Bar : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
>T : Symbol(T, Decl(classVarianceCircularity.ts, 8, 10))

num!: number;
>num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))

// Or swap these two lines
Field: number = (this as Bar<any>).num;
>Field : Symbol(Bar.Field, Decl(classVarianceCircularity.ts, 9, 17))
>(this as Bar<any>).num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))
>this : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
>Bar : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
>num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))

Value = (this as Bar<any>).num;
>Value : Symbol(Bar.Value, Decl(classVarianceCircularity.ts, 11, 43))
>(this as Bar<any>).num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))
>this : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
>Bar : Symbol(Bar, Decl(classVarianceCircularity.ts, 6, 1))
>num : Symbol(Bar.num, Decl(classVarianceCircularity.ts, 8, 14))
}
45 changes: 45 additions & 0 deletions tests/baselines/reference/classVarianceCircularity.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
=== tests/cases/compiler/classVarianceCircularity.ts ===
// Issue #52813

function f() {
>f : () => void

const b = new Bar();
>b : Bar<unknown>
>new Bar() : Bar<unknown>
>Bar : typeof Bar

// Uncomment to create error
console.log(b.Value);
>console.log(b.Value) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>b.Value : number
>b : Bar<unknown>
>Value : number
}

class Bar<T> {
>Bar : Bar<T>

num!: number;
>num : number

// Or swap these two lines
Field: number = (this as Bar<any>).num;
>Field : number
>(this as Bar<any>).num : number
>(this as Bar<any>) : Bar<any>
>this as Bar<any> : Bar<any>
>this : this
>num : number

Value = (this as Bar<any>).num;
>Value : number
>(this as Bar<any>).num : number
>(this as Bar<any>) : Bar<any>
>this as Bar<any> : Bar<any>
>this : this
>num : number
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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.


==== tests/cases/compiler/classVarianceResolveCircularity.ts (1 errors) ====
// Issue #52813

class Bar<T> {
num!: number; // Swap to remove error
Value = callme(this).num;
~~~~~
!!! 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.
Field: number = callme(this).num;
}
declare function callme(x: Bar<any>): Bar<any>;
declare function callme(x: object): string;
21 changes: 21 additions & 0 deletions tests/baselines/reference/classVarianceResolveCircularity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//// [classVarianceResolveCircularity.ts]
// Issue #52813

class Bar<T> {
num!: number; // Swap to remove error
Value = callme(this).num;
Field: number = callme(this).num;
}
declare function callme(x: Bar<any>): Bar<any>;
declare function callme(x: object): string;

//// [classVarianceResolveCircularity.js]
"use strict";
// Issue #52813
var Bar = /** @class */ (function () {
function Bar() {
this.Value = callme(this).num;
this.Field = callme(this).num;
}
return Bar;
}());
34 changes: 34 additions & 0 deletions tests/baselines/reference/classVarianceResolveCircularity.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
=== tests/cases/compiler/classVarianceResolveCircularity.ts ===
// Issue #52813

class Bar<T> {
>Bar : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
>T : Symbol(T, Decl(classVarianceResolveCircularity.ts, 2, 10))

num!: number; // Swap to remove error
>num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))

Value = callme(this).num;
>Value : Symbol(Bar.Value, Decl(classVarianceResolveCircularity.ts, 3, 17))
>callme(this).num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))
>callme : Symbol(callme, Decl(classVarianceResolveCircularity.ts, 6, 1), Decl(classVarianceResolveCircularity.ts, 7, 47))
>this : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
>num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))

Field: number = callme(this).num;
>Field : Symbol(Bar.Field, Decl(classVarianceResolveCircularity.ts, 4, 29))
>callme(this).num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))
>callme : Symbol(callme, Decl(classVarianceResolveCircularity.ts, 6, 1), Decl(classVarianceResolveCircularity.ts, 7, 47))
>this : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
>num : Symbol(Bar.num, Decl(classVarianceResolveCircularity.ts, 2, 14))
}
declare function callme(x: Bar<any>): Bar<any>;
>callme : Symbol(callme, Decl(classVarianceResolveCircularity.ts, 6, 1), Decl(classVarianceResolveCircularity.ts, 7, 47))
>x : Symbol(x, Decl(classVarianceResolveCircularity.ts, 7, 24))
>Bar : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))
>Bar : Symbol(Bar, Decl(classVarianceResolveCircularity.ts, 0, 0))

declare function callme(x: object): string;
>callme : Symbol(callme, Decl(classVarianceResolveCircularity.ts, 6, 1), Decl(classVarianceResolveCircularity.ts, 7, 47))
>x : Symbol(x, Decl(classVarianceResolveCircularity.ts, 8, 24))

33 changes: 33 additions & 0 deletions tests/baselines/reference/classVarianceResolveCircularity.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
=== tests/cases/compiler/classVarianceResolveCircularity.ts ===
// Issue #52813

class Bar<T> {
>Bar : Bar<T>

num!: number; // Swap to remove error
>num : number

Value = callme(this).num;
>Value : any
>callme(this).num : number
>callme(this) : Bar<any>
>callme : { (x: Bar<any>): Bar<any>; (x: object): string; }
>this : this
>num : number

Field: number = callme(this).num;
>Field : number
>callme(this).num : number
>callme(this) : Bar<any>
>callme : { (x: Bar<any>): Bar<any>; (x: object): string; }
>this : this
>num : number
}
declare function callme(x: Bar<any>): Bar<any>;
>callme : { (x: Bar<any>): Bar<any>; (x: object): string; }
>x : Bar<any>

declare function callme(x: object): string;
>callme : { (x: Bar<any>): Bar<any>; (x: object): string; }
>x : object

16 changes: 16 additions & 0 deletions tests/cases/compiler/classVarianceCircularity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @strict: true

// Issue #52813
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example where this PR's approach doesn't solve the circularity problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you mean the other test with the function calls rather than expressions, since that's the one that still has an error. :) If you want, you should file an issue about the other form, so the issue can still get backlog'd, if you want to consider looking into a broader fix in the future after this PR marks the original issue as fixed.


function f() {
const b = new Bar();
// Uncomment to create error
console.log(b.Value);
}

class Bar<T> {
num!: number;
// Or swap these two lines
Field: number = (this as Bar<any>).num;
Value = (this as Bar<any>).num;
}
11 changes: 11 additions & 0 deletions tests/cases/compiler/classVarianceResolveCircularity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @strict: true

// Issue #52813

class Bar<T> {
num!: number; // Swap to remove error
Value = callme(this).num;
Field: number = callme(this).num;
}
declare function callme(x: Bar<any>): Bar<any>;
declare function callme(x: object): string;