77 UnknownPattern ,
88 OptionalP ,
99 ArrayP ,
10+ MapP ,
11+ SetP ,
1012 AndP ,
1113 OrP ,
1214 NotP ,
@@ -68,7 +70,14 @@ export function optional<
6870 } ;
6971}
7072
71- type Elem < xs > = xs extends Array < infer x > ? x : never ;
73+ type UnwrapArray < xs > = xs extends Array < infer x > ? x : never ;
74+
75+ type UnwrapSet < xs > = xs extends Set < infer x > ? x : never ;
76+
77+ type UnwrapMapKey < xs > = xs extends Map < infer k , any > ? k : never ;
78+
79+ type UnwrapMapValue < xs > = xs extends Map < any , infer v > ? v : never ;
80+
7281type WithDefault < a , b > = [ a ] extends [ never ] ? b : a ;
7382
7483/**
@@ -83,7 +92,7 @@ type WithDefault<a, b> = [a] extends [never] ? b : a;
8392 */
8493export function array <
8594 input ,
86- const p extends Pattern < WithDefault < Elem < input > , unknown > >
95+ const p extends Pattern < WithDefault < UnwrapArray < input > , unknown > >
8796> ( pattern : p ) : ArrayP < input , p > {
8897 return {
8998 [ symbols . matcher ] ( ) {
@@ -116,6 +125,109 @@ export function array<
116125 } ;
117126}
118127
128+ /**
129+ * `P.set(subpattern)` takes a sub pattern and returns a pattern that matches
130+ * sets if all their elements match the sub pattern.
131+ *
132+ * [Read `P.set` documentation on GitHub](https://github.com/gvergnaud/ts-pattern#Pset-patterns)
133+ *
134+ * @example
135+ * match(value)
136+ * .with({ users: P.set(P.string) }, () => 'will match Set<string>')
137+ */
138+ export function set <
139+ input ,
140+ const p extends Pattern < WithDefault < UnwrapSet < input > , unknown > >
141+ > ( pattern : p ) : SetP < input , p > {
142+ return {
143+ [ symbols . matcher ] ( ) {
144+ return {
145+ match : < I > ( value : I | input ) => {
146+ if ( ! ( value instanceof Set ) ) return { matched : false } ;
147+
148+ let selections : Record < string , unknown [ ] > = { } ;
149+
150+ if ( value . size === 0 ) {
151+ return { matched : true , selections } ;
152+ }
153+
154+ const selector = ( key : string , value : unknown ) => {
155+ selections [ key ] = ( selections [ key ] || [ ] ) . concat ( [ value ] ) ;
156+ } ;
157+
158+ const matched = setEvery ( value , ( v ) =>
159+ matchPattern ( pattern , v , selector )
160+ ) ;
161+
162+ return { matched, selections } ;
163+ } ,
164+ getSelectionKeys : ( ) => getSelectionKeys ( pattern ) ,
165+ } ;
166+ } ,
167+ } ;
168+ }
169+
170+ const setEvery = < T > ( set : Set < T > , predicate : ( value : T ) => boolean ) => {
171+ for ( const value of set ) {
172+ if ( predicate ( value ) ) continue
173+ return false
174+ }
175+ return true
176+ }
177+
178+ /**
179+ * `P.set(subpattern)` takes a sub pattern and returns a pattern that matches
180+ * sets if all their elements match the sub pattern.
181+ *
182+ * [Read `P.set` documentation on GitHub](https://github.com/gvergnaud/ts-pattern#Pset-patterns)
183+ *
184+ * @example
185+ * match(value)
186+ * .with({ users: P.set(P.string) }, () => 'will match Set<string>')
187+ */
188+ export function map <
189+ input ,
190+ const pkey extends Pattern < WithDefault < UnwrapMapKey < input > , unknown > > ,
191+ const pvalue extends Pattern < WithDefault < UnwrapMapValue < input > , unknown > >
192+ > ( patternKey : pkey , patternValue : pvalue ) : MapP < input , pkey , pvalue > {
193+ return {
194+ [ symbols . matcher ] ( ) {
195+ return {
196+ match : < I > ( value : I | input ) => {
197+ if ( ! ( value instanceof Map ) ) return { matched : false } ;
198+
199+ let selections : Record < string , unknown [ ] > = { } ;
200+
201+ if ( value . size === 0 ) {
202+ return { matched : true , selections } ;
203+ }
204+
205+ const selector = ( key : string , value : unknown ) => {
206+ selections [ key ] = ( selections [ key ] || [ ] ) . concat ( [ value ] ) ;
207+ } ;
208+
209+ const matched = mapEvery ( value , ( v , k ) => {
210+ const keyMatch = matchPattern ( patternKey , k , selector )
211+ const valueMatch = matchPattern ( patternValue , v , selector )
212+ return keyMatch && valueMatch
213+ } ) ;
214+
215+ return { matched, selections } ;
216+ } ,
217+ getSelectionKeys : ( ) => getSelectionKeys ( patternKey ) . concat ( getSelectionKeys ( patternValue ) ) ,
218+ } ;
219+ } ,
220+ } ;
221+ }
222+
223+ const mapEvery = < K , T > ( map : Map < K , T > , predicate : ( value : T , key : K ) => boolean ) => {
224+ for ( const [ key , value ] of map . entries ( ) ) {
225+ if ( predicate ( value , key ) ) continue ;
226+ return false ;
227+ }
228+ return true ;
229+ }
230+
119231/**
120232 * `P.intersection(...patterns)` returns a pattern which matches
121233 * only if **every** patterns provided in parameter match the input.
@@ -474,7 +586,7 @@ export function instanceOf<T extends AnyConstructor>(
474586 * )
475587 */
476588export function typed < input > ( ) : {
477- array < const p extends Pattern < Elem < input > > > ( pattern : p ) : ArrayP < input , p > ;
589+ array < const p extends Pattern < UnwrapArray < input > > > ( pattern : p ) : ArrayP < input , p > ;
478590
479591 optional < const p extends Pattern < input > > ( pattern : p ) : OptionalP < input , p > ;
480592
0 commit comments