Skip to content

Commit f7b579a

Browse files
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>
1 parent e491322 commit f7b579a

File tree

6 files changed

+266
-27
lines changed

6 files changed

+266
-27
lines changed

src/compiler/binder.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3869,6 +3869,9 @@ export function getContainerFlags(node: Node): ContainerFlags {
38693869
case SyntaxKind.ModuleBlock:
38703870
return ContainerFlags.IsControlFlowContainer;
38713871

3872+
case SyntaxKind.PropertyDeclaration:
3873+
return (node as PropertyDeclaration).initializer ? ContainerFlags.IsControlFlowContainer : ContainerFlags.None;
3874+
38723875
case SyntaxKind.CatchClause:
38733876
case SyntaxKind.ForStatement:
38743877
case SyntaxKind.ForInStatement:

src/compiler/checker.ts

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11940,10 +11940,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1194011940
return containerObjectType;
1194111941
}
1194211942
}
11943-
const initType = checkDeclarationInitializer(declaration, checkMode);
11944-
const type = widenTypeInferredFromInitializer(declaration, initType);
11945-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
11946-
if (isPropertyDeclaration(declaration) && declaration.name && isIdentifier(declaration.name) && declaration.name.text === "D") { console.log("getTypeForVariableLikeDeclaration D: initType=", typeToString(initType), "widenedType=", typeToString(type), "checkMode=", checkMode); }
11943+
const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode));
1194711944
return addOptionality(type, isProperty, isOptional);
1194811945
}
1194911946

@@ -12452,11 +12449,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1245212449
// binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the
1245312450
// tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string.
1245412451
function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type {
12455-
const innerType = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal);
12456-
const result = widenTypeForVariableLikeDeclaration(innerType, declaration, reportErrors);
12457-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
12458-
if (isPropertyDeclaration(declaration)) { console.log("getWidenedTypeForVariableLikeDeclaration PropertyDecl: declName=", isIdentifier(declaration.name) ? declaration.name.text : "?", "innerType=", innerType ? typeToString(innerType) : "undefined", "result=", typeToString(result)); }
12459-
return result;
12452+
return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors);
1246012453
}
1246112454

1246212455
function getTypeFromImportAttributes(node: ImportAttributes): Type {
@@ -12553,12 +12546,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1255312546
if (!links.type && !isParameterOfContextSensitiveSignature(symbol)) {
1255412547
links.type = type;
1255512548
}
12556-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
12557-
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(' | ')); }
1255812549
return type;
1255912550
}
12560-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
12561-
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(' | ')); }
1256212551
return links.type;
1256312552
}
1256412553

@@ -12659,8 +12648,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1265912648
|| isJSDocPropertyLikeTag(declaration)
1266012649
) {
1266112650
type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true);
12662-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
12663-
if (isPropertyDeclaration(declaration) && declaration.name && (declaration.name as any).text === "D") { console.log("D property type =", typeToString(type)); }
1266412651
}
1266512652
// getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive.
1266612653
// Re-dispatch based on valueDeclaration.kind instead.
@@ -12977,8 +12964,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1297712964
return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol);
1297812965
}
1297912966
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
12980-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
12981-
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; }
1298212967
return getTypeOfVariableOrParameterOrProperty(symbol);
1298312968
}
1298412969
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
@@ -35013,8 +34998,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3501334998
}
3501434999

3501535000
propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writeOnly || isWriteOnlyAccess(node) ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop);
35016-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
35017-
if (isPropertyDeclaration(prop?.valueDeclaration) && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword) { console.log("checkPropAccess: prop=", symbolToString(prop), "propType=", typeToString(propType), "checkFlags=", getCheckFlags(prop)); }
3501835001
}
3501935002

3502035003
return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode);
@@ -35064,8 +35047,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3506435047
return getFlowTypeOfProperty(node, prop);
3506535048
}
3506635049
propType = getNarrowableTypeForReference(propType, node, checkMode);
35067-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
35068-
if (prop && (prop.name as string) === "D") { console.log("getFlowTypeOfAccessExpression: propType =", typeToString(propType)); }
3506935050
// If strict null checks and strict property initialization checks are enabled, if we have
3507035051
// a this.xxx property access, if the property is an instance property without an initializer,
3507135052
// 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 {
3509135072
assumeUninitialized = true;
3509235073
}
3509335074
const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType);
35094-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
35095-
if (prop && (prop.name as string) === "D") { console.log("getFlowTypeOfAccessExpression: flowType =", typeToString(flowType)); }
3509635075
if (assumeUninitialized && !containsUndefinedType(propType) && containsUndefinedType(flowType)) {
3509735076
error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
3509835077
// Return the declared type to reduce follow-on errors
@@ -41395,8 +41374,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4139541374
const type = getQuickTypeOfExpression(initializer) || (contextualType ?
4139641375
checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) :
4139741376
checkExpressionCached(initializer, checkMode));
41398-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
41399-
if (isPropertyDeclaration(declaration)) { console.log("checkDeclarationInitializer PropertyDecl: checkMode=", checkMode, "initKind=", initializer.kind, "type=", typeToString(type)); }
4140041377
if (isParameter(isBindingElement(declaration) ? walkUpBindingElementsAndPatterns(declaration) : declaration)) {
4140141378
if (declaration.name.kind === SyntaxKind.ObjectBindingPattern && isObjectLiteralType(type)) {
4140241379
return padObjectLiteralType(type as ObjectType, declaration.name);
@@ -46653,8 +46630,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4665346630
let hasDuplicateDefaultClause = false;
4665446631

4665546632
const expressionType = checkExpression(node.expression);
46656-
// @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
46657-
console.log("checkSwitchStatement: expressionType =", typeToString(expressionType), "nodeExprKind=", node.expression.kind, "isPAE=", isPropertyAccessExpression(node.expression));
4665846633

4665946634
forEach(node.caseBlock.clauses, clause => {
4666046635
// Grammar check for duplicate default clauses, skip if we already report duplicate default clause
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//// [tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts] ////
2+
3+
//// [classPropertyInferenceFromBroaderTypeConst.ts]
4+
// Repro from GH#62264
5+
// Class property should infer the wider declared type (AB), not the narrowed literal type ("A")
6+
7+
type AB = 'A' | 'B';
8+
9+
const DEFAULT: AB = 'A';
10+
11+
class C {
12+
D = DEFAULT;
13+
14+
method() {
15+
switch (this.D) {
16+
case 'A': break;
17+
case 'B': break; // should not error
18+
}
19+
}
20+
}
21+
22+
// D should be AB, not "A"
23+
declare const c: C;
24+
declare function expectAB(x: AB): void;
25+
expectAB(c.D); // ok
26+
c.D = 'B'; // ok
27+
28+
// Static property should work the same way
29+
class D {
30+
static SD = DEFAULT;
31+
}
32+
D.SD = 'B'; // ok
33+
34+
35+
//// [classPropertyInferenceFromBroaderTypeConst.js]
36+
"use strict";
37+
// Repro from GH#62264
38+
// Class property should infer the wider declared type (AB), not the narrowed literal type ("A")
39+
const DEFAULT = 'A';
40+
class C {
41+
D = DEFAULT;
42+
method() {
43+
switch (this.D) {
44+
case 'A': break;
45+
case 'B': break; // should not error
46+
}
47+
}
48+
}
49+
expectAB(c.D); // ok
50+
c.D = 'B'; // ok
51+
// Static property should work the same way
52+
class D {
53+
static SD = DEFAULT;
54+
}
55+
D.SD = 'B'; // ok
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//// [tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts] ////
2+
3+
=== classPropertyInferenceFromBroaderTypeConst.ts ===
4+
// Repro from GH#62264
5+
// Class property should infer the wider declared type (AB), not the narrowed literal type ("A")
6+
7+
type AB = 'A' | 'B';
8+
>AB : Symbol(AB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 0, 0))
9+
10+
const DEFAULT: AB = 'A';
11+
>DEFAULT : Symbol(DEFAULT, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 5))
12+
>AB : Symbol(AB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 0, 0))
13+
14+
class C {
15+
>C : Symbol(C, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 24))
16+
17+
D = DEFAULT;
18+
>D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9))
19+
>DEFAULT : Symbol(DEFAULT, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 5))
20+
21+
method() {
22+
>method : Symbol(C.method, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 8, 16))
23+
24+
switch (this.D) {
25+
>this.D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9))
26+
>this : Symbol(C, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 24))
27+
>D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9))
28+
29+
case 'A': break;
30+
case 'B': break; // should not error
31+
}
32+
}
33+
}
34+
35+
// D should be AB, not "A"
36+
declare const c: C;
37+
>c : Symbol(c, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 13))
38+
>C : Symbol(C, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 24))
39+
40+
declare function expectAB(x: AB): void;
41+
>expectAB : Symbol(expectAB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 19))
42+
>x : Symbol(x, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 20, 26))
43+
>AB : Symbol(AB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 0, 0))
44+
45+
expectAB(c.D); // ok
46+
>expectAB : Symbol(expectAB, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 19))
47+
>c.D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9))
48+
>c : Symbol(c, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 13))
49+
>D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9))
50+
51+
c.D = 'B'; // ok
52+
>c.D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9))
53+
>c : Symbol(c, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 19, 13))
54+
>D : Symbol(C.D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 7, 9))
55+
56+
// Static property should work the same way
57+
class D {
58+
>D : Symbol(D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 22, 10))
59+
60+
static SD = DEFAULT;
61+
>SD : Symbol(D.SD, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 25, 9))
62+
>DEFAULT : Symbol(DEFAULT, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 5, 5))
63+
}
64+
D.SD = 'B'; // ok
65+
>D.SD : Symbol(D.SD, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 25, 9))
66+
>D : Symbol(D, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 22, 10))
67+
>SD : Symbol(D.SD, Decl(classPropertyInferenceFromBroaderTypeConst.ts, 25, 9))
68+
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//// [tests/cases/compiler/classPropertyInferenceFromBroaderTypeConst.ts] ////
2+
3+
=== classPropertyInferenceFromBroaderTypeConst.ts ===
4+
// Repro from GH#62264
5+
// Class property should infer the wider declared type (AB), not the narrowed literal type ("A")
6+
7+
type AB = 'A' | 'B';
8+
>AB : AB
9+
> : ^^
10+
11+
const DEFAULT: AB = 'A';
12+
>DEFAULT : AB
13+
> : ^^
14+
>'A' : "A"
15+
> : ^^^
16+
17+
class C {
18+
>C : C
19+
> : ^
20+
21+
D = DEFAULT;
22+
>D : AB
23+
> : ^^
24+
>DEFAULT : AB
25+
> : ^^
26+
27+
method() {
28+
>method : () => void
29+
> : ^^^^^^^^^^
30+
31+
switch (this.D) {
32+
>this.D : AB
33+
> : ^^
34+
>this : this
35+
> : ^^^^
36+
>D : AB
37+
> : ^^
38+
39+
case 'A': break;
40+
>'A' : "A"
41+
> : ^^^
42+
43+
case 'B': break; // should not error
44+
>'B' : "B"
45+
> : ^^^
46+
}
47+
}
48+
}
49+
50+
// D should be AB, not "A"
51+
declare const c: C;
52+
>c : C
53+
> : ^
54+
55+
declare function expectAB(x: AB): void;
56+
>expectAB : (x: AB) => void
57+
> : ^ ^^ ^^^^^
58+
>x : AB
59+
> : ^^
60+
61+
expectAB(c.D); // ok
62+
>expectAB(c.D) : void
63+
> : ^^^^
64+
>expectAB : (x: AB) => void
65+
> : ^ ^^ ^^^^^
66+
>c.D : AB
67+
> : ^^
68+
>c : C
69+
> : ^
70+
>D : AB
71+
> : ^^
72+
73+
c.D = 'B'; // ok
74+
>c.D = 'B' : "B"
75+
> : ^^^
76+
>c.D : AB
77+
> : ^^
78+
>c : C
79+
> : ^
80+
>D : AB
81+
> : ^^
82+
>'B' : "B"
83+
> : ^^^
84+
85+
// Static property should work the same way
86+
class D {
87+
>D : D
88+
> : ^
89+
90+
static SD = DEFAULT;
91+
>SD : AB
92+
> : ^^
93+
>DEFAULT : AB
94+
> : ^^
95+
}
96+
D.SD = 'B'; // ok
97+
>D.SD = 'B' : "B"
98+
> : ^^^
99+
>D.SD : AB
100+
> : ^^
101+
>D : typeof D
102+
> : ^^^^^^^^
103+
>SD : AB
104+
> : ^^
105+
>'B' : "B"
106+
> : ^^^
107+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// @strict: true
2+
3+
// Repro from GH#62264
4+
// Class property should infer the wider declared type (AB), not the narrowed literal type ("A")
5+
6+
type AB = 'A' | 'B';
7+
8+
const DEFAULT: AB = 'A';
9+
10+
class C {
11+
D = DEFAULT;
12+
13+
method() {
14+
switch (this.D) {
15+
case 'A': break;
16+
case 'B': break; // should not error
17+
}
18+
}
19+
}
20+
21+
// D should be AB, not "A"
22+
declare const c: C;
23+
declare function expectAB(x: AB): void;
24+
expectAB(c.D); // ok
25+
c.D = 'B'; // ok
26+
27+
// Static property should work the same way
28+
class D {
29+
static SD = DEFAULT;
30+
}
31+
D.SD = 'B'; // ok

0 commit comments

Comments
 (0)