Skip to content

keyof generic intersection  #27094

@aleclarson

Description

@aleclarson

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

Activity

RyanCavanaugh

RyanCavanaugh commented on Sep 24, 2018

@RyanCavanaugh
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

aleclarson commented on Sep 24, 2018

@aleclarson
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

RyanCavanaugh commented on Sep 24, 2018

@RyanCavanaugh
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.

added
Design LimitationConstraints of the existing architecture prevent this from being fixed
on Sep 24, 2018
aleclarson

aleclarson commented on Sep 24, 2018

@aleclarson
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

RyanCavanaugh commented on Sep 24, 2018

@RyanCavanaugh
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

aleclarson commented on Jan 14, 2019

@aleclarson
Author

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @aleclarson@RyanCavanaugh

        Issue actions

          keyof generic intersection · Issue #27094 · microsoft/TypeScript