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 generic intersection #27094

Closed
aleclarson opened this issue Sep 14, 2018 · 6 comments
Closed

keyof generic intersection #27094

aleclarson opened this issue Sep 14, 2018 · 6 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@aleclarson
Copy link

TypeScript Version: 3.1.0-dev.201xxxxx

Search Terms:

  • keyof generic intersection

Code

Helper types:

// Extract keys whose values match a condition
type Filter<T, Cond> = {
  [K in keyof T]: T[K] extends Cond ? K : never
}[keyof T]

type Fun = (...args: any[]) => any

// Extract method names from an object type
type Methods<T> = Filter<T, Fun>

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Prefer<T, U> = U & Omit<T, (keyof T) & (keyof U)>

Implementation:

interface Events {
  foo(): void
  bar: number
}

class Foo<T, U = T & Events> {
  hey(arg: Methods<U>): void { }
  duk(arg: Filter<U, Fun>): void { }
  wat(arg: Methods<T & Events>): void { }
  how(arg: Filter<T & Events, Fun>): void { }
  foo(arg: Methods<Prefer<T, Events>>): void { }
  who(arg: Filter<Prefer<T, Events>, Fun>): void { }
  ugh(arg: keyof U): void { }
  bar(arg: keyof Prefer<T, Events>): void { }
  tau(arg: keyof (T & Events)): void { }

  test() {
    this.hey('foo') // ✕
    this.duk('foo') // ✕
    this.wat('foo') // ✕
    this.how('foo') // ✕
    this.foo('foo') // ✕
    this.who('foo') // ✕
    this.ugh('foo') // ✕
    this.bar('foo') // OK ("foo" | "bar" | Exclude<keyof T, ("foo" & keyof T) | ("bar" & keyof T)>)
    this.tau('foo') // OK (keyof T | "foo" | "bar")
  }
}

Expected behavior:
No errors

Actual behavior:
Unexpected errors

Playground Link: https://goo.gl/X4KcBN

Related Issues: #18538 #26409

@RyanCavanaugh
Copy link
Member

It looks like you're misapprehending what = does - in <T, U = T & Events>, U has a default of T & Events but is in no way constrained, e.g. new Foo<string, boolean> is OK and you shouldn't expect an arbitrary U inside your class to have Events keys.

@aleclarson
Copy link
Author

@RyanCavanaugh Yeah, sorry about that. But adding extends Events before the = operator only fixes the last error. Why are the others still errors?

@RyanCavanaugh
Copy link
Member

It's not correct to eagerly try to evaluate a conditional type when one of its inputs is generic - certain instantations of T actually would change what the correct outputs of Methods are.

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Sep 24, 2018
@aleclarson
Copy link
Author

You'd think Methods<U> could be partially evaluated to 'foo' | Methods<T>. Why aren't the non-generic parts of an intersection able to be evaluated?

@RyanCavanaugh
Copy link
Member

This presumes to know that nothing from Methods<T> could intersect in something into the foo property to cause it to become unassignable to (...args: any[]) => any. We know from inspection that this can't happen because you can't add anything to (...args: any[]) => any to cause it to become an incompatible target, but the compiler can't reason about types on a meta-level like that.

@aleclarson
Copy link
Author

aleclarson commented Jan 14, 2019

As of TypeScript v3.2, only the first 2 function calls cause compile errors. 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

2 participants