Skip to content

Commit 6104d06

Browse files
committed
Improve types
1 parent 0c8eedf commit 6104d06

File tree

2 files changed

+202
-49
lines changed

2 files changed

+202
-49
lines changed

src/main.test-d.ts

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import switchFunctional, {
55
type Switch,
66
} from 'switch-functional'
77

8-
const switchStatement = switchFunctional(true)
9-
expectType<Switch>(switchStatement)
8+
const switchStatement = switchFunctional(true as const)
9+
expectAssignable<Switch>(switchStatement)
1010

1111
// @ts-expect-error
1212
switchFunctional()
@@ -24,7 +24,10 @@ switchFunctional({} as const)
2424
switchFunctional([] as const)
2525
switchFunctional(() => {})
2626

27-
expectType<Switch>(switchStatement.case(true, true))
27+
const caseStatement = switchStatement.case(true, 0 as const)
28+
29+
expectType<Switch<0, true>>(switchStatement.case(true, 0))
30+
expectType<Switch<0 | 1, true>>(caseStatement.case(true, 1))
2831

2932
switchStatement.case(0, true)
3033
switchStatement.case(0n, true)
@@ -40,6 +43,22 @@ switchStatement.case([] as const, true)
4043
switchStatement.case([true] as const, true)
4144
switchStatement.case([() => ''] as const, true)
4245
switchStatement.case(() => true, true)
46+
switchStatement.case((value: true) => true, true)
47+
caseStatement.case(0, true)
48+
caseStatement.case(0n, true)
49+
caseStatement.case(true, true)
50+
caseStatement.case(null, true)
51+
caseStatement.case(undefined, true)
52+
caseStatement.case('', true)
53+
caseStatement.case(Symbol(''), true)
54+
caseStatement.case({} as const, true)
55+
caseStatement.case({ a: true } as const, true)
56+
caseStatement.case({ a: () => '' } as const, true)
57+
caseStatement.case([] as const, true)
58+
caseStatement.case([true] as const, true)
59+
caseStatement.case([() => ''] as const, true)
60+
caseStatement.case(() => true, true)
61+
caseStatement.case((value: true) => true, true)
4362
expectAssignable<Condition>(0)
4463
expectAssignable<Condition>(0n)
4564
expectAssignable<Condition>(true)
@@ -54,10 +73,24 @@ expectAssignable<Condition>([] as const)
5473
expectAssignable<Condition>([true] as const)
5574
expectAssignable<Condition>([() => ''] as const)
5675
expectAssignable<Condition>(() => true)
76+
expectAssignable<Condition<true>>(() => true)
77+
expectAssignable<Condition<true>>((value: true) => true)
5778

5879
// @ts-expect-error
5980
switchStatement.case(() => '', true)
81+
// @ts-expect-error
82+
switchStatement.case((value: false) => true, true)
83+
// @ts-expect-error
84+
switchStatement.case((value: true, second: true) => true, true)
85+
// @ts-expect-error
86+
caseStatement.case(() => '', true)
87+
// @ts-expect-error
88+
caseStatement.case((value: false) => true, true)
89+
// @ts-expect-error
90+
caseStatement.case((value: true, second: true) => true, true)
6091
expectNotAssignable<Condition>(() => '')
92+
expectNotAssignable<Condition<true>>((value: false) => true)
93+
expectNotAssignable<Condition<true>>((value: true, second: true) => true)
6194

6295
switchStatement.case(true, 0)
6396
switchStatement.case(true, 0n)
@@ -69,15 +102,39 @@ switchStatement.case(true, Symbol(''))
69102
switchStatement.case(true, {} as const)
70103
switchStatement.case(true, [] as const)
71104
switchStatement.case(true, () => {})
105+
switchStatement.case(true, (value: true) => {})
106+
caseStatement.case(true, 0)
107+
caseStatement.case(true, 0n)
108+
caseStatement.case(true, true)
109+
caseStatement.case(true, null)
110+
caseStatement.case(true, undefined)
111+
caseStatement.case(true, '')
112+
caseStatement.case(true, Symbol(''))
113+
caseStatement.case(true, {} as const)
114+
caseStatement.case(true, [] as const)
115+
caseStatement.case(true, () => {})
116+
caseStatement.case(true, (value: true) => {})
72117

73118
// @ts-expect-error
74119
switchStatement.case()
75120
// @ts-expect-error
76121
switchStatement.case(true)
77122
// @ts-expect-error
78123
switchStatement.case(true, true, true)
79-
80-
expectAssignable<unknown>(switchStatement.default(true))
124+
// @ts-expect-error
125+
switchStatement.case(true, (value: false) => {})
126+
// @ts-expect-error
127+
switchStatement.case(true, (value: true, second: false) => {})
128+
// @ts-expect-error
129+
caseStatement.case()
130+
// @ts-expect-error
131+
caseStatement.case(true)
132+
// @ts-expect-error
133+
caseStatement.case(true, true, true)
134+
// @ts-expect-error
135+
caseStatement.case(true, (value: false) => {})
136+
// @ts-expect-error
137+
caseStatement.case(true, (value: true, second: false) => {})
81138

82139
switchStatement.default(0)
83140
switchStatement.default(0n)
@@ -89,22 +146,56 @@ switchStatement.default(Symbol(''))
89146
switchStatement.default({} as const)
90147
switchStatement.default([] as const)
91148
switchStatement.default(() => {})
149+
switchStatement.default((value: true) => {})
150+
caseStatement.default(0)
151+
caseStatement.default(0n)
152+
caseStatement.default(true)
153+
caseStatement.default(null)
154+
caseStatement.default(undefined)
155+
caseStatement.default('')
156+
caseStatement.default(Symbol(''))
157+
caseStatement.default({} as const)
158+
caseStatement.default([] as const)
159+
caseStatement.default(() => {})
160+
caseStatement.default((value: true) => {})
92161

93162
// @ts-expect-error
94163
switchStatement.default()
95164
// @ts-expect-error
96165
switchStatement.default(true, true)
166+
// @ts-expect-error
167+
switchStatement.default((value: false) => {})
168+
// @ts-expect-error
169+
switchStatement.default((value: true, second: false) => {})
170+
// @ts-expect-error
171+
caseStatement.default()
172+
// @ts-expect-error
173+
caseStatement.default(true, true)
174+
// @ts-expect-error
175+
caseStatement.default((value: false) => {})
176+
// @ts-expect-error
177+
caseStatement.default((value: true, second: false) => {})
97178

98179
// @ts-expect-error
99180
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
100181
switchStatement.other()
182+
// @ts-expect-error
183+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
184+
caseStatement.other()
101185

102186
expectType<1>(switchStatement.default(1 as const))
103-
expectType<1 | 2>(switchStatement.case(true, 2 as const).default(1 as const))
187+
expectType<1 | 2>(switchStatement.case(true, 1 as const).default(2 as const))
188+
expectType<0 | 1>(caseStatement.default(1 as const))
189+
expectType<0 | 1 | 2>(caseStatement.case(true, 1 as const).default(2 as const))
104190

105-
expectType<Switch<1>>(switchStatement.case(true, 1 as const))
106-
expectType<Switch<1 | 2>>(
191+
expectType<Switch<1, true>>(switchStatement.case(true, 1 as const))
192+
expectType<Switch<1 | 2, true>>(
107193
switchStatement.case(true, 1 as const).case(true, 2 as const),
108194
)
195+
expectType<Switch<0 | 1, true>>(caseStatement.case(true, 1 as const))
196+
expectType<Switch<0 | 1 | 2, true>>(
197+
caseStatement.case(true, 1 as const).case(true, 2 as const),
198+
)
109199

110-
expectAssignable<Switch<1 | 2>>(switchStatement.case(true, 1 as const))
200+
expectAssignable<Switch<1 | 2, true>>(switchStatement.case(true, 1 as const))
201+
expectAssignable<Switch<0 | 1 | 2, true>>(caseStatement.case(true, 1 as const))

src/main.ts

Lines changed: 102 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
// Functional `switch` statement
22
const chain =
3-
<ReturnValues extends ReturnValue>(resolved: Resolved) =>
4-
(input: Input) => ({
3+
<
4+
OriginalInput extends Input = never,
5+
FinalReturnValues extends FinalReturnValue = never,
6+
>(
7+
resolved: Resolved,
8+
) =>
9+
<NewInput extends Input>(input: NewInput) => ({
510
/**
611
* If the `input` matches the `conditions`, the final return value will be
712
* `caseReturnValue`.
813
*
914
* `caseReturnValue` can optionally be a function taking the `input` as argument.
1015
*/
11-
case: addCase<ReturnValues>({ resolved, input }),
16+
case: addCase<GetOriginalInput<OriginalInput, NewInput>, FinalReturnValues>(
17+
{ resolved, input },
18+
),
1219

1320
/**
1421
* If one of the `.case()` statements matched, returns its
@@ -17,55 +24,89 @@ const chain =
1724
* `defaultReturnValue` can optionally be a function taking the `input` as
1825
* argument.
1926
*/
20-
default: useDefault<ReturnValues>({ resolved, input }),
27+
default: useDefault<
28+
GetOriginalInput<OriginalInput, NewInput>,
29+
FinalReturnValues
30+
>({ resolved, input }),
2131
})
2232

33+
type GetOriginalInput<
34+
OriginalInput extends Input,
35+
NewInput extends Input,
36+
> = OriginalInput[] extends never[] ? NewInput : OriginalInput
37+
2338
/**
2439
* Return value of `switchFunctional()` and `switchFunctional().case()`
2540
*/
26-
export interface Switch<ReturnValues extends ReturnValue = never> {
27-
case: <NewReturnValue extends ReturnValue>(
28-
conditions: Conditions,
41+
export interface Switch<
42+
FinalReturnValues extends FinalReturnValue = never,
43+
OriginalInput extends Input = Input,
44+
> {
45+
case: <NewReturnValue extends ReturnValue<OriginalInput>>(
46+
conditions: Conditions<OriginalInput>,
2947
caseReturnValue: NewReturnValue,
30-
) => Switch<ReturnValues | GetReturnValue<NewReturnValue>>
31-
default: <NewReturnValue extends ReturnValue>(
48+
) => Switch<FinalReturnValues | GetReturnValue<NewReturnValue>, OriginalInput>
49+
default: <NewReturnValue extends ReturnValue<OriginalInput>>(
3250
defaultReturnValue: NewReturnValue,
33-
) => ReturnValues | GetReturnValue<NewReturnValue>
51+
) => FinalReturnValues | GetReturnValue<NewReturnValue>
3452
}
3553

3654
// `switchFunctional(input)[.case(...)].case(conditions, returnValue)`
3755
const addCase =
38-
<ReturnValues extends ReturnValue>({ resolved, input }: Context) =>
39-
<NewReturnValue extends ReturnValue>(
40-
conditions: Conditions,
56+
<OriginalInput extends Input, FinalReturnValues extends FinalReturnValue>({
57+
resolved,
58+
input,
59+
}: Context) =>
60+
<NewReturnValue extends ReturnValue<OriginalInput>>(
61+
conditions: Conditions<OriginalInput>,
4162
caseReturnValue: NewReturnValue,
42-
): Switch<ReturnValues | GetReturnValue<NewReturnValue>> =>
43-
resolved || !matchesConditions(input, conditions)
44-
? chain<ReturnValues>(resolved)(input)
45-
: chain<ReturnValues | GetReturnValue<NewReturnValue>>(true)(
46-
applyReturnValue(input, caseReturnValue),
47-
)
63+
): Switch<
64+
FinalReturnValues | GetReturnValue<NewReturnValue>,
65+
OriginalInput
66+
> =>
67+
resolved || !matchesConditions(input as OriginalInput, conditions)
68+
? (chain(resolved)(input as OriginalInput) as Switch<
69+
FinalReturnValues,
70+
OriginalInput
71+
>)
72+
: (chain(true)(
73+
applyReturnValue(input as OriginalInput, caseReturnValue),
74+
) as Switch<
75+
FinalReturnValues | GetReturnValue<NewReturnValue>,
76+
OriginalInput
77+
>)
4878

4979
// `switchFunctional(input)[.case()...].default(returnValue)`
5080
const useDefault =
5181
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
52-
<ReturnValues extends ReturnValue>({ resolved, input }: Context) =>
53-
<NewReturnValue extends ReturnValue>(defaultReturnValue: NewReturnValue) =>
82+
<OriginalInput extends Input, FinalReturnValues extends FinalReturnValue>({
83+
resolved,
84+
input,
85+
}: Context) =>
86+
<NewReturnValue extends ReturnValue<OriginalInput>>(
87+
defaultReturnValue: NewReturnValue,
88+
) =>
5489
resolved
55-
? (input as ReturnValues)
90+
? (input as FinalReturnValues)
5691
: (applyReturnValue(
57-
input,
58-
defaultReturnValue as unknown,
92+
input as OriginalInput,
93+
defaultReturnValue,
5994
) as GetReturnValue<NewReturnValue>)
6095

61-
const matchesConditions = (input: Input, conditions: Conditions) =>
96+
const matchesConditions = <OriginalInput extends Input>(
97+
input: OriginalInput,
98+
conditions: Conditions<OriginalInput>,
99+
) =>
62100
Array.isArray(conditions)
63101
? (conditions as Condition[]).some((condition) =>
64102
matchesCondition(input, condition),
65103
)
66104
: matchesCondition(input, conditions)
67105

68-
const matchesCondition = (input: Input, condition: Condition) => {
106+
const matchesCondition = <OriginalInput extends Input>(
107+
input: OriginalInput,
108+
condition: Condition<OriginalInput>,
109+
) => {
69110
if (typeof condition === 'function') {
70111
return condition(input)
71112
}
@@ -102,10 +143,10 @@ const deepIncludes = (input: Input, subset: unknown): boolean => {
102143
const isObject = (input: Input): input is { [name: PropertyKey]: unknown } =>
103144
typeof input === 'object' && input !== null
104145

105-
const applyReturnValue = (input: Input, ReturnValue: ReturnValue): unknown =>
106-
typeof ReturnValue === 'function'
107-
? (ReturnValue as ReturnValueFunction)(input)
108-
: ReturnValue
146+
const applyReturnValue = <OriginalInput extends Input>(
147+
input: OriginalInput,
148+
returnValue: ReturnValue<OriginalInput>,
149+
) => (typeof returnValue === 'function' ? returnValue(input) : returnValue)
109150

110151
/**
111152
* Functional switch statement. This must be chained with
@@ -189,7 +230,7 @@ const applyReturnValue = (input: Input, ReturnValue: ReturnValue): unknown =>
189230
* .default((user) => user.genericType)
190231
* ```
191232
*/
192-
const switchFunctional = chain<never>(false)
233+
const switchFunctional = chain(false)
193234

194235
export default switchFunctional
195236

@@ -202,7 +243,9 @@ interface Context {
202243
readonly input: Input
203244
}
204245

205-
type Conditions = Condition | readonly Condition[]
246+
type Conditions<PassedInput extends Input = Input> =
247+
| Condition<PassedInput>
248+
| readonly Condition<PassedInput>[]
206249

207250
/**
208251
* The `conditions` can be:
@@ -214,7 +257,23 @@ type Conditions = Condition | readonly Condition[]
214257
* - A boolean
215258
* - An array of the above types, checking if _any_ condition in the array matches
216259
*/
217-
export type Condition =
260+
export type Condition<OriginalInput extends Input = Input> =
261+
| string
262+
| number
263+
| boolean
264+
| bigint
265+
| symbol
266+
| null
267+
| undefined
268+
| readonly unknown[]
269+
| { readonly [key: PropertyKey]: unknown }
270+
| ConditionFunction<OriginalInput>
271+
272+
type ConditionFunction<OriginalInput extends Input> = (
273+
input: OriginalInput,
274+
) => boolean
275+
276+
type ReturnValue<OriginalInput extends Input> =
218277
| string
219278
| number
220279
| boolean
@@ -224,13 +283,16 @@ export type Condition =
224283
| undefined
225284
| readonly unknown[]
226285
| { readonly [key: PropertyKey]: unknown }
227-
| ((input: unknown) => boolean)
286+
| ReturnValueFunction<OriginalInput>
228287

229-
type ReturnValue = unknown
288+
type FinalReturnValue = unknown
230289

231-
type GetReturnValue<NewReturnValue extends ReturnValue> =
232-
NewReturnValue extends ReturnValueFunction
233-
? ReturnType<NewReturnValue>
234-
: NewReturnValue
290+
type ReturnValueFunction<OriginalInput extends Input> = (
291+
input: OriginalInput,
292+
) => unknown
235293

236-
type ReturnValueFunction = (input: unknown) => unknown
294+
type GetReturnValue<NewReturnValue> = NewReturnValue extends (
295+
...args: never[]
296+
) => unknown
297+
? ReturnType<NewReturnValue>
298+
: NewReturnValue

0 commit comments

Comments
 (0)