Skip to content

Commit 57982b9

Browse files
committed
feat(exact): exact generic types
1 parent 80df163 commit 57982b9

File tree

3 files changed

+63
-7
lines changed

3 files changed

+63
-7
lines changed

sources/Function/Exact.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {Narrowable} from './_Internal'
2+
3+
/**
4+
* Force `A` to comply with `W`. `A` must be a shape of `W`. In other words, `A`
5+
* must extend `W` and have the same properties - no more, no less.
6+
* @param A
7+
* @param W
8+
*/
9+
type Exact<A, W> =
10+
W extends unknown ?
11+
A extends W
12+
? A extends Narrowable
13+
? A
14+
: {
15+
[K in keyof A]: K extends keyof W
16+
? Exact<A[K], W[K]>
17+
: never
18+
}
19+
: W
20+
: never;
21+
22+
export {Exact}

sources/Function/_api.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
export {AutoPath} from './AutoPath'
44
export {Compose} from './Compose'
5+
export {Exact} from './Exact'
56
export {Curry} from './Curry'
67
export {Function} from './Function'
8+
export {Narrow} from './Narrow'
79
export {Length} from './Length'
810
export {NoInfer} from './NoInfer'
911
export {Parameters} from './Parameters'

tests/Function.ts

+39-7
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ declare function curry<Fn extends F.Function>(f: Fn): F.Curry<Fn>;
5858

5959
const __ = {} as A.x
6060

61-
// @ts-ignore
6261
const toCurry = (name: string, age: number, single: boolean, nicknames?: string) => true
6362
const curried = curry(toCurry)
6463

@@ -68,6 +67,19 @@ const test02: boolean = curried(__, 26)(__, true, __)('Jane', 'JJ') // boolean
6867
const test03: boolean = curried('Jane', 26, true) // boolean
6968
const test04: boolean = curried('Jane', 26, true, 'JJ') // boolean
7069

70+
// ---------------------------------------------------------------------------------------
71+
// EXACT
72+
73+
declare function exactObject<A>(x: F.Exact<A, {a: number, b: 2}>): A;
74+
75+
const test07 = exactObject({} as {a: 1, b: 2})
76+
// @ts-expect-error
77+
const test08 = exactObject({} as {a: 1})
78+
79+
checks([
80+
check<typeof test07, {a: 1, b: 2}, Test.Pass>(),
81+
])
82+
7183
// ---------------------------------------------------------------------------------------
7284
// PARAMETERS
7385

@@ -102,9 +114,9 @@ checks([
102114
])
103115

104116
// ---------------------------------------------------------------------------------------
105-
// PATHVALID
117+
// VALIDPATH
106118

107-
type O_PATHVALID = {
119+
type O_VALIDPATH = {
108120
a: {
109121
a: {};
110122
};
@@ -118,10 +130,10 @@ type O_PATHVALID = {
118130

119131
checks([
120132
check<F.ValidPath<any, ['a', 'a']>, ['a', 'a'], Test.Pass>(),
121-
check<F.ValidPath<O_PATHVALID, ['a', 'a']>, ['a', 'a'], Test.Pass>(),
122-
check<F.ValidPath<O_PATHVALID, ['a', 'x']>, ['a', 'x'], Test.Pass>(),
123-
check<F.ValidPath<O_PATHVALID, ['b', 'a', 'a']>, ['b', 'a', 'a'], Test.Pass>(),
124-
check<F.ValidPath<O_PATHVALID, ['b', 'b', 0]>, ['b', 'b', 0], Test.Pass>(),
133+
check<F.ValidPath<O_VALIDPATH, ['a', 'a']>, ['a', 'a'], Test.Pass>(),
134+
check<F.ValidPath<O_VALIDPATH, ['a', 'x']>, ['a', 'x'], Test.Pass>(),
135+
check<F.ValidPath<O_VALIDPATH, ['b', 'a', 'a']>, ['b', 'a', 'a'], Test.Pass>(),
136+
check<F.ValidPath<O_VALIDPATH, ['b', 'b', 0]>, ['b', 'b', 0], Test.Pass>(),
125137
])
126138

127139
// ---------------------------------------------------------------------------------------
@@ -132,6 +144,20 @@ checks([
132144
check<F.Length<(a1: any, a2?: any) => any>, 1 | 2, Test.Pass>(),
133145
])
134146

147+
// ---------------------------------------------------------------------------------------
148+
// NARROW
149+
150+
declare function narrowList<A extends any[]>(x: F.Narrow<A>): A;
151+
declare function narrowObject<A extends object>(x: F.Narrow<A>): A;
152+
153+
const test05 = narrowList(['e', 2, true, {f: ['g', ['h']]}])
154+
const test06 = narrowObject({a: 1, b: 'c', d: ['e', 2, true, {f: ['g']}]})
155+
156+
checks([
157+
check<typeof test05, ['e', 2, true, {f: ['g', ['h']]}], Test.Pass>(),
158+
check<typeof test06, {a: 1, b: 'c', d: ['e', 2, true, {f: ['g']}]}, Test.Pass>(),
159+
])
160+
135161
// ---------------------------------------------------------------------------------------
136162
// PIPE
137163

@@ -147,6 +173,12 @@ const pipedSync = pipeSync(
147173
(message: string) => false, // receive previous return
148174
)
149175

176+
pipeSync(
177+
curry((a1: number, d2: number) => `${a1 + d2}`),
178+
(b1: string) => [b1],
179+
(c1: string[]) => [c1],
180+
)(23, 42)
181+
150182
checks([
151183
check<(typeof pipedSync), (name: string, age: number) => boolean, Test.Pass>(),
152184
])

0 commit comments

Comments
 (0)