Fix discriminant property check in CFA (#56156)

This commit is contained in:
Anders Hejlsberg 2023-10-19 14:04:41 -07:00 committed by GitHub
parent 3c221fc086
commit ac75a5ec9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 338 additions and 4 deletions

View File

@ -27677,13 +27677,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
const type = !(computedType.flags & TypeFlags.Union) && declaredType.flags & TypeFlags.Union ? declaredType : computedType;
if (type.flags & TypeFlags.Union) {
// As long as the computed type is a subset of the declared type, we use the full declared type to detect
// a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type
// predicate narrowing, we use the actual computed type.
if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) {
const access = getCandidateDiscriminantPropertyAccess(expr);
if (access) {
const name = getAccessedPropertyName(access);
if (name && isDiscriminantProperty(type, name)) {
return access;
if (name) {
const type = declaredType.flags & TypeFlags.Union && isTypeSubsetOf(computedType, declaredType) ? declaredType : computedType;
if (isDiscriminantProperty(type, name)) {
return access;
}
}
}
}

View File

@ -226,6 +226,43 @@ declare const workingAgain: Record<string, any> | undefined | unknown;
isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp;
isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp;
isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp;
// Repro from #56144
type Union =
| { type: 'a'; variant: 1 }
| { type: 'a'; variant: 2 }
| { type: 'b' };
function example1(value: Union): { type: 'a'; variant: 2 } | null {
if (value.type !== 'a') {
return null;
}
if (value.variant === 1) {
return null;
}
return value;
}
function example2(value: Union): { type: 'a'; variant: 2 } | null {
if (value.type !== 'a') {
return null;
}
if (value.type === 'a' && value.variant === 1) {
return null;
}
return value;
}
function example3(value: Union): { type: 'a'; variant: 2 } | null {
if (value.type !== 'a') {
return null;
}
if (value.type && value.variant === 1) {
return null;
}
return value;
}
//// [narrowingUnionToUnion.js]
@ -387,6 +424,33 @@ function f1x(obj) {
isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp;
isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp;
isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp;
function example1(value) {
if (value.type !== 'a') {
return null;
}
if (value.variant === 1) {
return null;
}
return value;
}
function example2(value) {
if (value.type !== 'a') {
return null;
}
if (value.type === 'a' && value.variant === 1) {
return null;
}
return value;
}
function example3(value) {
if (value.type !== 'a') {
return null;
}
if (value.type && value.variant === 1) {
return null;
}
return value;
}
//// [narrowingUnionToUnion.d.ts]
@ -451,3 +515,24 @@ declare function isMyDiscriminatedUnion(item: unknown): item is MyDiscriminatedU
declare const working: unknown;
declare const broken: Record<string, any> | undefined;
declare const workingAgain: Record<string, any> | undefined | unknown;
type Union = {
type: 'a';
variant: 1;
} | {
type: 'a';
variant: 2;
} | {
type: 'b';
};
declare function example1(value: Union): {
type: 'a';
variant: 2;
} | null;
declare function example2(value: Union): {
type: 'a';
variant: 2;
} | null;
declare function example3(value: Union): {
type: 'a';
variant: 2;
} | null;

View File

@ -538,3 +538,100 @@ isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgai
>workingAgain : Symbol(workingAgain, Decl(narrowingUnionToUnion.ts, 220, 13))
>aProp : Symbol(aProp, Decl(narrowingUnionToUnion.ts, 214, 40))
// Repro from #56144
type Union =
>Union : Symbol(Union, Decl(narrowingUnionToUnion.ts, 224, 88))
| { type: 'a'; variant: 1 }
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7))
>variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 229, 18))
| { type: 'a'; variant: 2 }
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 230, 7))
>variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 230, 18))
| { type: 'b' };
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 231, 7))
function example1(value: Union): { type: 'a'; variant: 2 } | null {
>example1 : Symbol(example1, Decl(narrowingUnionToUnion.ts, 231, 20))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 233, 18))
>Union : Symbol(Union, Decl(narrowingUnionToUnion.ts, 224, 88))
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 233, 34))
>variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 233, 45))
if (value.type !== 'a') {
>value.type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7), Decl(narrowingUnionToUnion.ts, 231, 7))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 233, 18))
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7), Decl(narrowingUnionToUnion.ts, 231, 7))
return null;
}
if (value.variant === 1) {
>value.variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 229, 18), Decl(narrowingUnionToUnion.ts, 230, 18))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 233, 18))
>variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 229, 18), Decl(narrowingUnionToUnion.ts, 230, 18))
return null;
}
return value;
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 233, 18))
}
function example2(value: Union): { type: 'a'; variant: 2 } | null {
>example2 : Symbol(example2, Decl(narrowingUnionToUnion.ts, 241, 1))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 243, 18))
>Union : Symbol(Union, Decl(narrowingUnionToUnion.ts, 224, 88))
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 243, 34))
>variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 243, 45))
if (value.type !== 'a') {
>value.type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7), Decl(narrowingUnionToUnion.ts, 231, 7))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 243, 18))
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7), Decl(narrowingUnionToUnion.ts, 231, 7))
return null;
}
if (value.type === 'a' && value.variant === 1) {
>value.type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 243, 18))
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7))
>value.variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 229, 18), Decl(narrowingUnionToUnion.ts, 230, 18))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 243, 18))
>variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 229, 18), Decl(narrowingUnionToUnion.ts, 230, 18))
return null;
}
return value;
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 243, 18))
}
function example3(value: Union): { type: 'a'; variant: 2 } | null {
>example3 : Symbol(example3, Decl(narrowingUnionToUnion.ts, 251, 1))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 253, 18))
>Union : Symbol(Union, Decl(narrowingUnionToUnion.ts, 224, 88))
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 253, 34))
>variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 253, 45))
if (value.type !== 'a') {
>value.type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7), Decl(narrowingUnionToUnion.ts, 231, 7))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 253, 18))
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7), Decl(narrowingUnionToUnion.ts, 231, 7))
return null;
}
if (value.type && value.variant === 1) {
>value.type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 253, 18))
>type : Symbol(type, Decl(narrowingUnionToUnion.ts, 229, 7), Decl(narrowingUnionToUnion.ts, 230, 7))
>value.variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 229, 18), Decl(narrowingUnionToUnion.ts, 230, 18))
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 253, 18))
>variant : Symbol(variant, Decl(narrowingUnionToUnion.ts, 229, 18), Decl(narrowingUnionToUnion.ts, 230, 18))
return null;
}
return value;
>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 253, 18))
}

View File

@ -574,3 +574,113 @@ isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgai
>workingAgain : { type: "A"; aProp: number; }
>aProp : number
// Repro from #56144
type Union =
>Union : { type: 'a'; variant: 1; } | { type: 'a'; variant: 2; } | { type: 'b'; }
| { type: 'a'; variant: 1 }
>type : "a"
>variant : 1
| { type: 'a'; variant: 2 }
>type : "a"
>variant : 2
| { type: 'b' };
>type : "b"
function example1(value: Union): { type: 'a'; variant: 2 } | null {
>example1 : (value: Union) => { type: 'a'; variant: 2;} | null
>value : Union
>type : "a"
>variant : 2
if (value.type !== 'a') {
>value.type !== 'a' : boolean
>value.type : "a" | "b"
>value : Union
>type : "a" | "b"
>'a' : "a"
return null;
}
if (value.variant === 1) {
>value.variant === 1 : boolean
>value.variant : 1 | 2
>value : { type: "a"; variant: 1; } | { type: "a"; variant: 2; }
>variant : 1 | 2
>1 : 1
return null;
}
return value;
>value : { type: "a"; variant: 2; }
}
function example2(value: Union): { type: 'a'; variant: 2 } | null {
>example2 : (value: Union) => { type: 'a'; variant: 2;} | null
>value : Union
>type : "a"
>variant : 2
if (value.type !== 'a') {
>value.type !== 'a' : boolean
>value.type : "a" | "b"
>value : Union
>type : "a" | "b"
>'a' : "a"
return null;
}
if (value.type === 'a' && value.variant === 1) {
>value.type === 'a' && value.variant === 1 : boolean
>value.type === 'a' : boolean
>value.type : "a"
>value : { type: "a"; variant: 1; } | { type: "a"; variant: 2; }
>type : "a"
>'a' : "a"
>value.variant === 1 : boolean
>value.variant : 1 | 2
>value : { type: "a"; variant: 1; } | { type: "a"; variant: 2; }
>variant : 1 | 2
>1 : 1
return null;
}
return value;
>value : { type: "a"; variant: 2; }
}
function example3(value: Union): { type: 'a'; variant: 2 } | null {
>example3 : (value: Union) => { type: 'a'; variant: 2;} | null
>value : Union
>type : "a"
>variant : 2
if (value.type !== 'a') {
>value.type !== 'a' : boolean
>value.type : "a" | "b"
>value : Union
>type : "a" | "b"
>'a' : "a"
return null;
}
if (value.type && value.variant === 1) {
>value.type && value.variant === 1 : boolean
>value.type : "a"
>value : { type: "a"; variant: 1; } | { type: "a"; variant: 2; }
>type : "a"
>value.variant === 1 : boolean
>value.variant : 1 | 2
>value : { type: "a"; variant: 1; } | { type: "a"; variant: 2; }
>variant : 1 | 2
>1 : 1
return null;
}
return value;
>value : { type: "a"; variant: 2; }
}

View File

@ -226,3 +226,40 @@ declare const workingAgain: Record<string, any> | undefined | unknown;
isMyDiscriminatedUnion(working) && working.type === 'A' && working.aProp;
isMyDiscriminatedUnion(broken) && broken.type === 'A' && broken.aProp;
isMyDiscriminatedUnion(workingAgain) && workingAgain.type === 'A' && workingAgain.aProp;
// Repro from #56144
type Union =
| { type: 'a'; variant: 1 }
| { type: 'a'; variant: 2 }
| { type: 'b' };
function example1(value: Union): { type: 'a'; variant: 2 } | null {
if (value.type !== 'a') {
return null;
}
if (value.variant === 1) {
return null;
}
return value;
}
function example2(value: Union): { type: 'a'; variant: 2 } | null {
if (value.type !== 'a') {
return null;
}
if (value.type === 'a' && value.variant === 1) {
return null;
}
return value;
}
function example3(value: Union): { type: 'a'; variant: 2 } | null {
if (value.type !== 'a') {
return null;
}
if (value.type && value.variant === 1) {
return null;
}
return value;
}