Closed
Description
TypeScript Version: 3.5.1
Search Terms: is not assignable to, discriminated union
Code
export const enum PatternKind {
Known = "Known",
Unknown = "Unknown"
}
export type Pattern1 = {
kind: PatternKind.Known;
name: "Pattern1";
pattern1_attribute: string;
};
export type Pattern2 = {
kind: PatternKind.Known;
name: "Pattern2";
pattern2_attribute: string;
};
export type KnownPattern = Pattern1 | Pattern2;
export type UnknownPattern = {
kind: PatternKind.Unknown;
name: string;
};
export type Pattern = KnownPattern | UnknownPattern;
function getPattern(): Pattern {
// TODO: implement
throw new Error("");
}
const pattern: Pattern = getPattern();
if (pattern.kind === PatternKind.Known) {
if (pattern.name === "Pattern1") {
// Error, pattern1_attribute is not accessible
pattern.pattern1_attribute = "foo";
// Error, pattern is not assignable to Pattern1
let pattern1: Pattern1 = pattern;
pattern1 = pattern1;
}
}
if (pattern.kind === PatternKind.Known) {
// But for some reason, storing pattern in a KnownPattern variable first makes it work, even
// though after the "pattern.kind === PatternKind.Known" above, the compiler is already
// recognizing pattern to be a KnownPattern (which is why pattern can be assigned to the
// KnownPattern variable by the way)
const knownPattern: KnownPattern = pattern;
if (knownPattern.name === "Pattern1") {
// No error, pattern1_attribute is accessible
knownPattern.pattern1_attribute = "foo";
// No error, knownPattern is assignable to Pattern1
let pattern1: Pattern1 = knownPattern;
pattern1 = pattern1;
}
}
Expected behavior: no compiler errors
Actual behavior: compiler errors
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
[-]Invalid assignments even after all necessary discriminated union checks are made[/-][+]"is not assignable to" even after all necessary discriminated union checks are made[/+]jack-williams commentedon Jul 13, 2019
This is a design limitation caused by the fact that discriminant narrowing only considers the declared type, rather than the current type of the identifier.
In your erroneous example you are relying on the composition of two discriminant narrowing's (
pattern.kind
and thenpattern.name
). In the other example you store the assigned the narrowed variable to a new identifier such that its declared type is the narrowed intermediate type (which is why that works).Ideally this would just work as expected but I think the perf regressions would be significant.
SamB commentedon Jul 20, 2019
And we don't even need the type annotation on the new variable:
Also, could you give a short description of exactly what would cause the performance regressions?
Like, would it lead to a lot of second-, third-, etc. guessing during type inference? What of and/or where would it happen?
jack-williams commentedon Jul 22, 2019
Synthesising the type for a property on a union, such as
pattern.kind
, is expensive and happens at each discriminant check. Caching makes this bearable, but you need to make sure that you consistently hit the cache - TypeScript does this by always narrowing from the declared type.RyanCavanaugh commentedon Jul 31, 2019
Tracked by #30557
typescript-bot commentedon Aug 2, 2019
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.