Skip to content

Commit

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

2. Handle recursively applying this constraint to nested methods for
   the `EmberMethodsOf` type, which has additional complexity to handle
   Ember's string-based dispatch. Combined with (1), this actually
   *simplifies* the types, eliminating one layer of conditional types.
  • Loading branch information
chriskrycho committed Nov 9, 2022
1 parent 720c302 commit 072830a
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 14 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
27 changes: 17 additions & 10 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,16 +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 :
never;
// For a basic method, we can just use the direct accessor.
M extends AnyMethod<T> ? Parameters<M> :
M extends MethodNamesOf<T> ? Parameters<MethodsOf<T>[M]> : 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 :
never;
M extends AnyMethod<T> ? ReturnType<M> :
M extends MethodNamesOf<T> ? ReturnType<MethodsOf<T>[M]> : never;

/**
* A type utility for Ember's common name-of-object-on-target-or-function
Expand Down

0 comments on commit 072830a

Please sign in to comment.