11namespace Hedgehog
22
33open System
4+ open System.Collections .Concurrent
45open System.Collections .Immutable
56
67// A generator factory which can be backed by a generic method.
@@ -21,8 +22,7 @@ type internal GeneratorKey = {
2122 ConcreteTypes: Type option list
2223}
2324
24- [<Struct>]
25- type internal GeneratorCollection =
25+ type internal GeneratorCollection = {
2626 // A dictionary of generators.
2727 // The key distinguishes between different patterns of generic vs concrete type arguments
2828 // The value is a tuple of:
@@ -31,14 +31,17 @@ type internal GeneratorCollection =
3131 // 3. A generator factory, which can be backed by a generic method,
3232 // so it takes an array of genetic type parameters,
3333 // and an array of arguments to create the generator.
34- GeneratorCollection of ImmutableDictionary < GeneratorKey , Type * Type [] * GeneratorFactory >
34+ Generators: ImmutableDictionary < GeneratorKey , Type * Type [] * GeneratorFactory >
35+ // The cache memoizes tryFindFor results to avoid repeated reflection calls.
36+ Cache: ConcurrentDictionary < Type , ( Type * ( Type [] * GeneratorFactory )) option >
37+ }
3538
3639module internal GeneratorCollection =
3740
38- let empty = GeneratorCollection ( ImmutableDictionary.Empty )
39-
40- let unwrap ( GeneratorCollection map ) = map
41- let map f = unwrap >> f >> GeneratorCollection
41+ let empty = {
42+ Generators = ImmutableDictionary.Empty
43+ Cache = ConcurrentDictionary < Type , ( Type * ( Type [] * GeneratorFactory )) option >()
44+ }
4245
4346 /// Create a GeneratorKey from a type by identifying which positions are generic vs concrete
4447 let private createKey ( t : Type ) : GeneratorKey =
@@ -52,12 +55,21 @@ module internal GeneratorCollection =
5255 // Non-generic types use themselves as the key
5356 { GenericTypeDefinition = t; ConcreteTypes = [] }
5457
55- let merge ( GeneratorCollection gens1 ) ( GeneratorCollection gens2 ) =
56- GeneratorCollection ( gens1.SetItems( gens2))
58+ let merge ( gens1 : GeneratorCollection ) ( gens2 : GeneratorCollection ) =
59+ // Create a new cache when merging since the generator set has changed
60+ {
61+ Generators = gens1.Generators.SetItems( gens2.Generators)
62+ Cache = ConcurrentDictionary< Type, ( Type * ( Type[] * GeneratorFactory)) option>()
63+ }
5764
5865 let addGenerator ( normalizedType : Type ) ( originalType : Type ) ( paramTypes : Type []) ( factory : Type [] -> obj [] -> obj ) =
59- let key = createKey normalizedType
60- map _. SetItem( key, ( originalType, paramTypes, factory))
66+ fun ( gc : GeneratorCollection ) ->
67+ let key = createKey normalizedType
68+ // Reset cache when adding a generator since lookup results may change
69+ {
70+ Generators = gc.Generators.SetItem( key, ( originalType, paramTypes, factory))
71+ Cache = ConcurrentDictionary< Type, ( Type * ( Type[] * GeneratorFactory)) option>()
72+ }
6173
6274 /// Count the number of generic parameters in a type
6375 let private countGenericParameters ( t : Type ) =
@@ -69,20 +81,23 @@ module internal GeneratorCollection =
6981 // to satisfy specific types (like Either<int, string>).
7082 // When multiple generators match, returns the most specific one (fewest generic parameters).
7183 // Returns the original reflected type along with the args and factory.
72- let tryFindFor ( targetType : Type ) =
73- unwrap
74- >> Seq.choose ( fun ( KeyValue ( key , ( originalType , paramTypes , factory ))) ->
75- // Only consider generators with the same generic type definition
76- let targetKey = createKey targetType
77- if key.GenericTypeDefinition = targetKey.GenericTypeDefinition then
78- // Check if the stored type can satisfy the target type
79- if originalType |> TypeUtils.satisfies targetType then
80- Some ( originalType, paramTypes, factory)
84+ // Results are memoized to avoid repeated reflection calls.
85+ let tryFindFor ( targetType : Type ) ( gc : GeneratorCollection ) =
86+ gc.Cache.GetOrAdd( targetType, fun targetType ->
87+ let targetKey = createKey targetType
88+ gc.Generators
89+ |> Seq.choose ( fun ( KeyValue ( key , ( originalType , paramTypes , factory ))) ->
90+ // Only consider generators with the same generic type definition
91+ if key.GenericTypeDefinition = targetKey.GenericTypeDefinition then
92+ // Check if the stored type can satisfy the target type
93+ if originalType |> TypeUtils.satisfies targetType then
94+ Some ( originalType, paramTypes, factory)
95+ else
96+ None
8197 else
8298 None
83- else
84- None
99+ )
100+ |> Seq.sortBy ( fun ( originalType , _ , _ ) -> countGenericParameters originalType)
101+ |> Seq.tryHead
102+ |> Option.map ( fun ( originalType , paramTypes , factory ) -> ( originalType, ( paramTypes, factory)))
85103 )
86- >> Seq.sortBy ( fun ( originalType , _ , _ ) -> countGenericParameters originalType)
87- >> Seq.tryHead
88- >> Option.map ( fun ( originalType , paramTypes , factory ) -> ( originalType, ( paramTypes, factory)))
0 commit comments