From f8dd2db9c9cb99022eff5c44eaab108a821b1604 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:30:26 +0000 Subject: [PATCH 1/3] Initial plan From e491322e52865943e561e241cc9570d6958027ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:13:21 +0000 Subject: [PATCH 2/3] Initial plan and debug investigation Agent-Logs-Url: https://github.com/microsoft/TypeScript/sessions/c865d3f5-aaf0-4dfc-8051-47e4fe1b3e86 Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0567712f11da3..921e5f23ee18e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11940,7 +11940,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return containerObjectType; } } - const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); + const initType = checkDeclarationInitializer(declaration, checkMode); + const type = widenTypeInferredFromInitializer(declaration, initType); + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if (isPropertyDeclaration(declaration) && declaration.name && isIdentifier(declaration.name) && declaration.name.text === "D") { console.log("getTypeForVariableLikeDeclaration D: initType=", typeToString(initType), "widenedType=", typeToString(type), "checkMode=", checkMode); } return addOptionality(type, isProperty, isOptional); } @@ -12449,7 +12452,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type { - return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors); + const innerType = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal); + const result = widenTypeForVariableLikeDeclaration(innerType, declaration, reportErrors); + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if (isPropertyDeclaration(declaration)) { console.log("getWidenedTypeForVariableLikeDeclaration PropertyDecl: declName=", isIdentifier(declaration.name) ? declaration.name.text : "?", "innerType=", innerType ? typeToString(innerType) : "undefined", "result=", typeToString(result)); } + return result; } function getTypeFromImportAttributes(node: ImportAttributes): Type { @@ -12546,8 +12553,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!links.type && !isParameterOfContextSensitiveSignature(symbol)) { links.type = type; } + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if ((symbol.escapedName as string) === "D" && symbol.valueDeclaration && isPropertyDeclaration(symbol.valueDeclaration)) { console.log("D getTypeOfVar (computed):", typeToString(type), "links.type:", links.type ? typeToString(links.type) : "none", new Error().stack?.split('\n').slice(1,6).join(' | ')); } return type; } + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if ((symbol.escapedName as string) === "D" && symbol.valueDeclaration && isPropertyDeclaration(symbol.valueDeclaration)) { console.log("D getTypeOfVar (cached):", typeToString(links.type), new Error().stack?.split('\n').slice(1,6).join(' | ')); } return links.type; } @@ -12648,6 +12659,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { || isJSDocPropertyLikeTag(declaration) ) { type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if (isPropertyDeclaration(declaration) && declaration.name && (declaration.name as any).text === "D") { console.log("D property type =", typeToString(type)); } } // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. // Re-dispatch based on valueDeclaration.kind instead. @@ -12964,6 +12977,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if ((symbol.escapedName as string) === "D" && symbol.valueDeclaration && isPropertyDeclaration(symbol.valueDeclaration)) { const t = getTypeOfVariableOrParameterOrProperty(symbol); console.log("getTypeOfSymbol D (Variable|Property):", typeToString(t), "checkFlags:", checkFlags); return t; } return getTypeOfVariableOrParameterOrProperty(symbol); } if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { @@ -34998,6 +35013,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writeOnly || isWriteOnlyAccess(node) ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if (isPropertyDeclaration(prop?.valueDeclaration) && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword) { console.log("checkPropAccess: prop=", symbolToString(prop), "propType=", typeToString(propType), "checkFlags=", getCheckFlags(prop)); } } return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode); @@ -35047,6 +35064,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getFlowTypeOfProperty(node, prop); } propType = getNarrowableTypeForReference(propType, node, checkMode); + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if (prop && (prop.name as string) === "D") { console.log("getFlowTypeOfAccessExpression: propType =", typeToString(propType)); } // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, // and if we are in a constructor of the same class as the property declaration, assume that @@ -35072,6 +35091,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { assumeUninitialized = true; } const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if (prop && (prop.name as string) === "D") { console.log("getFlowTypeOfAccessExpression: flowType =", typeToString(flowType)); } if (assumeUninitialized && !containsUndefinedType(propType) && containsUndefinedType(flowType)) { error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 // Return the declared type to reduce follow-on errors @@ -41374,6 +41395,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const type = getQuickTypeOfExpression(initializer) || (contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) : checkExpressionCached(initializer, checkMode)); + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + if (isPropertyDeclaration(declaration)) { console.log("checkDeclarationInitializer PropertyDecl: checkMode=", checkMode, "initKind=", initializer.kind, "type=", typeToString(type)); } if (isParameter(isBindingElement(declaration) ? walkUpBindingElementsAndPatterns(declaration) : declaration)) { if (declaration.name.kind === SyntaxKind.ObjectBindingPattern && isObjectLiteralType(type)) { return padObjectLiteralType(type as ObjectType, declaration.name); @@ -46630,6 +46653,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let hasDuplicateDefaultClause = false; const expressionType = checkExpression(node.expression); + // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE + console.log("checkSwitchStatement: expressionType =", typeToString(expressionType), "nodeExprKind=", node.expression.kind, "isPAE=", isPropertyAccessExpression(node.expression)); forEach(node.caseBlock.clauses, clause => { // Grammar check for duplicate default clauses, skip if we already report duplicate default clause From f7b579ad876c4c63770200260da2939e238ea902 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:22:51 +0000 Subject: [PATCH 3/3] Fix regression: restore PropertyDeclaration as flow container, add test Agent-Logs-Url: https://github.com/microsoft/TypeScript/sessions/c865d3f5-aaf0-4dfc-8051-47e4fe1b3e86 Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/binder.ts | 3 + src/compiler/checker.ts | 29 +---- ...ssPropertyInferenceFromBroaderTypeConst.js | 55 +++++++++ ...pertyInferenceFromBroaderTypeConst.symbols | 68 +++++++++++ ...ropertyInferenceFromBroaderTypeConst.types | 107 ++++++++++++++++++ ...ssPropertyInferenceFromBroaderTypeConst.ts | 31 +++++ 6 files changed, 266 insertions(+), 27 deletions(-) create mode 100644 tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.js create mode 100644 tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.symbols create mode 100644 tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.types create mode 100644 tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9ea90805fb51e..5980b113e7baf 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3869,6 +3869,9 @@ export function getContainerFlags(node: Node): ContainerFlags { case SyntaxKind.ModuleBlock: return ContainerFlags.IsControlFlowContainer; + case SyntaxKind.PropertyDeclaration: + return (node as PropertyDeclaration).initializer ? ContainerFlags.IsControlFlowContainer : ContainerFlags.None; + case SyntaxKind.CatchClause: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 921e5f23ee18e..0567712f11da3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11940,10 +11940,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return containerObjectType; } } - const initType = checkDeclarationInitializer(declaration, checkMode); - const type = widenTypeInferredFromInitializer(declaration, initType); - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if (isPropertyDeclaration(declaration) && declaration.name && isIdentifier(declaration.name) && declaration.name.text === "D") { console.log("getTypeForVariableLikeDeclaration D: initType=", typeToString(initType), "widenedType=", typeToString(type), "checkMode=", checkMode); } + const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); return addOptionality(type, isProperty, isOptional); } @@ -12452,11 +12449,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type { - const innerType = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal); - const result = widenTypeForVariableLikeDeclaration(innerType, declaration, reportErrors); - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if (isPropertyDeclaration(declaration)) { console.log("getWidenedTypeForVariableLikeDeclaration PropertyDecl: declName=", isIdentifier(declaration.name) ? declaration.name.text : "?", "innerType=", innerType ? typeToString(innerType) : "undefined", "result=", typeToString(result)); } - return result; + return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors); } function getTypeFromImportAttributes(node: ImportAttributes): Type { @@ -12553,12 +12546,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!links.type && !isParameterOfContextSensitiveSignature(symbol)) { links.type = type; } - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if ((symbol.escapedName as string) === "D" && symbol.valueDeclaration && isPropertyDeclaration(symbol.valueDeclaration)) { console.log("D getTypeOfVar (computed):", typeToString(type), "links.type:", links.type ? typeToString(links.type) : "none", new Error().stack?.split('\n').slice(1,6).join(' | ')); } return type; } - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if ((symbol.escapedName as string) === "D" && symbol.valueDeclaration && isPropertyDeclaration(symbol.valueDeclaration)) { console.log("D getTypeOfVar (cached):", typeToString(links.type), new Error().stack?.split('\n').slice(1,6).join(' | ')); } return links.type; } @@ -12659,8 +12648,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { || isJSDocPropertyLikeTag(declaration) ) { type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if (isPropertyDeclaration(declaration) && declaration.name && (declaration.name as any).text === "D") { console.log("D property type =", typeToString(type)); } } // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. // Re-dispatch based on valueDeclaration.kind instead. @@ -12977,8 +12964,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if ((symbol.escapedName as string) === "D" && symbol.valueDeclaration && isPropertyDeclaration(symbol.valueDeclaration)) { const t = getTypeOfVariableOrParameterOrProperty(symbol); console.log("getTypeOfSymbol D (Variable|Property):", typeToString(t), "checkFlags:", checkFlags); return t; } return getTypeOfVariableOrParameterOrProperty(symbol); } if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { @@ -35013,8 +34998,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writeOnly || isWriteOnlyAccess(node) ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if (isPropertyDeclaration(prop?.valueDeclaration) && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword) { console.log("checkPropAccess: prop=", symbolToString(prop), "propType=", typeToString(propType), "checkFlags=", getCheckFlags(prop)); } } return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode); @@ -35064,8 +35047,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getFlowTypeOfProperty(node, prop); } propType = getNarrowableTypeForReference(propType, node, checkMode); - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if (prop && (prop.name as string) === "D") { console.log("getFlowTypeOfAccessExpression: propType =", typeToString(propType)); } // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, // and if we are in a constructor of the same class as the property declaration, assume that @@ -35091,8 +35072,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { assumeUninitialized = true; } const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if (prop && (prop.name as string) === "D") { console.log("getFlowTypeOfAccessExpression: flowType =", typeToString(flowType)); } if (assumeUninitialized && !containsUndefinedType(propType) && containsUndefinedType(flowType)) { error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 // Return the declared type to reduce follow-on errors @@ -41395,8 +41374,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const type = getQuickTypeOfExpression(initializer) || (contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) : checkExpressionCached(initializer, checkMode)); - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - if (isPropertyDeclaration(declaration)) { console.log("checkDeclarationInitializer PropertyDecl: checkMode=", checkMode, "initKind=", initializer.kind, "type=", typeToString(type)); } if (isParameter(isBindingElement(declaration) ? walkUpBindingElementsAndPatterns(declaration) : declaration)) { if (declaration.name.kind === SyntaxKind.ObjectBindingPattern && isObjectLiteralType(type)) { return padObjectLiteralType(type as ObjectType, declaration.name); @@ -46653,8 +46630,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let hasDuplicateDefaultClause = false; const expressionType = checkExpression(node.expression); - // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE - console.log("checkSwitchStatement: expressionType =", typeToString(expressionType), "nodeExprKind=", node.expression.kind, "isPAE=", isPropertyAccessExpression(node.expression)); forEach(node.caseBlock.clauses, clause => { // Grammar check for duplicate default clauses, skip if we already report duplicate default clause diff --git a/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.js b/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.js new file mode 100644 index 0000000000000..3293aa9e34a7b --- /dev/null +++ b/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.js @@ -0,0 +1,55 @@ +//// [tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts] //// + +//// [classPropertyInferenceFromBroaderTypeConst.ts] +// Repro from GH#62264 +// Class property should infer the wider declared type (AB), not the narrowed literal type ("A") + +type AB = 'A' | 'B'; + +const DEFAULT: AB = 'A'; + +class C { + D = DEFAULT; + + method() { + switch (this.D) { + case 'A': break; + case 'B': break; // should not error + } + } +} + +// D should be AB, not "A" +declare const c: C; +declare function expectAB(x: AB): void; +expectAB(c.D); // ok +c.D = 'B'; // ok + +// Static property should work the same way +class D { + static SD = DEFAULT; +} +D.SD = 'B'; // ok + + +//// [classPropertyInferenceFromBroaderTypeConst.js] +"use strict"; +// Repro from GH#62264 +// Class property should infer the wider declared type (AB), not the narrowed literal type ("A") +const DEFAULT = 'A'; +class C { + D = DEFAULT; + method() { + switch (this.D) { + case 'A': break; + case 'B': break; // should not error + } + } +} +expectAB(c.D); // ok +c.D = 'B'; // ok +// Static property should work the same way +class D { + static SD = DEFAULT; +} +D.SD = 'B'; // ok diff --git a/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.symbols b/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.symbols new file mode 100644 index 0000000000000..997b69e5225b0 --- /dev/null +++ b/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.symbols @@ -0,0 +1,68 @@ +//// [tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts] //// + +=== classPropertyInferenceFromBroaderTypeConst.ts === +// Repro from GH#62264 +// Class property should infer the wider declared type (AB), not the narrowed literal type ("A") + +type AB = 'A' | 'B'; +>AB : Symbol(AB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 0, 0)) + +const DEFAULT: AB = 'A'; +>DEFAULT : Symbol(DEFAULT, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 5)) +>AB : Symbol(AB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 0, 0)) + +class C { +>C : Symbol(C, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 24)) + + D = DEFAULT; +>D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9)) +>DEFAULT : Symbol(DEFAULT, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 5)) + + method() { +>method : Symbol(C.method, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 8, 16)) + + switch (this.D) { +>this.D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9)) +>this : Symbol(C, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 24)) +>D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9)) + + case 'A': break; + case 'B': break; // should not error + } + } +} + +// D should be AB, not "A" +declare const c: C; +>c : Symbol(c, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 13)) +>C : Symbol(C, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 24)) + +declare function expectAB(x: AB): void; +>expectAB : Symbol(expectAB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 19)) +>x : Symbol(x, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 20, 26)) +>AB : Symbol(AB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 0, 0)) + +expectAB(c.D); // ok +>expectAB : Symbol(expectAB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 19)) +>c.D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9)) +>c : Symbol(c, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 13)) +>D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9)) + +c.D = 'B'; // ok +>c.D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9)) +>c : Symbol(c, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 13)) +>D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9)) + +// Static property should work the same way +class D { +>D : Symbol(D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 22, 10)) + + static SD = DEFAULT; +>SD : Symbol(D.SD, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 25, 9)) +>DEFAULT : Symbol(DEFAULT, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 5)) +} +D.SD = 'B'; // ok +>D.SD : Symbol(D.SD, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 25, 9)) +>D : Symbol(D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 22, 10)) +>SD : Symbol(D.SD, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 25, 9)) + diff --git a/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.types b/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.types new file mode 100644 index 0000000000000..e339f502bae82 --- /dev/null +++ b/tests/baselines/reference/classPropertyInferenceFromBroaderTypeConst.types @@ -0,0 +1,107 @@ +//// [tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts] //// + +=== classPropertyInferenceFromBroaderTypeConst.ts === +// Repro from GH#62264 +// Class property should infer the wider declared type (AB), not the narrowed literal type ("A") + +type AB = 'A' | 'B'; +>AB : AB +> : ^^ + +const DEFAULT: AB = 'A'; +>DEFAULT : AB +> : ^^ +>'A' : "A" +> : ^^^ + +class C { +>C : C +> : ^ + + D = DEFAULT; +>D : AB +> : ^^ +>DEFAULT : AB +> : ^^ + + method() { +>method : () => void +> : ^^^^^^^^^^ + + switch (this.D) { +>this.D : AB +> : ^^ +>this : this +> : ^^^^ +>D : AB +> : ^^ + + case 'A': break; +>'A' : "A" +> : ^^^ + + case 'B': break; // should not error +>'B' : "B" +> : ^^^ + } + } +} + +// D should be AB, not "A" +declare const c: C; +>c : C +> : ^ + +declare function expectAB(x: AB): void; +>expectAB : (x: AB) => void +> : ^ ^^ ^^^^^ +>x : AB +> : ^^ + +expectAB(c.D); // ok +>expectAB(c.D) : void +> : ^^^^ +>expectAB : (x: AB) => void +> : ^ ^^ ^^^^^ +>c.D : AB +> : ^^ +>c : C +> : ^ +>D : AB +> : ^^ + +c.D = 'B'; // ok +>c.D = 'B' : "B" +> : ^^^ +>c.D : AB +> : ^^ +>c : C +> : ^ +>D : AB +> : ^^ +>'B' : "B" +> : ^^^ + +// Static property should work the same way +class D { +>D : D +> : ^ + + static SD = DEFAULT; +>SD : AB +> : ^^ +>DEFAULT : AB +> : ^^ +} +D.SD = 'B'; // ok +>D.SD = 'B' : "B" +> : ^^^ +>D.SD : AB +> : ^^ +>D : typeof D +> : ^^^^^^^^ +>SD : AB +> : ^^ +>'B' : "B" +> : ^^^ + diff --git a/tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts b/tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts new file mode 100644 index 0000000000000..33faa717d76d6 --- /dev/null +++ b/tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts @@ -0,0 +1,31 @@ +// @strict: true + +// Repro from GH#62264 +// Class property should infer the wider declared type (AB), not the narrowed literal type ("A") + +type AB = 'A' | 'B'; + +const DEFAULT: AB = 'A'; + +class C { + D = DEFAULT; + + method() { + switch (this.D) { + case 'A': break; + case 'B': break; // should not error + } + } +} + +// D should be AB, not "A" +declare const c: C; +declare function expectAB(x: AB): void; +expectAB(c.D); // ok +c.D = 'B'; // ok + +// Static property should work the same way +class D { + static SD = DEFAULT; +} +D.SD = 'B'; // ok