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
Merged
Show file tree
Hide file tree
Changes from 4 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
90 changes: 72 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
arrayToMultiMap,
ArrayTypeNode,
ArrowFunction,
AsExpression,
AssertionExpression,
AssignmentDeclarationKind,
AssignmentKind,
Expand Down Expand Up @@ -762,6 +763,7 @@ import {
JSDocSatisfiesTag,
JSDocSignature,
JSDocTemplateTag,
JSDocTypeAssertion,
JSDocTypedefTag,
JSDocTypeExpression,
JSDocTypeLiteral,
Expand Down Expand Up @@ -1037,7 +1039,6 @@ import {
TypeReferenceSerializationKind,
TypeReferenceType,
TypeVariable,
UnaryExpression,
unescapeLeadingUnderscores,
UnionOrIntersectionType,
UnionOrIntersectionTypeNode,
Expand Down Expand Up @@ -34274,7 +34275,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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, node.type, node.expression);
return checkAssertionWorker(node);
}

function isValidConstAssertionArgument(node: Node): boolean {
Expand Down Expand Up @@ -34305,27 +34307,74 @@ 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) {
// TODO: maybe we don't need this? maybe can be helper?
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;
}

// function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) {
// let 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);
// return getRegularTypeOfLiteralType(exprType);
return getRegularTypeOfLiteralType(checkExpression(expression));
}
checkSourceElement(type);
exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType));
checkNodeDeferred(node); // >> TODO: get node
// exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType));
// const targetType = getTypeFromTypeNode(type);
// if (!isErrorType(targetType)) {
// addLazyDiagnostic(() => { // TODO: defer this check
// const widenedType = getWidenedType(exprType);
// if (!isTypeComparableTo(targetType, widenedType)) {
// checkTypeComparableTo(exprType, targetType, errNode,
// Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first);
// }
// });
// }
// return targetType;
return getTypeFromTypeNode(type);
}

function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) {
let type: TypeNode;
let expression: Expression;
let errNode: Node;
switch (node.kind) {
case SyntaxKind.AsExpression:
case SyntaxKind.TypeAssertionExpression:
type = (node as TypeAssertion | AsExpression).type;
expression = (node as TypeAssertion | AsExpression).expression;
errNode = node;
break;
case SyntaxKind.ParenthesizedExpression:
type = getJSDocTypeAssertionType(node);
expression = node.expression;
errNode = type;
break;
}
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression)));
const targetType = getTypeFromTypeNode(type);
if (!isErrorType(targetType)) {
addLazyDiagnostic(() => {
const widenedType = getWidenedType(exprType);
if (!isTypeComparableTo(targetType, widenedType)) {
checkTypeComparableTo(exprType, targetType, errNode,
Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first);
}
});
const widenedType = getWidenedType(exprType);
if (!isTypeComparableTo(targetType, widenedType)) {
checkTypeComparableTo(exprType, targetType, errNode,
Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first);
}
}
return targetType;
}

function checkNonNullChain(node: NonNullChain) {
Expand Down Expand Up @@ -37554,8 +37603,9 @@ 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);
// const type = getJSDocTypeAssertionType(node);
// return checkAssertionWorker(type, type, node.expression);
return checkAssertionWorker(node);
}
}
return checkExpression(node.expression, checkMode);
Expand Down Expand Up @@ -44678,7 +44728,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Here, performing a full type check of the body of the function expression whilst in the process of
// determining the type of foo would cause foo to be given type any because of the recursive reference.
// Delaying the type check of the body ensures foo has been assigned a type.
function checkNodeDeferred(node: Node) {
function checkNodeDeferred(node: Node) { // >>
const enclosingFile = getSourceFileOfNode(node);
const links = getNodeLinks(enclosingFile);
if (!(links.flags & NodeCheckFlags.TypeChecked)) {
Expand All @@ -44698,7 +44748,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
links.deferredNodes = undefined;
}

function checkDeferredNode(node: Node) {
function checkDeferredNode(node: Node) { // >>
tracing?.push(tracing.Phase.Check, "checkDeferredNode", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath });
const saveCurrentNode = currentNode;
currentNode = node;
Expand Down Expand Up @@ -44736,6 +44786,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

Loading