@@ -11,6 +11,13 @@ local UNHANDLED = "Type 'Effect<string, NotFound, Database>' is not assignable t
1111local LS_ERRORS = " Missing 'NotFound | Timeout' in the expected Effect errors."
1212local LS_CTX = " This Effect requires a service that is missing from the expected Effect context: `Greeter`."
1313local SCOPE = " Type 'Effect<string, never, Scope>' is not assignable to type 'Effect<string, never, never>'"
14+ local LAYER_RIN = " Type 'Layer<{ readonly get: (id: string) => Effect<User, DbError, never>; }, never, Database>' is not assignable to type 'Layer<{ readonly get: (id: string) => Effect<User, DbError, never>; }, never, never>'."
15+ local LS_LAYER = " Missing 'Database' in the expected Layer context."
16+ local WIDE_R = " Argument of type 'Effect<string, never, unknown>' is not assignable to parameter of type 'Effect<string, never, never>'" .. SUFFIX
17+ local WIDE_E = " Type 'Effect<string, unknown, never>' is not assignable to type 'Effect<string, never, never>'" .. SUFFIX
18+ local LS_WIDE_E = " Missing 'unknown' in the expected Effect errors."
19+ local LS_WIDE_RIN = " Missing 'any' in the expected Layer context."
20+ local MULTI = " Type 'Effect<string, NotFound, Database>' is not assignable to type 'Effect<number, never, never>'" .. SUFFIX
1421
1522local NO_OVERLOAD = table.concat ({
1623 " No overload matches this call." ,
@@ -44,6 +51,40 @@ describe("hints.family", function()
4451 assert .are .same ({ " NotFound" , " Timeout" }, names )
4552 end )
4653
54+ it (" classifies a Layer's missing RIn as services and marks the tag" , function ()
55+ local parsed = parse .parse (LAYER_RIN )
56+ local f , names = hints .family (parsed )
57+ assert .are .equal (" services" , f )
58+ assert .are .same ({ " Database" }, names )
59+ assert .are .equal (" layer" , parsed .tag )
60+ parsed = parse .parse (LS_LAYER )
61+ f , names = hints .family (parsed )
62+ assert .are .equal (" services" , f )
63+ assert .are .same ({ " Database" }, names )
64+ assert .are .equal (" layer" , parsed .tag )
65+ end )
66+
67+ it (" classifies a channel that is only unknown/any as widened, named by its label" , function ()
68+ local f , names = hints .family (parse .parse (WIDE_R ))
69+ assert .are .equal (" widened" , f )
70+ assert .are .same ({ " R" }, names )
71+ f , names = hints .family (parse .parse (WIDE_E ))
72+ assert .are .equal (" widened" , f )
73+ assert .are .same ({ " E" }, names )
74+ f , names = hints .family (parse .parse (LS_WIDE_E ))
75+ assert .are .equal (" widened" , f )
76+ assert .are .same ({ " E" }, names )
77+ f , names = hints .family (parse .parse (LS_WIDE_RIN ))
78+ assert .are .equal (" widened" , f )
79+ assert .are .same ({ " RIn" }, names )
80+ end )
81+
82+ it (" asks nothing when more than one channel differs" , function ()
83+ local parsed = parse .parse (MULTI )
84+ assert .is_true (parsed .diff_count > 1 )
85+ assert .is_nil (hints .family (parsed ))
86+ end )
87+
4788 it (" leaves scope-only results alone" , function ()
4889 assert .is_nil (hints .family (parse .parse (SCOPE )))
4990 assert .is_nil (hints .family (nil ))
@@ -95,6 +136,48 @@ describe("hints resolver seam", function()
95136 assert .is_falsy (box :find (" SomeLayer" , 1 , true ))
96137 end )
97138
139+ it (" honors the resolver in the Layer RIn box, from a type diff and from the language service" , function ()
140+ hints .set_resolver (function (parsed , family , names )
141+ assert .are .equal (" services" , family )
142+ assert .are .equal (" layer" , parsed .tag )
143+ return { label = " Jev" , line = hints .templates .provide (" DatabaseLive" , names , " layer" ), detail = " layer DatabaseLive 0.93" }
144+ end )
145+ for _ , case in ipairs ({ { LAYER_RIN , " typescript" }, { LS_LAYER , " effect" } }) do
146+ local box = render .artistic (diag (case [1 ], case [2 ]), { effect = true })
147+ assert .is_truthy (box :find (" Missing RIn" , 1 , true ))
148+ assert .is_truthy (box :find (" ⚡ Jev: Layer.provide(DatabaseLive) inside this layer" , 1 , true ))
149+ assert .is_truthy (box :find (" ↳ layer DatabaseLive 0.93" , 1 , true ))
150+ assert .is_falsy (box :find (" Layer.merge" , 1 , true ))
151+ end
152+ hints .set_resolver (function ()
153+ return { lean = " jev unsure: layer none 0.40" }
154+ end )
155+ local box = render .artistic (diag (LAYER_RIN ), { effect = true })
156+ assert .is_truthy (box :find (" ⚡ Hint: compose with Layer.provide(...) or Layer.merge(...)" , 1 , true ))
157+ assert .is_truthy (box :find (" ↳ jev unsure: layer none 0.40" , 1 , true ))
158+ end )
159+
160+ it (" lets the resolver name where a widened channel lost its type" , function ()
161+ hints .set_resolver (function (_ , family , names )
162+ assert .are .equal (" widened" , family )
163+ return { label = " Jev" , line = hints .templates .widened (names [1 ], " fromRegistry" , " app.ts" , 12 ), detail = " widened fromRegistry 0.81" }
164+ end )
165+ local box = render .artistic (diag (WIDE_R ), { effect = true })
166+ assert .is_truthy (box :find (" R Not Inferred" , 1 , true ))
167+ assert .is_truthy (box :find (" ⚡ Jev: annotate fromRegistry (app.ts:12), where R widened" , 1 , true ))
168+ assert .is_truthy (box :find (" ↳ widened fromRegistry 0.81" , 1 , true ))
169+ assert .is_falsy (box :find (" annotate the effect to find where" , 1 , true ))
170+ box = render .artistic (diag (LS_WIDE_E , " effect" ), { effect = true })
171+ assert .is_truthy (box :find (" E Not Inferred" , 1 , true ))
172+ assert .is_truthy (box :find (" where E widened" , 1 , true ))
173+ hints .set_resolver (function ()
174+ return { lean = " jev unsure: widened none 0.30" }
175+ end )
176+ box = render .artistic (diag (WIDE_E ), { effect = true })
177+ assert .is_truthy (box :find (" ⚡ Hint: annotate the effect to find where E widened" , 1 , true ))
178+ assert .is_truthy (box :find (" ↳ jev unsure: widened none 0.30" , 1 , true ))
179+ end )
180+
98181 it (" keeps the generic line and adds a lean when the resolver is unsure" , function ()
99182 hints .set_resolver (function ()
100183 return { lean = " jev unsure: fix declare 0.47" }
0 commit comments