Skip to content

Commit

Permalink
Rework MethodsOf type utilities for robustness
Browse files Browse the repository at this point in the history
Using the keyof constraint feature introduced in 4.1, avoid ever introducing
types with properties whose type is `never` for the non-method fields.

NOTE: this commit introduces the same fix in Ember's own internals and in the
preview types, but in the preview types we need a small additional bit of
plumbing to handle recursively applying this constraint to nested methods for
the `EmberMethodsOf` type, which has additional complexity to handle Ember's
string-based dispatch.
  • Loading branch information
chriskrycho authored and wagenet committed Nov 9, 2022
1 parent 720c302 commit 02c9eed
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 12 deletions.
16 changes: 12 additions & 4 deletions packages/@ember/-internals/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
export type AnyFn = (...args: any[]) => any;

export type MethodNamesOf<T> = {
[K in keyof T]: T[K] extends AnyFn ? K : never;
}[keyof T];
// The formatting here is designed to help make this type actually be
// comprehensible to mortals, including the mortals who came up with it.
// prettier-ignore
export type MethodsOf<T> = {
// This `keyof` check is the thing which gives us *only* these keys, and no
// `foo: never` appears in the final type.
[K in keyof T as T[K] extends AnyFn ? K : never]:
// While this makes sure the resolved type only has `AnyFn` in it, so that
// the resulting type is known to be only function types.
T[K] extends AnyFn ? T[K] : never;
};

export type MethodsOf<O> = Pick<O, MethodNamesOf<O>>;
export type MethodNamesOf<T> = keyof MethodsOf<T>;

export type MethodParams<T, M extends MethodNamesOf<T>> = Parameters<MethodsOf<T>[M]>;

Expand Down
24 changes: 16 additions & 8 deletions types/preview/ember/-private/type-utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ declare module 'ember/-private/type-utils' {

export type AnyMethod<Target> = (this: Target, ...args: any[]) => unknown;

export type MethodNamesOf<O> = {
[K in keyof O]: O[K] extends AnyFn ? K : never;
}[keyof O];
// The formatting here is designed to help make this type actually be
// comprehensible to mortals, including the mortals who came up with it.
// prettier-ignore
export type MethodsOf<T> = {
// This `keyof` check is the thing which gives us *only* these keys, and no
// `foo: never` appears in the final type.
[K in keyof T as T[K] extends AnyFn ? K : never]:
// While this makes sure the resolved type only has `AnyFn` in it, so that
// the resulting type is known to be only function types.
T[K] extends AnyFn ? T[K] : never;
};

export type MethodsOf<O> = Pick<O, MethodNamesOf<O>>;
export type MethodNamesOf<T> = keyof MethodsOf<T>;

export type MethodParams<T, M extends MethodNamesOf<T>> = Parameters<MethodsOf<T>[M]>;

Expand All @@ -20,15 +28,15 @@ declare module 'ember/-private/type-utils' {
// prettier-ignore
/** Get the return value of a method string name or a function. */
export type EmberMethodParams<T, M extends EmberMethod<T>> =
M extends AnyMethod<T> ? Parameters<M> :
M extends keyof T ? T[M] extends AnyMethod<T> ? Parameters<MethodsOf<T>[M]> : never :
M extends AnyMethod<T> ? Parameters<M> :
M extends keyof T ? T[M] extends AnyMethod<T> ? Parameters<MethodsOf<T>[M]> : never :
never;

// prettier-ignore
/** Get the return value of a method string name or a function. */
export type EmberMethodReturn<T, M extends EmberMethod<T>> =
M extends AnyMethod<T> ? ReturnType<M> :
M extends keyof T ? T[M] extends AnyMethod<T> ? ReturnType<MethodsOf<T>[M]> : never :
M extends AnyMethod<T> ? ReturnType<M> :
M extends keyof T ? T[M] extends AnyMethod<T> ? ReturnType<MethodsOf<T>[M]> : never :
never;

/**
Expand Down

0 comments on commit 02c9eed

Please sign in to comment.