diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 16e982bfff38b..35b37c23910af 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -762,6 +762,7 @@ import { JSDocSatisfiesTag, JSDocSignature, JSDocTemplateTag, + JSDocTypeAssertion, JSDocTypedefTag, JSDocTypeExpression, JSDocTypeLiteral, @@ -1037,7 +1038,6 @@ import { TypeReferenceSerializationKind, TypeReferenceType, TypeVariable, - UnaryExpression, unescapeLeadingUnderscores, UnionOrIntersectionType, UnionOrIntersectionTypeNode, @@ -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 { @@ -34305,8 +34305,9 @@ 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); @@ -34314,7 +34315,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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(() => { @@ -34325,7 +34351,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } }); } - return targetType; } function checkNonNullChain(node: NonNullChain) { @@ -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); @@ -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: @@ -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(); diff --git a/tests/baselines/reference/classVarianceCircularity.js b/tests/baselines/reference/classVarianceCircularity.js new file mode 100644 index 0000000000000..60b4dd2995aab --- /dev/null +++ b/tests/baselines/reference/classVarianceCircularity.js @@ -0,0 +1,32 @@ +//// [classVarianceCircularity.ts] +// Issue #52813 + +function f() { + const b = new Bar(); + // Uncomment to create error + console.log(b.Value); +} + +class Bar { + num!: number; + // Or swap these two lines + Field: number = (this as Bar).num; + Value = (this as Bar).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; +}()); diff --git a/tests/baselines/reference/classVarianceCircularity.symbols b/tests/baselines/reference/classVarianceCircularity.symbols new file mode 100644 index 0000000000000..c0c3085755311 --- /dev/null +++ b/tests/baselines/reference/classVarianceCircularity.symbols @@ -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 { +>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).num; +>Field : Symbol(Bar.Field, Decl(classVarianceCircularity.ts, 9, 17)) +>(this as Bar).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).num; +>Value : Symbol(Bar.Value, Decl(classVarianceCircularity.ts, 11, 43)) +>(this as Bar).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)) +} diff --git a/tests/baselines/reference/classVarianceCircularity.types b/tests/baselines/reference/classVarianceCircularity.types new file mode 100644 index 0000000000000..664e5ef220490 --- /dev/null +++ b/tests/baselines/reference/classVarianceCircularity.types @@ -0,0 +1,45 @@ +=== tests/cases/compiler/classVarianceCircularity.ts === +// Issue #52813 + +function f() { +>f : () => void + + const b = new Bar(); +>b : Bar +>new Bar() : Bar +>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 +>Value : number +} + +class Bar { +>Bar : Bar + + num!: number; +>num : number + + // Or swap these two lines + Field: number = (this as Bar).num; +>Field : number +>(this as Bar).num : number +>(this as Bar) : Bar +>this as Bar : Bar +>this : this +>num : number + + Value = (this as Bar).num; +>Value : number +>(this as Bar).num : number +>(this as Bar) : Bar +>this as Bar : Bar +>this : this +>num : number +} diff --git a/tests/baselines/reference/classVarianceResolveCircularity.errors.txt b/tests/baselines/reference/classVarianceResolveCircularity.errors.txt new file mode 100644 index 0000000000000..faff1bf64260a --- /dev/null +++ b/tests/baselines/reference/classVarianceResolveCircularity.errors.txt @@ -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 { + 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): Bar; + declare function callme(x: object): string; \ No newline at end of file diff --git a/tests/baselines/reference/classVarianceResolveCircularity.js b/tests/baselines/reference/classVarianceResolveCircularity.js new file mode 100644 index 0000000000000..2518fa84c7bdc --- /dev/null +++ b/tests/baselines/reference/classVarianceResolveCircularity.js @@ -0,0 +1,21 @@ +//// [classVarianceResolveCircularity.ts] +// Issue #52813 + +class Bar { + num!: number; // Swap to remove error + Value = callme(this).num; + Field: number = callme(this).num; +} +declare function callme(x: Bar): Bar; +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; +}()); diff --git a/tests/baselines/reference/classVarianceResolveCircularity.symbols b/tests/baselines/reference/classVarianceResolveCircularity.symbols new file mode 100644 index 0000000000000..a3bf2bc878ff6 --- /dev/null +++ b/tests/baselines/reference/classVarianceResolveCircularity.symbols @@ -0,0 +1,34 @@ +=== tests/cases/compiler/classVarianceResolveCircularity.ts === +// Issue #52813 + +class Bar { +>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): Bar; +>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)) + diff --git a/tests/baselines/reference/classVarianceResolveCircularity.types b/tests/baselines/reference/classVarianceResolveCircularity.types new file mode 100644 index 0000000000000..d7e48dffe67df --- /dev/null +++ b/tests/baselines/reference/classVarianceResolveCircularity.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/classVarianceResolveCircularity.ts === +// Issue #52813 + +class Bar { +>Bar : Bar + + num!: number; // Swap to remove error +>num : number + + Value = callme(this).num; +>Value : any +>callme(this).num : number +>callme(this) : Bar +>callme : { (x: Bar): Bar; (x: object): string; } +>this : this +>num : number + + Field: number = callme(this).num; +>Field : number +>callme(this).num : number +>callme(this) : Bar +>callme : { (x: Bar): Bar; (x: object): string; } +>this : this +>num : number +} +declare function callme(x: Bar): Bar; +>callme : { (x: Bar): Bar; (x: object): string; } +>x : Bar + +declare function callme(x: object): string; +>callme : { (x: Bar): Bar; (x: object): string; } +>x : object + diff --git a/tests/cases/compiler/classVarianceCircularity.ts b/tests/cases/compiler/classVarianceCircularity.ts new file mode 100644 index 0000000000000..3c92600d4197d --- /dev/null +++ b/tests/cases/compiler/classVarianceCircularity.ts @@ -0,0 +1,16 @@ +// @strict: true + +// Issue #52813 + +function f() { + const b = new Bar(); + // Uncomment to create error + console.log(b.Value); +} + +class Bar { + num!: number; + // Or swap these two lines + Field: number = (this as Bar).num; + Value = (this as Bar).num; +} \ No newline at end of file diff --git a/tests/cases/compiler/classVarianceResolveCircularity.ts b/tests/cases/compiler/classVarianceResolveCircularity.ts new file mode 100644 index 0000000000000..abff110eea799 --- /dev/null +++ b/tests/cases/compiler/classVarianceResolveCircularity.ts @@ -0,0 +1,11 @@ +// @strict: true + +// Issue #52813 + +class Bar { + num!: number; // Swap to remove error + Value = callme(this).num; + Field: number = callme(this).num; +} +declare function callme(x: Bar): Bar; +declare function callme(x: object): string; \ No newline at end of file