Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use just one of interfaces with different properties #28879

Closed
sorryformystupidquestions opened this issue Dec 6, 2018 · 4 comments
Closed
Labels
Duplicate An existing issue was already created

Comments

@sorryformystupidquestions
Copy link

sorryformystupidquestions commented Dec 6, 2018

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: #12745, #20060, #23535

@sorryformystupidquestions sorryformystupidquestions changed the title Cannot use just one of interfaces with different properties Now to just one of interfaces with different properties Dec 6, 2018
@sorryformystupidquestions sorryformystupidquestions changed the title Now to just one of interfaces with different properties Now to use just one of interfaces with different properties Dec 6, 2018
@jack-williams
Copy link
Collaborator

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]
*/

@sorryformystupidquestions sorryformystupidquestions changed the title Now to use just one of interfaces with different properties How to use just one of interfaces with different properties Dec 6, 2018
@weswigham
Copy link
Member

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

@weswigham weswigham added the Duplicate An existing issue was already created label Dec 6, 2018
@jack-williams
Copy link
Collaborator

jack-williams commented Dec 6, 2018

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 };

@typescript-bot
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants