TypeScript Version: 3.3.0-dev.20181206
Search Terms: interface, union
Code
type WithId = {
id: number
}
type WithNumber = {
number: number
}
type User = WithId | WithNumber
const user: User = {
id: 1,
number: 1
};
Expected behavior: user cannot have both id and number properties
Actual behavior: user can have both id and number properties. How can I achieve this?
Playground Link: http://www.typescriptlang.org/play/#src=type%20WithId%20%3D%20%7B%0D%0A%20%20%20%20id%3A%20number%0D%0A%7D%0D%0A%0D%0Atype%20WithNumber%20%3D%20%7B%0D%0A%20%20%20%20number%3A%20number%0D%0A%7D%0D%0A%0D%0Atype%20User%20%3D%20WithId%20%7C%20WithNumber%0D%0A%20%20%0D%0Aconst%20user%3A%20User%20%3D%20%7B%0D%0A%20%20%20%20id%3A%201%2C%0D%0A%20%20%20%20number%3A%201%0D%0A%7D%3B
Related Issues: https://github.com/Microsoft/TypeScript/issues/12745, https://github.com/Microsoft/TypeScript/issues/20060, https://github.com/Microsoft/TypeScript/issues/23535
I think this is a duplicate of #20863.
Currently a property is excess in a union type if the property is excess in all branches. In this instance no one property is excess in both branch.
I would be interested to get the opinion from the team as to whether it would be reasonable to have a partial solution that works for 1 or 2 levels deep (but correctly for intersection and unions). I think it would handle most cases people have, but would be tractable to implement and would hopefully retain the performance benefits EPC gives currently.
I will also take this shameless opportunity to plug my experimental work on exact types, which would solve this issue (#28749).
type Exact<T> = {| [K in keyof T]: T[K]; |}
type WithId = {
id: number
}
type WithNumber = {
number: number
}
type User = Exact<WithId> | Exact<WithNumber>;
const user: User = {
id: 1,
number: 1
};
/*
Type '{ id: number; number: number; }' is not assignable to type 'User'.
Type '{ id: number; number: number; }' is not assignable to type 'Exact<WithNumber>'.
Object literal may only specify known properties, and 'id' does not exist in type 'Exact<WithNumber>'. [2322]
*/
Probably worth noting that explicitly listing out all the properties you care about not being in excess with an optional undefined type will make the types work as you'd expect:
type WithId = {
id: number
number?: undefined,
}
type WithNumber = {
id?: undefined,
number: number
}
type User = WithId | WithNumber
const user: User = {
id: 1,
number: 1
};
these are the kinds of types we infer when you're working with fresh literals to accomplish the same thing.
But yes, mostly a duplicate of #20863
Here is a convoluted way to implement @weswigham's suggestion without changing the constituent types. (Disclaimer: I haven't really debugged these).
type WithId = {
id: number;
}
type WithNumber = {
number: number;
}
const user: UserExact = { // error
id: 1,
number: 1
};
type User = WithId | WithNumber;
type UserExact = Exactify<User>;
type Exactify<T> = Exactify2<T, T>;
type Exactify2<T, U> = T extends unknown ? ExactifyWorker<T, U> : never;
type ExactifyWorker<T, U> = OptionalK<T, MinusKeys<T, U> & string>;
type KeyMap<U> = U extends unknown ? keyof U : never;
type MinusKeys<T, U> = Exclude<KeyMap<U>, keyof T>;
type OptionalK<T, K extends string> = T & { [P in K]?: undefined };
This issue has been marked as a duplicate and has seen no activity in the last day. It has been closed automatic house-keeping purposes.
Most helpful comment
Probably worth noting that explicitly listing out all the properties you care about not being in excess with an optional undefined type will make the types work as you'd expect:
these are the kinds of types we infer when you're working with fresh literals to accomplish the same thing.
But yes, mostly a duplicate of #20863