Skip to content

Commit 73be1db

Browse files
committed
basic fix for aliased variables CFA
1 parent 731ba91 commit 73be1db

File tree

6 files changed

+125
-37
lines changed

6 files changed

+125
-37
lines changed

src/compiler/checker.ts

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22235,11 +22235,24 @@ namespace ts {
2223522235
&& (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText;
2223622236
case SyntaxKind.Identifier:
2223722237
case SyntaxKind.PrivateIdentifier:
22238-
return isThisInTypeQuery(source) ?
22239-
target.kind === SyntaxKind.ThisKeyword :
22240-
target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) ||
22241-
(target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) &&
22242-
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target);
22238+
if (isThisInTypeQuery(source)) {
22239+
return target.kind === SyntaxKind.ThisKeyword;
22240+
}
22241+
// Debug.assert(isIdentifier(source) || isPrivateIdentifier(source));
22242+
if (target.kind === SyntaxKind.Identifier
22243+
&& getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier)
22244+
|| (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement)
22245+
&& getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target)
22246+
) {
22247+
return true;
22248+
}
22249+
else {
22250+
const access = getPropertyAccess(source as Identifier);
22251+
if (access) {
22252+
return isMatchingReference(isAccessExpression(access) ? access.expression : access.parent.parent.initializer!, target);
22253+
}
22254+
}
22255+
return false;
2224322256
case SyntaxKind.ThisKeyword:
2224422257
return target.kind === SyntaxKind.ThisKeyword;
2224522258
case SyntaxKind.SuperKeyword:
@@ -23714,7 +23727,7 @@ namespace ts {
2371423727
}
2371523728

2371623729
// @ts-ignore
23717-
function __isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
23730+
function _isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
2371823731
const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType;
2371923732
if (!(type.flags & TypeFlags.Union) || !isAccessExpression(expr)) {
2372023733
return false;
@@ -24033,6 +24046,7 @@ namespace ts {
2403324046
}
2403424047

2403524048
/**
24049+
* Assume there is variable `foo` whose type is `A`, try to get the final type of foo.property1.proerty2...
2403624050
* @param type Assume this should be just the type of 'reference'
2403724051
* @param expressionWithOutKeyword an expression without any keyword. "typeof root.a" is not acceptable.
2403824052
*/
@@ -24051,8 +24065,10 @@ namespace ts {
2405124065
const nonCallExpressionWithOutKeyword = expressionWithOutKeyword;
2405224066

2405324067
// check some condition, if not meet, it means we could not handle this confition for now.
24054-
if (nonCallExpressionWithOutKeyword.kind !== SyntaxKind.BindingElement && nonCallExpressionWithOutKeyword.kind !== SyntaxKind.Identifier && !isAccessExpression(nonCallExpressionWithOutKeyword)) {// || (<AccessExpression>expressionWithOutKeyword).expression.kind === SyntaxKind.ThisKeyword) {
24055-
// ATTENTION: binding element is not handled!!!!!!!!!!!!!!!
24068+
if (nonCallExpressionWithOutKeyword.kind !== SyntaxKind.Identifier && nonCallExpressionWithOutKeyword.kind !== SyntaxKind.BindingElement && !isAccessExpression(nonCallExpressionWithOutKeyword)) {// || (<AccessExpression>expressionWithOutKeyword).expression.kind === SyntaxKind.ThisKeyword) {
24069+
// if (nonCallExpressionWithOutKeyword.kind === SyntaxKind.BindingElement) {
24070+
// debugger;
24071+
// }
2405624072
return undefined;
2405724073
}
2405824074

@@ -24067,7 +24083,7 @@ namespace ts {
2406724083
// 1. type A is { a: undefined }.
2406824084
// 2. type B is { a: never }.
2406924085
// Now we have `type C = { a: {innerType: 1} }`
24070-
// for case 1, create `type X = A|C`, it should be filtered through optional chain, and not cause any error although A.a does not have any inner type. Like `root.a?.innerType` is valid.
24086+
// for case 1, create `type X = A|C`, it should be filtered through optional chain, and not cause any error, although A.a does not have any inner type. Like `root.a?.innerType` is valid.
2407124087
// for case 2, it is similar, and we do not even need optional chain. Like `root.a.innerType`
2407224088

2407324089
let propertyTypeArray: NarrowDeepPropertyInfo[] | undefined;
@@ -24084,27 +24100,45 @@ namespace ts {
2408424100

2408524101
return propertyTypeArray;
2408624102

24103+
// Assume reference is a
2408724104
// If expression is a, return []
2408824105
// If expression is a.b["c"].d(), return ["b","c","d"]
24106+
// If element is BindingElement 'const x = a.b.c', return ["b", "c"]
24107+
// If element is BindingElement 'const { c: x } = a.b', return ["b", "c"]
2408924108
// NOTE: If element expression is not known in compile progress like a.b[f()].d, the result would be undefined
24109+
// NOTE: Binding element is restricted for now: reference must be more "expanded" than node, which might be a binding element. See this example:
24110+
// const tmp1 = root.a.b; const { b : tmp2 } = root.a;
24111+
// if (tmp1.c.d === "A") root.a.b // root would be narrowed
24112+
// if (root.a.b.c.d === "A") tmp1.c.d // however, tmp1 would not be narrowed for now
24113+
// The result is similar for tmp2
2409024114
// //NOTE: this function need improvement, ElementAccessExpression argument might could be known in compile time, like "1"+"2", we should check "12" in the path, but how to get the value?
24091-
function tryGetPropertyPathsOfReferenceFromExpression(expressionOri: Expression, reference: Node): PathInfo[] | undefined {
24115+
function tryGetPropertyPathsOfReferenceFromExpression(node: Expression | BindingElement, reference: Node): PathInfo[] | undefined {
2409224116
const properties = [];
24093-
let expr: Expression = expressionOri;
24094-
while (isAccessExpression(expr)) {
24095-
if (isMatchingReference(reference, expr)) {
24096-
return properties;
24097-
}
24098-
const propName = getAccessedPropertyName(expr);
24117+
let exprOrBindingElemenr: BindingElement | Expression | undefined = node;
24118+
let access: Expression | undefined;
24119+
24120+
while (exprOrBindingElemenr && !isMatchingReference(reference, currentNode()) && (isAccessExpression(exprOrBindingElemenr) || isBindingElement(exprOrBindingElemenr))) {
24121+
const propName = getAccessedPropertyName(exprOrBindingElemenr);
2409924122
if (!propName) {
2410024123
return undefined;
2410124124
}
24102-
properties.unshift({ path: propName, isOptionalChain: isOptionalChain(expr) });
24103-
expr = expr.expression;
24125+
properties.unshift({ path: propName, isOptionalChain: isOptionalChain(exprOrBindingElemenr) });
24126+
access = isBindingElement(exprOrBindingElemenr) ? exprOrBindingElemenr.parent.parent.initializer : exprOrBindingElemenr.expression;
24127+
if (!access) {
24128+
return undefined;
24129+
}
24130+
exprOrBindingElemenr = getPropertyAccess(access);
2410424131
}
24105-
if (isMatchingReference(reference, expr)) {
24132+
24133+
if (isMatchingReference(reference, currentNode())) {
2410624134
return properties;
2410724135
}
24136+
24137+
function currentNode() {
24138+
if (exprOrBindingElemenr) return exprOrBindingElemenr;
24139+
if (access) return access;
24140+
throw new Error("Never");
24141+
}
2410824142
}
2410924143

2411024144
function getPropertyTypeFromTypeAccordingToPath(constituentType: Type, pathInfos: PathInfo[], isCallExpression: boolean): NarrowDeepPropertyInfo | undefined {

tests/baselines/reference/destructuringControlFlow.types

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,34 +63,34 @@ function f2(obj: [number, string] | null[]) {
6363
>obj[0] : number | null
6464
>obj : [number, string] | null[]
6565
>0 : 0
66-
>obj[1] : string
67-
>obj : [number, string]
66+
>obj[1] : string | null
67+
>obj : [number, string] | null[]
6868
>1 : 1
6969

7070
let c0 = obj[0]; // number
7171
>c0 : number
7272
>obj[0] : number
73-
>obj : [number, string]
73+
>obj : [number, string] | null[]
7474
>0 : 0
7575

7676
let c1 = obj[1]; // string
7777
>c1 : string
7878
>obj[1] : string
79-
>obj : [number, string]
79+
>obj : [number, string] | null[]
8080
>1 : 1
8181

8282
let [d0, d1] = obj;
8383
>d0 : number
8484
>d1 : string
85-
>obj : [number, string]
85+
>obj : [number, string] | null[]
8686

8787
([c0, c1] = obj);
88-
>([c0, c1] = obj) : [number, string]
89-
>[c0, c1] = obj : [number, string]
88+
>([c0, c1] = obj) : [number, string] | null[]
89+
>[c0, c1] = obj : [number, string] | null[]
9090
>[c0, c1] : [number, string]
9191
>c0 : number
9292
>c1 : string
93-
>obj : [number, string]
93+
>obj : [number, string] | null[]
9494
}
9595
}
9696

tests/baselines/reference/discriminatedUnionTypes2.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(27,30): error TS
22
Object literal may only specify known properties, and 'c' does not exist in type '{ a: null; b: string; }'.
33
tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(32,11): error TS2339: Property 'b' does not exist on type '{ a: 0; b: string; } | { a: T; c: number; }'.
44
Property 'b' does not exist on type '{ a: T; c: number; }'.
5-
tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(132,11): error TS2339: Property 'value' does not exist on type 'never'.
65

76

8-
==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (3 errors) ====
7+
==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (2 errors) ====
98
function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) {
109
if (x.kind === false) {
1110
x.a;
@@ -144,8 +143,6 @@ tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(132,11): error T
144143
}
145144
else {
146145
x.value; // Error, x is never
147-
~~~~~
148-
!!! error TS2339: Property 'value' does not exist on type 'never'.
149146
}
150147
}
151148

tests/baselines/reference/discriminatedUnionTypes2.symbols

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,9 @@ function foo1(x: RuntimeValue & { type: 'number' }) {
367367
}
368368
else {
369369
x.value; // Error, x is never
370+
>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
370371
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
372+
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
371373
}
372374
}
373375

tests/baselines/reference/discriminatedUnionTypes2.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,9 @@ function foo1(x: RuntimeValue & { type: 'number' }) {
380380
}
381381
else {
382382
x.value; // Error, x is never
383-
>x.value : any
384-
>x : never
385-
>value : any
383+
>x.value : number
384+
>x : { type: "number"; value: number; } & { type: "number"; }
385+
>value : number
386386
}
387387
}
388388

tests/baselines/reference/discriminatedUnionTypes3.errors.txt

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
1-
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS2339: Property 's' does not exist on type 'T'.
1+
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(13,15): error TS2339: Property 'x' does not exist on type 'X | Y'.
2+
Property 'x' does not exist on type 'Y'.
3+
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(16,15): error TS2339: Property 'y' does not exist on type 'X | Y'.
4+
Property 'y' does not exist on type 'X'.
5+
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(34,11): error TS2339: Property 'x' does not exist on type 'X | Y'.
6+
Property 'x' does not exist on type 'Y'.
7+
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(36,11): error TS2339: Property 'y' does not exist on type 'X | Y'.
8+
Property 'y' does not exist on type 'X'.
9+
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(42,11): error TS2339: Property 'x' does not exist on type 'X | W'.
10+
Property 'x' does not exist on type 'W'.
11+
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(55,11): error TS2339: Property 's' does not exist on type 'S | T'.
12+
Property 's' does not exist on type 'T'.
13+
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(56,21): error TS2339: Property 'x' does not exist on type 'X | Y'.
14+
Property 'x' does not exist on type 'Y'.
15+
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS2339: Property 's' does not exist on type 'S | T'.
16+
Property 's' does not exist on type 'T'.
217

318

4-
==== tests/cases/conformance/types/union/discriminatedUnionTypes3.ts (1 errors) ====
19+
==== tests/cases/conformance/types/union/discriminatedUnionTypes3.ts (8 errors) ====
520
type A = { type2: "a", a: number }
621
type B = { type2: "b", b: number }
722
type C = { type2: "c", b: number | string }
@@ -15,9 +30,15 @@ tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS
1530
switch (x.type1.type2) {
1631
case "a":
1732
x.x // typeof x is X
33+
~
34+
!!! error TS2339: Property 'x' does not exist on type 'X | Y'.
35+
!!! error TS2339: Property 'x' does not exist on type 'Y'.
1836
break;
1937
case "b":
2038
x.y // typeof x is Y
39+
~
40+
!!! error TS2339: Property 'y' does not exist on type 'X | Y'.
41+
!!! error TS2339: Property 'y' does not exist on type 'X'.
2142
break;
2243
}
2344
}
@@ -36,14 +57,23 @@ tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS
3657
function f34(x: X | Y) {
3758
if (x.type1.type2 === "a") {
3859
x.x // typeof x is X
60+
~
61+
!!! error TS2339: Property 'x' does not exist on type 'X | Y'.
62+
!!! error TS2339: Property 'x' does not exist on type 'Y'.
3963
} else if (x.type1.type2 === "b") {
4064
x.y // typeof x is Y
65+
~
66+
!!! error TS2339: Property 'y' does not exist on type 'X | Y'.
67+
!!! error TS2339: Property 'y' does not exist on type 'X'.
4168
}
4269
}
4370

4471
function f35(x: X | W) {
4572
if (x.type1?.type2 === "a") {
4673
x.x
74+
~
75+
!!! error TS2339: Property 'x' does not exist on type 'X | W'.
76+
!!! error TS2339: Property 'x' does not exist on type 'W'.
4777
}
4878
}
4979

@@ -57,12 +87,37 @@ tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS
5787
function f37(s: S | T) {
5888
if (s.sub.type0.type1.type2 === "a") {
5989
s.s // typeof s is S
90+
~
91+
!!! error TS2339: Property 's' does not exist on type 'S | T'.
92+
!!! error TS2339: Property 's' does not exist on type 'T'.
6093
s.sub.type0.x // type of s.sub.type is X
94+
~
95+
!!! error TS2339: Property 'x' does not exist on type 'X | Y'.
96+
!!! error TS2339: Property 'x' does not exist on type 'Y'.
6197
s.sub.type0.type1.a // type of s.sub.type.type is A
6298
} else {
6399
s.s // type error!
64100
~
65-
!!! error TS2339: Property 's' does not exist on type 'T'.
101+
!!! error TS2339: Property 's' does not exist on type 'S | T'.
102+
!!! error TS2339: Property 's' does not exist on type 'T'.
66103
}
67104
}
105+
106+
// Repro from #44435
107+
108+
type Correct = {
109+
code: string
110+
property: true
111+
err: undefined
112+
}
113+
type Err = {
114+
err: `${string} is wrong!`
115+
}
116+
type SomeReturnType = Correct | Err;
117+
118+
const example: SomeReturnType = {} as SomeReturnType;
119+
120+
if (example.err === undefined) {
121+
example.property; // true
122+
}
68123

0 commit comments

Comments
 (0)