Skip to content

Commit

Permalink
basic fix for aliased variables CFA
Browse files Browse the repository at this point in the history
  • Loading branch information
ShuiRuTian committed Oct 7, 2021
1 parent 731ba91 commit 73be1db
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 37 deletions.
72 changes: 53 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22235,11 +22235,24 @@ namespace ts {
&& (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText;
case SyntaxKind.Identifier:
case SyntaxKind.PrivateIdentifier:
return isThisInTypeQuery(source) ?
target.kind === SyntaxKind.ThisKeyword :
target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) ||
(target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) &&
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target);
if (isThisInTypeQuery(source)) {
return target.kind === SyntaxKind.ThisKeyword;
}
// Debug.assert(isIdentifier(source) || isPrivateIdentifier(source));
if (target.kind === SyntaxKind.Identifier
&& getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier)
|| (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement)
&& getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target)
) {
return true;
}
else {
const access = getPropertyAccess(source as Identifier);
if (access) {
return isMatchingReference(isAccessExpression(access) ? access.expression : access.parent.parent.initializer!, target);
}
}
return false;
case SyntaxKind.ThisKeyword:
return target.kind === SyntaxKind.ThisKeyword;
case SyntaxKind.SuperKeyword:
Expand Down Expand Up @@ -23714,7 +23727,7 @@ namespace ts {
}

// @ts-ignore
function __isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
function _isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType;
if (!(type.flags & TypeFlags.Union) || !isAccessExpression(expr)) {
return false;
Expand Down Expand Up @@ -24033,6 +24046,7 @@ namespace ts {
}

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

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

Expand All @@ -24067,7 +24083,7 @@ namespace ts {
// 1. type A is { a: undefined }.
// 2. type B is { a: never }.
// Now we have `type C = { a: {innerType: 1} }`
// 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.
// 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.
// for case 2, it is similar, and we do not even need optional chain. Like `root.a.innerType`

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

return propertyTypeArray;

// Assume reference is a
// If expression is a, return []
// If expression is a.b["c"].d(), return ["b","c","d"]
// If element is BindingElement 'const x = a.b.c', return ["b", "c"]
// If element is BindingElement 'const { c: x } = a.b', return ["b", "c"]
// NOTE: If element expression is not known in compile progress like a.b[f()].d, the result would be undefined
// NOTE: Binding element is restricted for now: reference must be more "expanded" than node, which might be a binding element. See this example:
// const tmp1 = root.a.b; const { b : tmp2 } = root.a;
// if (tmp1.c.d === "A") root.a.b // root would be narrowed
// if (root.a.b.c.d === "A") tmp1.c.d // however, tmp1 would not be narrowed for now
// The result is similar for tmp2
// //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?
function tryGetPropertyPathsOfReferenceFromExpression(expressionOri: Expression, reference: Node): PathInfo[] | undefined {
function tryGetPropertyPathsOfReferenceFromExpression(node: Expression | BindingElement, reference: Node): PathInfo[] | undefined {
const properties = [];
let expr: Expression = expressionOri;
while (isAccessExpression(expr)) {
if (isMatchingReference(reference, expr)) {
return properties;
}
const propName = getAccessedPropertyName(expr);
let exprOrBindingElemenr: BindingElement | Expression | undefined = node;
let access: Expression | undefined;

while (exprOrBindingElemenr && !isMatchingReference(reference, currentNode()) && (isAccessExpression(exprOrBindingElemenr) || isBindingElement(exprOrBindingElemenr))) {
const propName = getAccessedPropertyName(exprOrBindingElemenr);
if (!propName) {
return undefined;
}
properties.unshift({ path: propName, isOptionalChain: isOptionalChain(expr) });
expr = expr.expression;
properties.unshift({ path: propName, isOptionalChain: isOptionalChain(exprOrBindingElemenr) });
access = isBindingElement(exprOrBindingElemenr) ? exprOrBindingElemenr.parent.parent.initializer : exprOrBindingElemenr.expression;
if (!access) {
return undefined;
}
exprOrBindingElemenr = getPropertyAccess(access);
}
if (isMatchingReference(reference, expr)) {

if (isMatchingReference(reference, currentNode())) {
return properties;
}

function currentNode() {
if (exprOrBindingElemenr) return exprOrBindingElemenr;
if (access) return access;
throw new Error("Never");
}
}

function getPropertyTypeFromTypeAccordingToPath(constituentType: Type, pathInfos: PathInfo[], isCallExpression: boolean): NarrowDeepPropertyInfo | undefined {
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/destructuringControlFlow.types
Original file line number Diff line number Diff line change
Expand Up @@ -63,34 +63,34 @@ function f2(obj: [number, string] | null[]) {
>obj[0] : number | null
>obj : [number, string] | null[]
>0 : 0
>obj[1] : string
>obj : [number, string]
>obj[1] : string | null
>obj : [number, string] | null[]
>1 : 1

let c0 = obj[0]; // number
>c0 : number
>obj[0] : number
>obj : [number, string]
>obj : [number, string] | null[]
>0 : 0

let c1 = obj[1]; // string
>c1 : string
>obj[1] : string
>obj : [number, string]
>obj : [number, string] | null[]
>1 : 1

let [d0, d1] = obj;
>d0 : number
>d1 : string
>obj : [number, string]
>obj : [number, string] | null[]

([c0, c1] = obj);
>([c0, c1] = obj) : [number, string]
>[c0, c1] = obj : [number, string]
>([c0, c1] = obj) : [number, string] | null[]
>[c0, c1] = obj : [number, string] | null[]
>[c0, c1] : [number, string]
>c0 : number
>c1 : string
>obj : [number, string]
>obj : [number, string] | null[]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(27,30): error TS
Object literal may only specify known properties, and 'c' does not exist in type '{ a: null; b: string; }'.
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; }'.
Property 'b' does not exist on type '{ a: T; c: number; }'.
tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(132,11): error TS2339: Property 'value' does not exist on type 'never'.


==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (3 errors) ====
==== tests/cases/conformance/types/union/discriminatedUnionTypes2.ts (2 errors) ====
function f10(x : { kind: false, a: string } | { kind: true, b: string } | { kind: string, c: string }) {
if (x.kind === false) {
x.a;
Expand Down Expand Up @@ -144,8 +143,6 @@ tests/cases/conformance/types/union/discriminatedUnionTypes2.ts(132,11): error T
}
else {
x.value; // Error, x is never
~~~~~
!!! error TS2339: Property 'value' does not exist on type 'never'.
}
}

Expand Down
2 changes: 2 additions & 0 deletions tests/baselines/reference/discriminatedUnionTypes2.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,9 @@ function foo1(x: RuntimeValue & { type: 'number' }) {
}
else {
x.value; // Error, x is never
>x.value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
>x : Symbol(x, Decl(discriminatedUnionTypes2.ts, 126, 14))
>value : Symbol(value, Decl(discriminatedUnionTypes2.ts, 122, 23))
}
}

Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/discriminatedUnionTypes2.types
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,9 @@ function foo1(x: RuntimeValue & { type: 'number' }) {
}
else {
x.value; // Error, x is never
>x.value : any
>x : never
>value : any
>x.value : number
>x : { type: "number"; value: number; } & { type: "number"; }
>value : number
}
}

Expand Down
61 changes: 58 additions & 3 deletions tests/baselines/reference/discriminatedUnionTypes3.errors.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS2339: Property 's' does not exist on type 'T'.
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(13,15): error TS2339: Property 'x' does not exist on type 'X | Y'.
Property 'x' does not exist on type 'Y'.
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(16,15): error TS2339: Property 'y' does not exist on type 'X | Y'.
Property 'y' does not exist on type 'X'.
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(34,11): error TS2339: Property 'x' does not exist on type 'X | Y'.
Property 'x' does not exist on type 'Y'.
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(36,11): error TS2339: Property 'y' does not exist on type 'X | Y'.
Property 'y' does not exist on type 'X'.
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(42,11): error TS2339: Property 'x' does not exist on type 'X | W'.
Property 'x' does not exist on type 'W'.
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(55,11): error TS2339: Property 's' does not exist on type 'S | T'.
Property 's' does not exist on type 'T'.
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(56,21): error TS2339: Property 'x' does not exist on type 'X | Y'.
Property 'x' does not exist on type 'Y'.
tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS2339: Property 's' does not exist on type 'S | T'.
Property 's' does not exist on type 'T'.


==== tests/cases/conformance/types/union/discriminatedUnionTypes3.ts (1 errors) ====
==== tests/cases/conformance/types/union/discriminatedUnionTypes3.ts (8 errors) ====
type A = { type2: "a", a: number }
type B = { type2: "b", b: number }
type C = { type2: "c", b: number | string }
Expand All @@ -15,9 +30,15 @@ tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS
switch (x.type1.type2) {
case "a":
x.x // typeof x is X
~
!!! error TS2339: Property 'x' does not exist on type 'X | Y'.
!!! error TS2339: Property 'x' does not exist on type 'Y'.
break;
case "b":
x.y // typeof x is Y
~
!!! error TS2339: Property 'y' does not exist on type 'X | Y'.
!!! error TS2339: Property 'y' does not exist on type 'X'.
break;
}
}
Expand All @@ -36,14 +57,23 @@ tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS
function f34(x: X | Y) {
if (x.type1.type2 === "a") {
x.x // typeof x is X
~
!!! error TS2339: Property 'x' does not exist on type 'X | Y'.
!!! error TS2339: Property 'x' does not exist on type 'Y'.
} else if (x.type1.type2 === "b") {
x.y // typeof x is Y
~
!!! error TS2339: Property 'y' does not exist on type 'X | Y'.
!!! error TS2339: Property 'y' does not exist on type 'X'.
}
}

function f35(x: X | W) {
if (x.type1?.type2 === "a") {
x.x
~
!!! error TS2339: Property 'x' does not exist on type 'X | W'.
!!! error TS2339: Property 'x' does not exist on type 'W'.
}
}

Expand All @@ -57,12 +87,37 @@ tests/cases/conformance/types/union/discriminatedUnionTypes3.ts(59,11): error TS
function f37(s: S | T) {
if (s.sub.type0.type1.type2 === "a") {
s.s // typeof s is S
~
!!! error TS2339: Property 's' does not exist on type 'S | T'.
!!! error TS2339: Property 's' does not exist on type 'T'.
s.sub.type0.x // type of s.sub.type is X
~
!!! error TS2339: Property 'x' does not exist on type 'X | Y'.
!!! error TS2339: Property 'x' does not exist on type 'Y'.
s.sub.type0.type1.a // type of s.sub.type.type is A
} else {
s.s // type error!
~
!!! error TS2339: Property 's' does not exist on type 'T'.
!!! error TS2339: Property 's' does not exist on type 'S | T'.
!!! error TS2339: Property 's' does not exist on type 'T'.
}
}

// Repro from #44435

type Correct = {
code: string
property: true
err: undefined
}
type Err = {
err: `${string} is wrong!`
}
type SomeReturnType = Correct | Err;

const example: SomeReturnType = {} as SomeReturnType;

if (example.err === undefined) {
example.property; // true
}

0 comments on commit 73be1db

Please sign in to comment.