Skip to content

Commit ca65145

Browse files
authored
Merge pull request #54 from typed-ember/simplify-resolution
2 parents 33ef9cc + 02058a5 commit ca65145

26 files changed

+263
-180
lines changed

packages/environment-ember-loose/types/intrinsics/action.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Invokable } from '@glint/template/-private/resolution';
2+
13
export type ActionNamedArgs<T> = {
24
value?: keyof T;
35
};
@@ -8,7 +10,7 @@ export type ActionResult<T, Args extends ActionNamedArgs<T>> = undefined extends
810
? T[Args['value']]
911
: T;
1012

11-
export interface ActionKeyword {
13+
export type ActionKeyword = Invokable<{
1214
<Ret, Args extends ActionNamedArgs<Ret>, Params extends unknown[]>(
1315
args: Args,
1416
f: (...rest: Params) => Ret
@@ -42,4 +44,4 @@ export interface ActionKeyword {
4244
(args: ActionNamedArgs<Record<string, unknown>>, action: string, ...rest: unknown[]): (
4345
...rest: unknown[]
4446
) => unknown;
45-
}
47+
}>;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { AcceptsBlocks, NoNamedArgs } from '@glint/template/-private';
2+
import { Invokable } from '@glint/template/-private/resolution';
23

3-
export interface EachInKeyword {
4+
export type EachInKeyword = Invokable<{
45
<T>(args: NoNamedArgs, object: T): AcceptsBlocks<{
56
default: [key: keyof T, value: T[keyof T]];
67
}>;
7-
}
8+
}>;

packages/environment-ember-loose/types/intrinsics/link-to.d.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { AcceptsBlocks, NoNamedArgs } from '@glint/template/-private';
2+
import { Invokable } from '@glint/template/-private/resolution';
23

3-
export interface LinkToKeyword {
4+
export type LinkToKeyword = Invokable<{
45
(args: NoNamedArgs, route: string, ...params: unknown[]): AcceptsBlocks<{
56
default?: [];
67
}>;
7-
}
8+
}>;
89

910
export interface LinkToArgs {
1011
route: string;
@@ -17,6 +18,6 @@ export interface LinkToArgs {
1718
query?: Record<string, unknown>;
1819
}
1920

20-
export interface LinkToComponent {
21+
export type LinkToComponent = Invokable<{
2122
(args: LinkToArgs): AcceptsBlocks<{ default: [] }>;
22-
}
23+
}>;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NoNamedArgs } from '@glint/template/-private';
2+
import { Invokable } from '@glint/template/-private/resolution';
23

3-
export interface LogKeyword {
4+
export type LogKeyword = Invokable<{
45
(args: NoNamedArgs, ...params: unknown[]): void;
5-
}
6+
}>;

packages/environment-ember-loose/types/signatures.d.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ import '@ember/component/helper';
44
import { ModifierArgs } from 'ember-modifier';
55

66
import { NoYields, NoNamedArgs, CreatesModifier } from '@glint/template/-private';
7-
import { ContextType, SignatureType } from '@glint/template/-private';
7+
import { ContextType, Invoke } from '@glint/template/-private';
88
import { TemplateContext, AcceptsBlocks } from '@glint/template/-private';
9+
import { Invokable } from '@glint/template/-private/resolution';
910

1011
declare module '@glimmer/component' {
1112
export default interface Component<Args, Yields = NoYields> {
12-
[SignatureType]: (args: Args) => AcceptsBlocks<Yields>;
13+
[Invoke]: (args: Args) => AcceptsBlocks<Yields>;
1314
[ContextType]: TemplateContext<this, Args, Yields>;
1415
}
1516
}
1617

1718
declare module '@ember/component' {
1819
export default interface Component<Args = NoNamedArgs, Yields = NoYields> {
19-
[SignatureType]: (args: Args) => AcceptsBlocks<Yields>;
20+
[Invoke]: (args: Args) => AcceptsBlocks<Yields>;
2021
[ContextType]: TemplateContext<this, Args, Yields>;
2122
}
2223
}
@@ -28,17 +29,17 @@ declare module '@ember/component/helper' {
2829
Return = unknown
2930
> {
3031
compute(params: Positional, hash: Named): Return;
31-
[SignatureType]: (named: Named, ...positional: Positional) => Return;
32+
[Invoke]: (named: Named, ...positional: Positional) => Return;
3233
}
3334

3435
export function helper<Positional extends unknown[] = [], Named = NoNamedArgs, Return = unknown>(
3536
fn: (params: Positional, hash: Named) => Return
36-
): (named: Named, ...positional: Positional) => Return;
37+
): new () => Invokable<(named: Named, ...positional: Positional) => Return>;
3738
}
3839

3940
declare module 'ember-modifier' {
4041
export default interface ClassBasedModifier<Args extends ModifierArgs = ModifierArgs> {
41-
[SignatureType]: (args: Args['named'], ...positional: Args['positional']) => CreatesModifier;
42+
[Invoke]: (args: Args['named'], ...positional: Args['positional']) => CreatesModifier;
4243
}
4344

4445
export function modifier<
@@ -47,5 +48,5 @@ declare module 'ember-modifier' {
4748
Named = NoNamedArgs
4849
>(
4950
fn: (element: El, positional: Positional, named: Named) => unknown
50-
): (named: Named, ...positional: Positional) => CreatesModifier;
51+
): new () => Invokable<(named: Named, ...positional: Positional) => CreatesModifier>;
5152
}

packages/environment-glimmerx/__tests__/globals.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { expectTypeOf } from 'expect-type';
2-
import DebuggerKeyword from '@glint/template/-private/keywords/debugger';
3-
import EachKeyword from '@glint/template/-private/keywords/each';
4-
import HasBlockKeyword from '@glint/template/-private/keywords/has-block';
5-
import HasBlockParamsKeyword from '@glint/template/-private/keywords/has-block-params';
6-
import InElementKeyword from '@glint/template/-private/keywords/in-element';
7-
import LetKeyword from '@glint/template/-private/keywords/let';
8-
import WithKeyword from '@glint/template/-private/keywords/with';
2+
import { DebuggerKeyword } from '@glint/template/-private/keywords/debugger';
3+
import { EachKeyword } from '@glint/template/-private/keywords/each';
4+
import { HasBlockKeyword } from '@glint/template/-private/keywords/has-block';
5+
import { HasBlockParamsKeyword } from '@glint/template/-private/keywords/has-block-params';
6+
import { InElementKeyword } from '@glint/template/-private/keywords/in-element';
7+
import { LetKeyword } from '@glint/template/-private/keywords/let';
8+
import { WithKeyword } from '@glint/template/-private/keywords/with';
99

1010
import { Globals } from '@glint/environment-glimmerx/types';
1111

packages/environment-glimmerx/__tests__/helper.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { helper, fn as fnDefinition } from '@glimmerx/helper';
21
import { resolve } from '@glint/environment-glimmerx/types';
2+
import { helper, fn2 as fnDefinition } from '@glimmerx/helper';
33
import { expectTypeOf } from 'expect-type';
44
import { NoNamedArgs } from '@glint/template/-private';
55

@@ -69,3 +69,42 @@ import { NoNamedArgs } from '@glint/template/-private';
6969
expectTypeOf(repeat({ word: 'hi' })).toEqualTypeOf<Array<string>>();
7070
expectTypeOf(repeat({ word: 'hi', count: 3 })).toEqualTypeOf<Array<string>>();
7171
}
72+
73+
// Custom helper: bare function
74+
{
75+
let definition = <T>(item: T, count?: number): Array<T> => {
76+
return Array.from({ length: count ?? 2 }, () => item);
77+
};
78+
79+
let repeat = resolve(definition);
80+
81+
expectTypeOf(repeat).toEqualTypeOf<<T>(args: NoNamedArgs, item: T, count?: number) => Array<T>>();
82+
83+
// @ts-expect-error: unexpected named arg
84+
repeat({ word: 'hi' }, 123, 12);
85+
86+
// @ts-expect-error: missing required positional arg
87+
repeat({});
88+
89+
// @ts-expect-error: extra positional arg
90+
repeat({}, 'hi', 12, 'ok');
91+
92+
expectTypeOf(repeat({}, 123)).toEqualTypeOf<Array<number>>();
93+
expectTypeOf(repeat({}, 'hi', 5)).toEqualTypeOf<Array<string>>();
94+
}
95+
96+
// Custom helper: type guard
97+
{
98+
let definition = (arg: unknown): arg is string => typeof arg === 'string';
99+
100+
let isString = resolve(definition);
101+
102+
expectTypeOf(isString).toEqualTypeOf<(args: NoNamedArgs, arg: unknown) => arg is string>();
103+
104+
let x = 'hi' as string | number;
105+
if (isString({}, x)) {
106+
expectTypeOf(x).toEqualTypeOf<string>();
107+
} else {
108+
expectTypeOf(x).toEqualTypeOf<number>();
109+
}
110+
}

packages/environment-glimmerx/__tests__/modifier.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { on as onDefinition } from '@glimmerx/modifier';
1+
import { on2 as onDefinition } from '@glimmerx/modifier';
22
import { resolve, invokeModifier } from '@glint/environment-glimmerx/types';
33
import { expectTypeOf } from 'expect-type';
44

packages/environment-glimmerx/types/index.d.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,47 @@ import './signatures';
22

33
export * from '@glint/template';
44
export { Globals } from './globals';
5+
6+
/*
7+
* Since GlimmerX supports using bare functions as helpers that only
8+
* accept positional parameters, we can't just use the core definitions
9+
* of `resolve` and `resolveOrReturn`. Instead we need to export versions
10+
* with additional overloads at the correct point in the chain to handle
11+
* those functions correctly.
12+
*
13+
* In order, we have:
14+
* - explicit `Invokable<T>`
15+
* - constructor for an `Invokable<T>`
16+
* - a plain type guard
17+
* - any other kind of plain function
18+
*
19+
* And in the case of `resolveOrReturn`, a final fallback for any other
20+
* type of value. See the upstream definitions in `@glint/template` for
21+
* further details on resolution.
22+
*/
23+
24+
import { Invokable, Invoke } from '@glint/template/-private/resolution';
25+
import { NoNamedArgs } from '@glint/template/-private/signature';
26+
27+
export declare function resolve<T extends Invokable>(item: T): T[typeof Invoke];
28+
export declare function resolve<Args extends unknown[], Instance extends Invokable>(
29+
item: new (...args: Args) => Instance
30+
): (...args: Parameters<Instance[typeof Invoke]>) => ReturnType<Instance[typeof Invoke]>;
31+
export declare function resolve<Value, Args extends unknown[], T extends Value>(
32+
item: (value: Value, ...args: Args) => value is T
33+
): (named: NoNamedArgs, value: Value, ...args: Args) => value is T;
34+
export declare function resolve<Args extends unknown[], T>(
35+
item: (...args: Args) => T
36+
): (named: NoNamedArgs, ...args: Args) => T;
37+
38+
export declare function resolveOrReturn<T extends Invokable>(item: T): T[typeof Invoke];
39+
export declare function resolveOrReturn<Args extends unknown[], Instance extends Invokable>(
40+
item: new (...args: Args) => Instance
41+
): (...args: Parameters<Instance[typeof Invoke]>) => ReturnType<Instance[typeof Invoke]>;
42+
export declare function resolveOrReturn<Value, Args extends unknown[], T extends Value>(
43+
item: (value: Value, ...args: Args) => value is T
44+
): (named: NoNamedArgs, value: Value, ...args: Args) => value is T;
45+
export declare function resolveOrReturn<Args extends unknown[], T>(
46+
item: (...args: Args) => T
47+
): (named: NoNamedArgs, ...args: Args) => T;
48+
export declare function resolveOrReturn<T>(item: T): (args: NoNamedArgs) => T;
Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,61 @@
11
import { NoNamedArgs, CreatesModifier, NoYields } from '@glint/template/-private';
2-
import { ContextType, SignatureType } from '@glint/template/-private';
2+
import { ContextType, Invoke } from '@glint/template/-private';
33
import { TemplateContext, AcceptsBlocks } from '@glint/template/-private';
4+
import { Invokable } from '@glint/template/-private/resolution';
45

56
declare module '@glimmerx/component' {
67
export default interface Component<Args, Yields = NoYields> {
7-
[SignatureType]: (args: Args) => AcceptsBlocks<Yields>;
8+
[Invoke]: (args: Args) => AcceptsBlocks<Yields>;
89
[ContextType]: TemplateContext<this, Args, Yields>;
910
}
1011
}
1112

1213
declare module '@glimmerx/modifier' {
13-
export function on<Name extends keyof HTMLElementEventMap>(
14+
// TODO: this is really bringing https://github.com/typed-ember/glint/issues/25 to a head
15+
// We need to stop trying to augment existing types and instead setup re-exports with our
16+
// own proper types.
17+
export const on2: Invokable<<Name extends keyof HTMLElementEventMap>(
1418
args: NoNamedArgs,
1519
name: Name,
1620
callback: (event: HTMLElementEventMap[Name]) => void
17-
): CreatesModifier;
21+
) => CreatesModifier>;
1822
}
1923

2024
declare module '@glimmerx/helper' {
2125
export function helper<Result, Named = NoNamedArgs, Positional extends unknown[] = []>(
2226
fn: (positional: Positional, named: Named) => Result
23-
): (args: Named, ...positional: Positional) => Result;
27+
): new () => Invokable<(args: Named, ...positional: Positional) => Result>;
2428

25-
export function fn<Ret, Args extends unknown[]>(
26-
args: NoNamedArgs,
27-
f: (...rest: Args) => Ret
28-
): (...rest: Args) => Ret;
29-
export function fn<A, Ret, Args extends unknown[]>(
30-
args: NoNamedArgs,
31-
f: (a: A, ...rest: Args) => Ret,
32-
a: A
33-
): (...rest: Args) => Ret;
34-
export function fn<A, B, Ret, Args extends unknown[]>(
35-
args: NoNamedArgs,
36-
f: (a: A, b: B, ...rest: Args) => Ret,
37-
a: A,
38-
b: B
39-
): (...rest: Args) => Ret;
40-
export function fn<A, B, C, Ret, Args extends unknown[]>(
41-
args: NoNamedArgs,
42-
f: (a: A, b: B, c: C, ...rest: Args) => Ret,
43-
a: A,
44-
b: B,
45-
c: C
46-
): (...rest: Args) => Ret;
47-
export function fn<A, B, C, D, Ret, Args extends unknown[]>(
48-
args: NoNamedArgs,
49-
f: (a: A, b: B, c: C, d: D, ...rest: Args) => Ret,
50-
a: A,
51-
b: B,
52-
c: C,
53-
d: D
54-
): (...rest: Args) => Ret;
29+
// TODO: this is really bringing https://github.com/typed-ember/glint/issues/25 to a head
30+
// We need to stop trying to augment existing types and instead setup re-exports with our
31+
// own proper types.
32+
export const fn2: Invokable<{
33+
<Ret, Args extends unknown[]>(args: NoNamedArgs, f: (...rest: Args) => Ret): (
34+
...rest: Args
35+
) => Ret;
36+
<A, Ret, Args extends unknown[]>(args: NoNamedArgs, f: (a: A, ...rest: Args) => Ret, a: A): (
37+
...rest: Args
38+
) => Ret;
39+
<A, B, Ret, Args extends unknown[]>(
40+
args: NoNamedArgs,
41+
f: (a: A, b: B, ...rest: Args) => Ret,
42+
a: A,
43+
b: B
44+
): (...rest: Args) => Ret;
45+
<A, B, C, Ret, Args extends unknown[]>(
46+
args: NoNamedArgs,
47+
f: (a: A, b: B, c: C, ...rest: Args) => Ret,
48+
a: A,
49+
b: B,
50+
c: C
51+
): (...rest: Args) => Ret;
52+
<A, B, C, D, Ret, Args extends unknown[]>(
53+
args: NoNamedArgs,
54+
f: (a: A, b: B, c: C, d: D, ...rest: Args) => Ret,
55+
a: A,
56+
b: B,
57+
c: C,
58+
d: D
59+
): (...rest: Args) => Ret;
60+
}>;
5561
}

packages/template/-private/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { AcceptsBlocks, CreatesModifier, NoNamedArgs, NoYields } from './signature';
2-
export { SignatureType, ContextType } from './resolution';
2+
export { Invoke, ContextType } from './resolution';
33
export { TemplateContext } from './template';
Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
import { AcceptsBlocks, AnyBlocks } from '../signature';
2-
import { HasSignature } from '../resolution';
2+
import { Invokable } from '../resolution';
33

4-
export default interface ComponentKeyword {
5-
// Invoking with a component class
4+
export type ComponentKeyword = Invokable<{
65
<
76
Args,
87
GivenArgs extends Partial<Args>,
98
Blocks extends AnyBlocks,
109
ConstructorArgs extends unknown[]
1110
>(
1211
args: GivenArgs,
13-
component: new (...args: ConstructorArgs) => HasSignature<(args: Args) => AcceptsBlocks<Blocks>>
14-
): (
15-
args: Omit<Args, keyof GivenArgs> & Partial<Pick<Args, keyof GivenArgs & keyof Args>>
16-
) => AcceptsBlocks<Blocks>;
17-
18-
// Invoking with the result of another `{{component}}` expression
19-
<Args, GivenArgs extends Partial<Args>, Blocks extends AnyBlocks>(
20-
args: GivenArgs,
21-
component: (args: Args) => AcceptsBlocks<Blocks>
22-
): (
23-
args: Omit<Args, keyof GivenArgs> & Partial<Pick<Args, keyof GivenArgs & keyof Args>>
24-
) => AcceptsBlocks<Blocks>;
25-
}
12+
component: new (...args: ConstructorArgs) => Invokable<(args: Args) => AcceptsBlocks<Blocks>>
13+
): new () => Invokable<
14+
(
15+
args: Omit<Args, keyof GivenArgs> & Partial<Pick<Args, keyof GivenArgs & keyof Args>>
16+
) => AcceptsBlocks<Blocks>
17+
>;
18+
}>;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { Invokable } from '../resolution';
12
import { NoNamedArgs } from '../signature';
23

3-
export default interface DebuggerKeyword {
4+
export type DebuggerKeyword = Invokable<{
45
(args: NoNamedArgs): void;
5-
}
6+
}>;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { Invokable } from '../resolution';
12
import { AcceptsBlocks } from '../signature';
23

3-
export default interface EachKeyword {
4+
export type EachKeyword = Invokable<{
45
<T>(args: { key?: string }, items: T[]): AcceptsBlocks<{
56
default: [T, number];
67
inverse?: [];
78
}>;
8-
}
9+
}>;

0 commit comments

Comments
 (0)