Skip to content

Commit

Permalink
Fix type distributivity in Glimmer Component Signature
Browse files Browse the repository at this point in the history
When using a `keyof` type to check whether the type parameter for
Glimmer Component is a `Signature` or the classic `Args`-only type, if
we do not force TS to distribute over union types, it resolves the
`keyof` check for union types with no shared members as `never`, and
`never extends <anything>` is always true. This in turn meant that for
all such unions, as well as for cases where users were providing generic
types which could then be further extended in their own subclasses.

Accordingly, introduce the standard technique TypeScript provides for
opting into distributivity: conditional types are documented to support
exactly this.

(cherry picked from commit 499d266)
  • Loading branch information
chriskrycho committed Apr 1, 2022
1 parent 872c3c2 commit 6fa1ace
Showing 1 changed file with 19 additions and 12 deletions.
31 changes: 19 additions & 12 deletions packages/@glimmer/component/addon/-private/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,7 @@ type ArgsFor<S> = 'Args' extends keyof S
: { Named: S['Args']; Positional: [] }
: { Named: EmptyObject; Positional: [] };

/**
* Given any allowed shorthand form of a signature, desugars it to its full
* expanded type.
*
* @internal This is only exported so we can avoid duplicating it in
* [Glint](https://github.com/typed-ember/glint) or other such tooling. It is
* *not* intended for public usage, and the specific mechanics it uses may
* change at any time. Although the signature produced by is part of Glimmer's
* public API the existence and mechanics of this specific symbol are *not*,
* so ***DO NOT RELY ON IT***.
*/
export type ExpandSignature<T> = {
type _ExpandSignature<T> = {
Element: GetOrElse<T, 'Element', null>;
Args: keyof T extends 'Args' | 'Element' | 'Blocks' // Is this a `Signature`?
? ArgsFor<T> // Then use `Signature` args
Expand All @@ -84,6 +73,24 @@ export type ExpandSignature<T> = {
}
: EmptyObject;
};
/**
* Given any allowed shorthand form of a signature, desugars it to its full
* expanded type.
*
* @internal This is only exported so we can avoid duplicating it in
* [Glint](https://github.com/typed-ember/glint) or other such tooling. It is
* *not* intended for public usage, and the specific mechanics it uses may
* change at any time. Although the signature produced by is part of Glimmer's
* public API the existence and mechanics of this specific symbol are *not*,
* so ***DO NOT RELY ON IT***.
*/
// The conditional type here is because TS applies conditional types
// distributively. This means that for union types, checks like `keyof T` get
// all the keys from all elements of the union, instead of ending up as `never`
// and then always falling into the `Signature` path instead of falling back to
// the legacy args handling path.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ExpandSignature<T> = T extends any ? _ExpandSignature<T> : never;

/**
* @internal we use this type for convenience internally; inference means users
Expand Down

0 comments on commit 6fa1ace

Please sign in to comment.