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

Cannot extract/filter property keys from this. #29413

Closed
superamadeus opened this issue Jan 14, 2019 · 6 comments
Closed

Cannot extract/filter property keys from this. #29413

superamadeus opened this issue Jan 14, 2019 · 6 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@superamadeus
Copy link

TypeScript Version: 3.3.0-dev.201xxxxx (tested with typescript@next -v: 3.3.0-dev.20190112)

Search Terms: keyof this, mapped this type, filter this properties, filter this property keys

Code

/**
 * Get the property keys of properties on T which are of type U.
 */
type ExtractPropKeysOfType<T, U> = {
  [k in keyof T]: T[k] extends U ? k : never
}[keyof T];

/**
 * Pick the properties on T which are of type U.
 */
type PickPropsOfType<T, U> = Pick<T, ExtractPropKeysOfType<T, U>>;

function pickNumericProps<T>(t: T): PickPropsOfType<T, number> {
  return {} as any;
}

class Example {
  public a!: number;
  public b!: string;
  public c!: 11;

  public ownNumericProps(): PickPropsOfType<this, number> {
    return pickNumericProps(this);
  }

  public consumerMethod(): void {
    type OwnNumericPropKeys = ExtractPropKeysOfType<this, number>; // it seems that TS cannot resolve this type when applied to `this`.

    pickNumericProps(this).a; // ERROR: Property 'a' does not exist on type 'Pick<this, { [k in keyof this]: this[k] extends number ? k : never; }[keyof this]>'.
    this.ownNumericProps().a; // ERROR: Property 'a' does not exist on type 'Pick<this, { [k in keyof this]: this[k] extends number ? k : never; }[keyof this]>'.
  }
}

const example = new Example();
example.ownNumericProps().a; // works as expected
pickNumericProps(example).a; // works as expected

Expected behavior:
The type of pickNumericProps(this) should be { a: number, c: 11 }.

Actual behavior:
The type of pickNumericProps(this) is unresolved (? not sure how to describe it).

Playground Link: Playground link

Related Issues:
Couldn't find any

@RyanCavanaugh
Copy link
Member

The type of pickNumericProps(this) should be { a: number, c: 11 }.

That step of logic isn't correct. this could be, for example,

class Example2 extends Example {
  a: 10;
  b: number;
}

which means pickNumericProps(this) would "actually" be { a: 10, b: number, c: 11 }.

@superamadeus
Copy link
Author

@RyanCavanaugh Correct. That's the desired behavior I'm looking for when extending the base class. Still, the type seems unable to resolve itself. Is there a term for that?

@weswigham
Copy link
Member

weswigham commented Jan 15, 2019

The type is resolved - it's generic, since this could be any arbitrary subtype, so any implementation code you write needs to handle any arbitrary subtype.

@weswigham weswigham added the Question An issue which isn't directly actionable in code label Jan 15, 2019
@superamadeus
Copy link
Author

@weswigham Well, shouldn't it be safe to assume that in my example the type of pickNumericProps(this) should extend { a: number, c: 11 }? So accessing a or c should not throw an error?

@Nathan-Fenner
Copy link
Contributor

Nathan-Fenner commented Jan 18, 2019

I think this is essentially the same as #29225.

  • this types are really generic parameter types under-the-hood
  • this has a constraint: this extends {a: number, c: 11}
  • but constraints are (at least currently, due to a design limitation) ignored when simplifying conditional types:
type Example<T extends string> = T extends string ? "yes" : "no";

This example is "stuck" until you actually instantiate it with a particular T.

The result in your case is that you get a "stuck" type that cannot be simplified, and therefore the compiler can't determine that it actually has the key you're expecting (because the conditional type that contains the this is completely opaque).

The reason things are this way is because of the following example:

type Example2<T extends {x: any}> = T extends {x: string} ? "yes" : "no"

The problem here is that if T = {x: number}, then T extends {x: any} but the conditional should evaluate to no, even though {x: any} is a subtype of {x: string}. This is basically brought about by any making the type system unsound, but I think it may possibly be extended toward other cases as well.

I think it may be possible to lift this design limitation, but only in some cases (where the constraint-bound can be fully simplified with no "stuck" mapped or conditional types, where no "any" types occur, ...).

@superamadeus
Copy link
Author

@Nathan-Fenner I appreciate the reference and explanation.

I'd love to see some of the more straightforward constraints handled more thoroughly, but I understand why the design limitation is there.

I mean, I'd really love to see that. I have been working on an api that depends on being able to conditionally map against keyof this but for now instead of omitting properties I'm just mapping them to never, so I end up with this: PickNumericProps<this>; // { a: number, b: never, c: 11 }.

@superamadeus superamadeus changed the title Cannot extract/filter property keys from *this*. Cannot extract/filter property keys from this. Jan 18, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

5 participants