@@ -293,8 +293,10 @@ data CekValue uni fun ann
293293 | VLamAbs ! NamedDeBruijn ! (NTerm uni fun ann ) ! (CekValEnv uni fun ann )
294294 | {-| A partial builtin application, accumulating arguments for eventual full application.
295295 We don't need a 'CekValEnv' here unlike in the other constructors, because 'VBuiltin'
296- values always store their corresponding 'Term's fully discharged, see the comments at
297- the call sites (search for 'VBuiltin'). -}
296+ values store their corresponding 'Term's fully discharged, see the comments at
297+ the call sites (search for 'VBuiltin'). Note however that a 'VBuiltin' /can/ be stored
298+ in an environment (e.g. when passed as an argument to a lambda), so 'dischargeCekValue'
299+ must still shift its free variables when discharging under additional binders. -}
298300 VBuiltin
299301 ! fun
300302 {-^ So that we know, for what builtin we're calculating the cost. We can sneak this into
@@ -673,11 +675,10 @@ dischargeCekValue value0 = DischargeNonConstant $ goValue 0 value0
673675 -- We only return a discharged builtin application when (a) it's being returned by the
674676 -- machine, or (b) it's needed for an error message.
675677 -- @term@ is fully discharged, so we can return it directly without any further discharging.
676- -- In particular, no @global@ shifting is needed because the @term@ field of 'VBuiltin'
677- -- is maintained during evaluation as a fully-applied UPLC term whose variables already
678- -- refer to the correct scope — it is never stored in an environment to be discharged
679- -- under additional binders later.
680- VBuiltin _ term _ -> term
678+ -- However, @global@ shifting IS needed because a 'VBuiltin' can be stored in an
679+ -- environment and later discharged under additional binders — e.g. when a partially
680+ -- applied builtin containing free variables is passed as an argument to a lambda.
681+ VBuiltin _ term _ -> shiftTerm global term
681682 VConstr ind args -> Constr () ind . map (goValue global) $ argStackToList args
682683
683684 -- Instantiate all the free variables of a term by looking them up in an environment.
@@ -712,6 +713,27 @@ dischargeCekValue value0 = DischargeNonConstant $ goValue 0 value0
712713 Constr _ ind args -> Constr () ind $ map (go global shift) args
713714 Case _ scrut alts -> Case () (go global shift scrut) $ fmap (go global shift) alts
714715
716+ -- \| Shift all free variables in a fully discharged term by the given amount.
717+ -- Used for 'VBuiltin' terms which have no associated environment.
718+ shiftTerm :: Word64 -> NTerm uni fun () -> NTerm uni fun ()
719+ shiftTerm 0 t = t
720+ shiftTerm amount t = goShift 0 t
721+ where
722+ goShift :: Word64 -> NTerm uni fun () -> NTerm uni fun ()
723+ goShift ! depth = \ case
724+ Var _ named@ (NamedDeBruijn _ (coerce -> idx))
725+ | depth >= idx -> Var () named
726+ | otherwise -> Var () (shiftNamedDeBruijn amount named)
727+ LamAbs _ name body -> LamAbs () name $ goShift (depth + 1 ) body
728+ Apply _ fun arg -> Apply () (goShift depth fun) (goShift depth arg)
729+ Delay _ term -> Delay () $ goShift depth term
730+ Force _ term -> Force () $ goShift depth term
731+ Constant _ val -> Constant () val
732+ Builtin _ fun -> Builtin () fun
733+ Error _ -> Error ()
734+ Constr _ ind args -> Constr () ind $ map (goShift depth) args
735+ Case _ scrut alts -> Case () (goShift depth scrut) $ fmap (goShift depth) alts
736+
715737instance (PrettyUni uni , Pretty fun ) => PrettyBy PrettyConfigPlc (CekValue uni fun ann ) where
716738 prettyBy cfg = prettyBy cfg . dischargeResultToTerm . dischargeCekValue
717739
0 commit comments