Problem
The constructor folds fire on a constructor that is syntactically in place: reduceKnownConstructor matches ReflectCtor (Ctor …) and DataArgumentByIndex i (Ctor …), and propagateKnownCtorThroughLet (#214) extends this through a Standalone Let whose right-hand side is a saturated Ctor read only through constructor-eliminating reads. A conditional of known constructors slips through both. case (if b then Just x else Just y) of … let-binds the scrutinee (it is read more than once: a tag test plus a field read), so the eliminating reads see ReflectCtor (Ref v) and DataArgumentByIndex 0 (Ref v) with v = IfThenElse b (Ctor Just [x]) (Ctor Just [y]) — a Ref, never the conditional of Ctors — and nothing folds. The #243 distribution (pushEliminatorIntoIfBranches) does not reach it either: it fires on an eliminator whose scrutinee is directly an IfThenElse, not one hidden behind a Let binder. The runtime cost is the un-folded shape: both arms allocate a constructor table only for the tag to be read back off and the payload extracted.
Approach
An if-of-ctors sibling of propagateKnownCtorThroughLet: when a Let binder's RHS is an IfThenElse whose arms are saturated Ctor applications (of the same algebraic type) and the binder is read only through constructor-eliminating reads, distribute the reads arm-wise — e.g. rewrite the body's ReflectCtor v / DataArgumentByIndex i v occurrences under a per-arm binding, or equivalently push the whole Let body into the conditional when the duplication is admissible. The duplication concern is the same one #243 sidesteps: the body appears once per arm, so the safe subset needs either a cheapness gate on the body or the join points of #234, which is what makes the unrestricted version out of scope today.
Prerequisites / Relations
Sharpest after #234 (join points would remove the duplication constraint entirely); a gated subset (trivial bodies, or single eliminating read) can ship without it. Relates to #243 (the direct-scrutinee distribution), #214 (the Let-propagation machinery this would extend), and #206/#208 (whose specializations produce exactly such conditional-of-constructor RHSs at StateT-style boundaries).
Verification / Measurement
A focused unit test: let v = if b then Just x else Just y in (tag test + field read of v) folds to a conditional over the extracted payloads with no Ctor allocation left; a non-eliminating extra read of v declines. A golden showing the allocation disappear from the .lua, with eval output unchanged.
Problem
The constructor folds fire on a constructor that is syntactically in place:
reduceKnownConstructormatchesReflectCtor (Ctor …)andDataArgumentByIndex i (Ctor …), andpropagateKnownCtorThroughLet(#214) extends this through aStandaloneLet whose right-hand side is a saturatedCtorread only through constructor-eliminating reads. A conditional of known constructors slips through both.case (if b then Just x else Just y) of …let-binds the scrutinee (it is read more than once: a tag test plus a field read), so the eliminating reads seeReflectCtor (Ref v)andDataArgumentByIndex 0 (Ref v)withv = IfThenElse b (Ctor Just [x]) (Ctor Just [y])— a Ref, never the conditional ofCtors — and nothing folds. The #243 distribution (pushEliminatorIntoIfBranches) does not reach it either: it fires on an eliminator whose scrutinee is directly anIfThenElse, not one hidden behind a Let binder. The runtime cost is the un-folded shape: both arms allocate a constructor table only for the tag to be read back off and the payload extracted.Approach
An if-of-ctors sibling of
propagateKnownCtorThroughLet: when a Let binder's RHS is anIfThenElsewhose arms are saturatedCtorapplications (of the same algebraic type) and the binder is read only through constructor-eliminating reads, distribute the reads arm-wise — e.g. rewrite the body'sReflectCtor v/DataArgumentByIndex i voccurrences under a per-arm binding, or equivalently push the whole Let body into the conditional when the duplication is admissible. The duplication concern is the same one #243 sidesteps: the body appears once per arm, so the safe subset needs either a cheapness gate on the body or the join points of #234, which is what makes the unrestricted version out of scope today.Prerequisites / Relations
Sharpest after #234 (join points would remove the duplication constraint entirely); a gated subset (trivial bodies, or single eliminating read) can ship without it. Relates to #243 (the direct-scrutinee distribution), #214 (the Let-propagation machinery this would extend), and #206/#208 (whose specializations produce exactly such conditional-of-constructor RHSs at StateT-style boundaries).
Verification / Measurement
A focused unit test:
let v = if b then Just x else Just y in (tag test + field read of v)folds to a conditional over the extracted payloads with noCtorallocation left; a non-eliminating extra read ofvdeclines. A golden showing the allocation disappear from the.lua, with eval output unchanged.