@@ -14,6 +14,9 @@ module Yoga.Om.Layer
1414 , combineRequirements
1515 , provide
1616 , (>->)
17+ , class CheckMemoKeys
18+ , class CheckMemoKeysList
19+ , class CheckKeyNotDuplicated
1720 , class CheckAllProvided
1821 , class CheckAllLabelsExist
1922 , class CheckLabelExists
@@ -85,15 +88,16 @@ closeScope (Scope s) = do
8588-- OmLayer
8689-- =============================================================================
8790
88−-- | A layer that requires dependencies (req) and provides services (prov).
89−data OmLayer req prov err = OmLayer (Om (Record req) err (Record prov))
91+-- | A layer that requires dependencies (req), provides services (prov),
92+-- | and tracks memoization keys (memo) to prevent key collisions at compile time.
93+data OmLayer req prov err (memo :: Row Type) = OmLayer (Om (Record req) err (Record prov))
9094
9195-- =============================================================================
9296-- Layer constructors
9397-- =============================================================================
9498
9599-- | Create a layer from an Om computation.
96−makeLayer :: forall req prov err. Om (Record req) err (Record prov) -> OmLayer req prov err
100+makeLayer :: forall req prov err. Om (Record req) err (Record prov) -> OmLayer req prov err ()
97101makeLayer = OmLayer
98102
99103-- | Create a scoped layer with acquire/release semantics.
@@ -103,7 +107,7 @@ makeScopedLayer
103107 :: forall req prov err
104108 . Om { scope :: Scope | req } err (Record prov)
105109 -> (Record prov -> Aff Unit)
106− -> OmLayer (scope :: Scope | req) prov err
110+ -> OmLayer (scope :: Scope | req) prov err ()
107111makeScopedLayer acquire release = makeLayer do
108112 acquireRelease acquire release
109113
@@ -114,7 +118,7 @@ bracketLayer
114118 . Om { scope :: Scope | req } err resource
115119 -> (resource -> Aff Unit)
116120 -> (resource -> Om { scope :: Scope | req } err (Record prov))
117− -> OmLayer (scope :: Scope | req) prov err
121+ -> OmLayer (scope :: Scope | req) prov err ()
118122bracketLayer acquire release use = makeLayer do
119123 resource <- acquireRelease acquire (\r -> release r)
120124 use resource
@@ -137,11 +141,11 @@ acquireRelease acquire release = do
137141-- | If the same key has already been built in this scope, the cached result
138142-- | is returned without re-running the layer.
139143memoized
140− :: forall sym req prov err
144+ :: forall sym req prov err memo
141145 . IsSymbol sym
142146 => Proxy sym
143− -> OmLayer (scope :: Scope | req) prov err
144− -> OmLayer (scope :: Scope | req) prov err
147+ -> OmLayer (scope :: Scope | req) prov err memo
148+ -> OmLayer (scope :: Scope | req) prov err (sym :: Record prov | memo)
145149memoized _ (OmLayer om) = OmLayer do
146150 { scope: Scope s } <- Om.ask
147151 let key = reflectSymbol (Proxy :: Proxy sym)
@@ -153,6 +157,38 @@ memoized _ (OmLayer om) = OmLayer do
153157 liftEffect $ Ref.modify_ (Map.insert key (toDynamic prov)) s.cache
154158 pure prov
155159
160+-- =============================================================================
161+-- Memo key safety — reject duplicate keys with different types
162+-- =============================================================================
163+
164+class CheckMemoKeys (row :: Row Type)
165+
166+instance (RowToList row rl, CheckMemoKeysList rl) => CheckMemoKeys row
167+
168+class CheckMemoKeysList (rl :: RowList Type)
169+
170+instance CheckMemoKeysList Nil
171+
172+instance
173+ ( CheckMemoKeysList tail
174+ , CheckKeyNotDuplicated label ty tail
175+ ) =>
176+ CheckMemoKeysList (Cons label ty tail)
177+
178+class CheckKeyNotDuplicated (label :: Symbol) (ty :: Type) (rest :: RowList Type)
179+
180+instance CheckKeyNotDuplicated label ty Nil
181+
182+instance
183+ ( TypeEquals ty ty'
184+ , CheckKeyNotDuplicated label ty tail
185+ ) =>
186+ CheckKeyNotDuplicated label ty (Cons label ty' tail)
187+
188+else instance
189+ CheckKeyNotDuplicated label ty tail =>
190+ CheckKeyNotDuplicated label ty (Cons otherLabel otherTy tail)
191+
156192-- =============================================================================
157193-- Custom Type Errors for Missing Dependencies
158194-- =============================================================================
@@ -229,10 +265,10 @@ else instance
229265
230266-- | Run a layer with a given context, with custom error if requirements aren't met.
231267runLayer
232− :: forall req prov err available
268+ :: forall req prov err available memo
233269 . CheckAllProvided req available
234270 => Record available
235− -> OmLayer req prov err
271+ -> OmLayer req prov err memo
236272 -> Om (Record available) err (Record prov)
237273runLayer _ctx (OmLayer om) = widenCtx om
238274 where
@@ -242,8 +278,8 @@ runLayer _ctx (OmLayer om) = widenCtx om
242278-- | Build a fully-provided scoped layer, return the provisions.
243279-- | All finalizers run after the provisions are returned.
244280runScoped
245− :: forall prov
246− . OmLayer (scope :: Scope) prov ()
281+ :: forall prov memo
282+ . OmLayer (scope :: Scope) prov () memo
247283 -> Aff (Record prov)
248284runScoped layer = withScoped layer pure
249285
@@ -252,8 +288,8 @@ runScoped layer = withScoped layer pure
252288-- | running all finalizers in reverse order — whether by success, failure,
253289-- | or interruption.
254290withScoped
255− :: forall prov a
256− . OmLayer (scope :: Scope) prov ()
291+ :: forall prov a memo
292+ . OmLayer (scope :: Scope) prov () memo
257293 -> (Record prov -> Aff a)
258294 -> Aff a
259295withScoped (OmLayer om) callback = bracket acquire release use
@@ -270,7 +306,7 @@ withScoped (OmLayer om) callback = bracket acquire release use
270306-- =============================================================================
271307
272308combineRequirements
273− :: forall req1 req2 prov1 prov2 err1 err2 provMerged provMergedRaw reqMerged reqDeduped _req1 _req2 _err1 _err2
309+ :: forall req1 req2 prov1 prov2 err1 err2 memo1 memo2 provMerged provMergedRaw reqMerged reqDeduped memoMerged memoDeduped _req1 _req2 _err1 _err2
274310 . Union req1 req2 reqMerged
275311 => Nub reqMerged reqDeduped
276312 => Union req1 _req1 reqDeduped
@@ -279,11 +315,14 @@ combineRequirements
279315 => Union err2 _err2 ()
280316 => Union prov1 prov2 provMergedRaw
281317 => Nub provMergedRaw provMerged
318+ => Union memo1 memo2 memoMerged
319+ => CheckMemoKeys memoMerged
320+ => Nub memoMerged memoDeduped
282321 => Keys req1
283322 => Keys req2
284− => OmLayer req1 prov1 err1
285− -> OmLayer req2 prov2 err2
286− -> OmLayer reqDeduped provMerged ()
323+ => OmLayer req1 prov1 err1 memo1
324+ -> OmLayer req2 prov2 err2 memo2
325+ -> OmLayer reqDeduped provMerged () memoDeduped
287326combineRequirements (OmLayer build1) (OmLayer build2) = OmLayer do
288327 rec1 <- Om.expand build1
289328 rec2 <- Om.expand build2
@@ -294,7 +333,7 @@ combineRequirements (OmLayer build1) (OmLayer build2) = OmLayer do
294333-- =============================================================================
295334
296335provide
297− :: forall req prov1 prov2 err1 err2 req2 reqOut _req _prov1 _err1 _err2
336+ :: forall req prov1 prov2 err1 err2 req2 reqOut memo1 memo2 memoMerged memoDeduped _req _prov1 _err1 _err2
298337 . Union req _req req
299338 => Union prov1 _prov1 prov1
300339 => Union err1 _err1 ()
@@ -302,12 +341,15 @@ provide
302341 => Union prov1 req reqOut
303342 => Nub reqOut reqOut
304343 => Union req2 _prov1 reqOut
344+ => Union memo1 memo2 memoMerged
345+ => CheckMemoKeys memoMerged
346+ => Nub memoMerged memoDeduped
305347 => Keys req
306348 => Keys prov1
307349 => Keys req2
308− => OmLayer req2 prov2 err2
309− -> OmLayer req prov1 err1
310− -> OmLayer req prov2 ()
350+ => OmLayer req2 prov2 err2 memo2
351+ -> OmLayer req prov1 err1 memo1
352+ -> OmLayer req prov2 () memoDeduped
311353provide (OmLayer layer2) (OmLayer layer1) = OmLayer do
312354 prov1 <- Om.expand layer1
313355 Om.expand layer2 # Om.widenCtx prov1