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

keyof R is not assignable to extends keyof R #21884

Closed
artaommahe opened this issue Feb 12, 2018 · 5 comments
Closed

keyof R is not assignable to extends keyof R #21884

artaommahe opened this issue Feb 12, 2018 · 5 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@artaommahe
Copy link

artaommahe commented Feb 12, 2018

TypeScript Version: 2.7.1

Code

class Store<R> {
  protected events: { type: keyof R, data: R[keyof R] }[] = [];

  public on<A extends keyof R>(type: A): R[A] {
    return this.events
      .find(event => event.type === type)
      .data;
  }
}

interface IEvents {
  smth: string;
  smth2: number;
  smth3: boolean;
}

const store = new Store<IEvents>();

const smthEvent = store.on('smth');

Expected behavior:
No error, smthEvent has proper string type

Actual behavior:

Type 'R[keyof R]' is not assignable to type 'R[A]'.
  Type 'keyof R' is not assignable to type 'A'.

or with

public on(type: keyof R): R[keyof R]

smthEvent has parameter) data: string | number | boolean type instead if string

No error with TS 2.6.2

Playground Link:
https://www.typescriptlang.org/play/#src=class%20Store%3CR%3E%20%7B%0D%0A%20%20protected%20events%3A%20%7B%20type%3A%20keyof%20R%2C%20data%3A%20R%5Bkeyof%20R%5D%20%7D%5B%5D%20%3D%20%5B%5D%3B%0D%0A%0D%0A%20%20public%20on%3CA%20extends%20keyof%20R%3E(type%3A%20A)%3A%20R%5BA%5D%20%7B%0D%0A%20%20%20%20return%20this.events%0D%0A%20%20%20%20%20%20.find(event%20%3D%3E%20event.type%20%3D%3D%3D%20type)%0D%0A%20%20%20%20%20%20.data%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0Ainterface%20IEvents%20%7B%0D%0A%20%20smth%3A%20string%3B%0D%0A%20%20smth2%3A%20number%3B%0D%0A%20%20smth3%3A%20boolean%3B%0D%0A%7D%0D%0A%0D%0Aconst%20store%20%3D%20new%20Store%3CIEvents%3E()%3B%0D%0A%0D%0Aconst%20smthEvent%20%3D%20store.on('smth')%3B

@jcalz
Copy link
Contributor

jcalz commented Feb 12, 2018

Looks like things are working as intended here. R[keyof R] can be any of the property value types of R; there is no guarantee in { type: keyof R, data: R[keyof R] } that the type of data corresponds to the same key in type. Probably you should just use type assertions, if you're sure you set it up properly.

The type-safe way to get this done would probably require existential types (#14466) which TypeScript doesn't have built-in support for. For fun, here's how you'd need to do it today:

type Event<R, K extends keyof R> = { type: K, data: R[K] };
type SomeEvent<R> = <O>(f: <K extends keyof R>(e: Event<R, K>) => O) => O
type SomeParticularEvent<R, K extends keyof R> = <O>(f: (e: Event<R, K>) => O) => O

const narrowSomeEvent = <R, K extends keyof R>(k: K) =>
  (someEvent: SomeEvent<R>): someEvent is SomeParticularEvent<R, K> =>
    someEvent(e => e.type === k as string);

class Store<R> {
  protected events: SomeEvent<R>[] = [];

  public on<A extends keyof R>(type: A): R[A] | undefined {
    const theEvent = this.events.find(narrowSomeEvent(type))
    return theEvent ? theEvent(e => e.data) : undefined;
  }
}

Might not be worth it to you. Anyway, this doesn't look like a bug to me and perhaps this kind of question belongs on StackOverflow instead.

@DanielRosenwasser
Copy link
Member

The title has it backwards - it's that keyof R is not assignable to A which extends keyof R.

@DanielRosenwasser DanielRosenwasser added the Working as Intended The behavior described is the intended behavior; this is not a bug label Feb 12, 2018
@DanielRosenwasser DanielRosenwasser changed the title extends keyof R is not assignable to keyof R keyof R is not assignable to extends keyof R Feb 12, 2018
@DanielRosenwasser
Copy link
Member

You could also use a type predicate function to convey the narrowing to find:

class Store<R> {
  protected events: { type: keyof R, data: R[keyof R] }[] = [];

  public on<A extends keyof R>(type: A): R[A] {
    return this.events
      .find((event): event is { type: A, data: R[A] } => event.type === type)!
      .data;
  }
}

interface IEvents {
  smth: string;
  smth2: number;
  smth3: boolean;
}

const store = new Store<IEvents>();

const smthEvent = store.on('smth');

@artaommahe
Copy link
Author

thx for solutions

  • why has this been broken from 2.6.2 to 2.7?
  • will be there more obvious solution for such cases?

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants