Skip to content

[Builtins] Allow casing on booleans and integers #7029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
06d1a16
[Builtins] Allow casing on booleans
effectfully Apr 9, 2025
2c5a658
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully Apr 17, 2025
89f9909
Restore the original case-of-case
effectfully Apr 19, 2025
9e65c92
Proper error messages, support for old 'IfThenElse'
effectfully Apr 29, 2025
5f0c523
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully Apr 29, 2025
50689dc
Make the default branch correspond to 'False'
effectfully May 16, 2025
d0a5a27
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully May 16, 2025
43789e2
Fix conformance tests
effectfully May 19, 2025
8173d7c
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully May 22, 2025
15f15b9
Add 'Case on constants' tests
effectfully May 22, 2025
eaf8284
Docs
effectfully May 22, 2025
7c5387e
Cosmetics
effectfully May 22, 2025
b63ff2f
Fix a test
effectfully May 23, 2025
35c61d8
Apply 'fourmolu' to 'plutus-tx'
effectfully May 23, 2025
dafd361
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully May 23, 2025
9ac9643
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully May 30, 2025
5fad6c1
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully May 30, 2025
7826ad0
Remove redudant golden files
effectfully May 31, 2025
b5aff71
Remove 'term' from 'CaseBuiltin'
effectfully Jun 2, 2025
d404be1
Address comments
effectfully Jun 2, 2025
81948ad
Allow constants in case-of-case too
effectfully Jun 3, 2025
82d703f
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully Jun 3, 2025
cabbbe3
A failed experiment to add 'caserBuiltin' to 'MachineParameters'
effectfully Jun 4, 2025
8d72e6c
Add versioning for casing
effectfully Jun 5, 2025
2748dbb
Add 'BuiltinCasing' to fix 'plutus-ledger-api' tests
effectfully Jun 10, 2025
2338a26
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully Jun 11, 2025
7901ba5
Fix constitution tests
effectfully Jun 11, 2025
d221ef4
Fix 'cost-model'
effectfully Jun 11, 2025
9fa4ef3
Add a comment to 'CaseOfCase'
effectfully Jun 11, 2025
29c1442
Fix golden tests
effectfully Jun 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plutus-benchmark/common/PlutusBenchmark/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import PlutusBenchmark.ProtocolParameters as PP
import PlutusLedgerApi.Common qualified as LedgerApi

import PlutusCore qualified as PLC
import PlutusCore.Builtin qualified as PLC
import PlutusCore.Default
import PlutusCore.Evaluation.Machine.ExBudget (ExBudget (..))
import PlutusCore.Evaluation.Machine.ExBudgetingDefaults qualified as PLC
Expand Down Expand Up @@ -97,6 +98,7 @@ mkEvalCtx ll semvar =
let errOrCtx =
LedgerApi.mkDynEvaluationContext
ll
(\_ -> PLC.CaserBuiltin PLC.caseBuiltin)
[semvar]
(const semvar)
p
Expand Down
5 changes: 3 additions & 2 deletions plutus-conformance/haskell-steppable/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module Main (main) where

import PlutusConformance.Common
import PlutusCore.Evaluation.Machine.MachineParameters qualified as UPLC
import PlutusCore.Evaluation.Machine.MachineParameters.Default
import PlutusPrelude
import UntypedPlutusCore as UPLC
Expand Down Expand Up @@ -31,9 +32,9 @@ failingBudgetTests = []
evalSteppableUplcProg :: UplcEvaluator
evalSteppableUplcProg = UplcEvaluatorWithCosting $
\modelParams (UPLC.Program a v t) -> do
params <- case mkMachineParametersFor [def] modelParams of
params <- case mkMachineVariantParametersFor [def] modelParams of
Left _ -> Nothing
Right machParamsList -> lookup def machParamsList
Right machParamsList -> UPLC.MachineParameters def <$> lookup def machParamsList
-- runCek-like functions (e.g. evaluateCekNoEmit) are partial on term's with
-- free variables, that is why we manually check first for any free vars
case UPLC.deBruijnTerm t of
Expand Down
5 changes: 3 additions & 2 deletions plutus-conformance/haskell/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module Main (main) where

import PlutusConformance.Common (UplcEvaluator (..), runUplcEvalTests)
import PlutusCore.Evaluation.Machine.MachineParameters qualified as UPLC
import PlutusCore.Evaluation.Machine.MachineParameters.Default
import PlutusPrelude (def)
import UntypedPlutusCore qualified as UPLC
Expand All @@ -13,9 +14,9 @@ import UntypedPlutusCore.Evaluation.Machine.Cek (CountingSt (..), counting, runC
evalUplcProg :: UplcEvaluator
evalUplcProg = UplcEvaluatorWithCosting $ \modelParams (UPLC.Program a v t) ->
do
params <- case mkMachineParametersFor [def] modelParams of
params <- case mkMachineVariantParametersFor [def] modelParams of
Left _ -> Nothing
Right machParamsList -> lookup def machParamsList
Right machParamsList -> UPLC.MachineParameters def <$> lookup def machParamsList
-- runCek-like functions (e.g. evaluateCekNoEmit) are partial on term's with
-- free variables, that is why we manually check first for any free vars
case UPLC.deBruijnTerm t of
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- case of non-constr
(program 1.1.0
(case (con integer 1) (lam x x) (lam x x))
(case (lam x x) (lam x x) (lam x x))
)
2 changes: 1 addition & 1 deletion plutus-core/cost-model/budgeting-bench/Benchmarks/Nops.hs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ nopCostModel =

nopCostParameters :: MachineParameters CekMachineCosts NopFun (CekValue DefaultUni NopFun ())
nopCostParameters =
mkMachineParameters def $
MachineParameters def . mkMachineVariantParameters def $
CostModel defaultCekMachineCostsForTesting nopCostModel

-- This is just to avoid some deeply nested case expressions for the NopNc
Expand Down
1 change: 1 addition & 0 deletions plutus-core/plutus-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ library
Data.Aeson.Flatten
Data.Functor.Foldable.Monadic
Data.Vector.Orphans
PlutusCore.Builtin.Case
PlutusCore.Builtin.HasConstant
PlutusCore.Builtin.KnownKind
PlutusCore.Builtin.KnownType
Expand Down
1 change: 1 addition & 0 deletions plutus-core/plutus-core/src/PlutusCore/Builtin.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module PlutusCore.Builtin
( module Export
) where

import PlutusCore.Builtin.Case as Export
import PlutusCore.Builtin.HasConstant as Export
import PlutusCore.Builtin.KnownKind as Export
import PlutusCore.Builtin.KnownType as Export
Expand Down
51 changes: 51 additions & 0 deletions plutus-core/plutus-core/src/PlutusCore/Builtin/Case.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}

module PlutusCore.Builtin.Case where

import PlutusCore.Core.Type (Type, UniOf)
import PlutusCore.Name.Unique

import Control.DeepSeq (NFData (..), rwhnf)
import Data.Default.Class (Default (..))
import Data.Text (Text)
import Data.Vector (Vector)
import NoThunks.Class
import Text.PrettyBy (display)
import Universe

class AnnotateCaseBuiltin uni where
-- | Given a tag for a built-in type and a list of branches, annotate each of the branches with
-- its expected argument types or fail if casing on values of the built-in type isn't supported.
annotateCaseBuiltin
:: UniOf term ~ uni
=> SomeTypeIn uni
-> [term]
-> Either Text [(term, [Type TyName uni ann])]

class CaseBuiltin uni where
-- | Given a constant with its type tag and a vector of branches, choose the appropriate branch
-- or fail if the constant doesn't correspond to any of the branches (or casing on constants of
-- this type isn't supported at all).
caseBuiltin :: UniOf term ~ uni => Some (ValueOf uni) -> Vector term -> Either Text term

data CaserBuiltin uni = CaserBuiltin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems CaserBuiltin should render the CaseBuiltin class superfluous, since an instance of CaseBuiltin can just be represented with a CaserBuiltin value?

Also, why not newtype?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do use CaseBuiltin as an actual constraint

image

Also, why not newtype?

See this for a similar case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do use CaseBuiltin as an actual constraint

Ok, so Case is used to define how a universe normally handles casing on builtins, and Caser exists so that it can vary based on protocol version. I guess that's fine, but not easy to tell, and their similar names don't help.

See this for a similar case.

Create a Note and link to it?

{ unCaserBuiltin
:: !(forall term. UniOf term ~ uni => Some (ValueOf uni) -> Vector term -> Either Text term)
}

instance NFData (CaserBuiltin uni) where
rnf = rwhnf

deriving via OnlyCheckWhnfNamed "PlutusCore.Builtin.Case.CaserBuiltin" (CaserBuiltin uni)
instance NoThunks (CaserBuiltin uni)

instance CaseBuiltin uni => Default (CaserBuiltin uni) where
def = CaserBuiltin caseBuiltin

unavailableCaserBuiltin :: Int -> CaserBuiltin uni
unavailableCaserBuiltin ver =
CaserBuiltin $ \_ _ -> Left $
"'case' on values of built-in types is not supported in protocol version " <> display ver
9 changes: 7 additions & 2 deletions plutus-core/plutus-core/src/PlutusCore/Compiler/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

module PlutusCore.Compiler.Types where

import Data.Hashable
import PlutusCore
import PlutusCore.Builtin
import PlutusCore.Name.Unique
import PlutusCore.Quote

import Data.Hashable

type Compiling m uni fun name a =
( ToBuiltinMeaning uni fun
, MonadQuote m
, CaseBuiltin uni
, GEq uni
, Closed uni
, Everywhere uni Eq
, HasUnique name TermUnique
, Ord name
, Typeable name
Expand Down
54 changes: 44 additions & 10 deletions plutus-core/plutus-core/src/PlutusCore/Default/Universe.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
-- to test that some constraints are solvable
{-# OPTIONS -Wno-redundant-constraints #-}

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
Expand All @@ -20,6 +21,7 @@
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
Expand Down Expand Up @@ -58,7 +60,8 @@ import Data.Proxy (Proxy (Proxy))
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Typeable (typeRep)
import Data.Vector.Strict (Vector)
import Data.Vector qualified as Vector
import Data.Vector.Strict qualified as Strict (Vector)
import Data.Word (Word16, Word32, Word64)
import GHC.Exts (inline, oneShot)
import Text.PrettyBy.Fixity (RenderContext, inContextM, juxtPrettyM)
Expand Down Expand Up @@ -106,7 +109,7 @@ data DefaultUni a where
DefaultUniString :: DefaultUni (Esc Text)
DefaultUniUnit :: DefaultUni (Esc ())
DefaultUniBool :: DefaultUni (Esc Bool)
DefaultUniProtoArray :: DefaultUni (Esc Vector)
DefaultUniProtoArray :: DefaultUni (Esc Strict.Vector)
DefaultUniProtoList :: DefaultUni (Esc [])
DefaultUniProtoPair :: DefaultUni (Esc (,))
DefaultUniApply :: !(DefaultUni (Esc f)) -> !(DefaultUni (Esc a)) -> DefaultUni (Esc (f a))
Expand Down Expand Up @@ -261,7 +264,7 @@ instance DefaultUni `Contains` Bool where
knownUni = DefaultUniBool
instance DefaultUni `Contains` [] where
knownUni = DefaultUniProtoList
instance DefaultUni `Contains` Vector where
instance DefaultUni `Contains` Strict.Vector where
knownUni = DefaultUniProtoArray
instance DefaultUni `Contains` (,) where
knownUni = DefaultUniProtoPair
Expand All @@ -286,8 +289,8 @@ instance KnownBuiltinTypeAst tyname DefaultUni Bool =>
KnownTypeAst tyname DefaultUni Bool
instance KnownBuiltinTypeAst tyname DefaultUni [a] =>
KnownTypeAst tyname DefaultUni [a]
instance KnownBuiltinTypeAst tyname DefaultUni (Vector a) =>
KnownTypeAst tyname DefaultUni (Vector a)
instance KnownBuiltinTypeAst tyname DefaultUni (Strict.Vector a) =>
KnownTypeAst tyname DefaultUni (Strict.Vector a)
instance KnownBuiltinTypeAst tyname DefaultUni (a, b) =>
KnownTypeAst tyname DefaultUni (a, b)
instance KnownBuiltinTypeAst tyname DefaultUni Data =>
Expand All @@ -313,8 +316,8 @@ instance KnownBuiltinTypeIn DefaultUni term Data =>
ReadKnownIn DefaultUni term Data
instance KnownBuiltinTypeIn DefaultUni term [a] =>
ReadKnownIn DefaultUni term [a]
instance KnownBuiltinTypeIn DefaultUni term (Vector a) =>
ReadKnownIn DefaultUni term (Vector a)
instance KnownBuiltinTypeIn DefaultUni term (Strict.Vector a) =>
ReadKnownIn DefaultUni term (Strict.Vector a)
instance KnownBuiltinTypeIn DefaultUni term (a, b) =>
ReadKnownIn DefaultUni term (a, b)
instance KnownBuiltinTypeIn DefaultUni term BLS12_381.G1.Element =>
Expand All @@ -338,8 +341,8 @@ instance KnownBuiltinTypeIn DefaultUni term Data =>
MakeKnownIn DefaultUni term Data
instance KnownBuiltinTypeIn DefaultUni term [a] =>
MakeKnownIn DefaultUni term [a]
instance KnownBuiltinTypeIn DefaultUni term (Vector a) =>
MakeKnownIn DefaultUni term (Vector a)
instance KnownBuiltinTypeIn DefaultUni term (Strict.Vector a) =>
MakeKnownIn DefaultUni term (Strict.Vector a)
instance KnownBuiltinTypeIn DefaultUni term (a, b) =>
MakeKnownIn DefaultUni term (a, b)
instance KnownBuiltinTypeIn DefaultUni term BLS12_381.G1.Element =>
Expand Down Expand Up @@ -518,6 +521,37 @@ instance KnownBuiltinTypeIn DefaultUni term Integer => ReadKnownIn DefaultUni te
]
{-# INLINE readKnown #-}

outOfBoundsErr :: Pretty a => a -> Vector.Vector term -> Text
outOfBoundsErr x branches = fold
[ "'case "
, display x
, "' is out of bounds for the given number of branches: "
, display $ Vector.length branches
]

instance AnnotateCaseBuiltin DefaultUni where
annotateCaseBuiltin (SomeTypeIn uni) branches = case uni of
DefaultUniBool -> Right $ map (, []) branches
DefaultUniInteger -> Right $ map (, []) branches
_ -> Left $ display uni <> " isn't supported in 'case'"

instance CaseBuiltin DefaultUni where
caseBuiltin (Some (ValueOf uni x)) branches = case uni of
DefaultUniBool -> case x of
-- We allow there to be only one branch as long as the scrutinee is 'False'.
-- This is strictly to save size by not having the 'True' branch if it was gonna be
-- 'Error' anyway.
False | len == 1 || len == 2 -> Right $ branches Vector.! 0
True | len == 2 -> Right $ branches Vector.! 1
_ -> Left $ outOfBoundsErr x branches
DefaultUniInteger
| 0 <= x && x < toInteger len -> Right $ branches Vector.! fromInteger x
| otherwise -> Left $ outOfBoundsErr x branches
_ -> Left $ display uni <> " isn't supported in 'case'"
where
!len = Vector.length branches
{-# INLINE caseBuiltin #-}

{- Note [Stable encoding of tags]
'encodeUni' and 'decodeUni' are used for serialisation and deserialisation of types from the
universe and we need serialised things to be extremely stable, hence the definitions of 'encodeUni'
Expand All @@ -534,7 +568,7 @@ instance Closed DefaultUni where
, constr `Permits` ()
, constr `Permits` Bool
, constr `Permits` []
, constr `Permits` Vector
, constr `Permits` Strict.Vector
, constr `Permits` (,)
, constr `Permits` Data
, constr `Permits` BLS12_381.G1.Element
Expand Down
6 changes: 6 additions & 0 deletions plutus-core/plutus-core/src/PlutusCore/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ data TypeError term uni fun ann
| FreeTypeVariableE !ann !TyName
| FreeVariableE !ann !Name
| UnknownBuiltinFunctionE !ann !fun
| UnsupportedCaseBuiltin !ann !T.Text
deriving stock (Show, Eq, Generic, Functor)
deriving anyclass (NFData)

Expand Down Expand Up @@ -270,6 +271,11 @@ instance (Pretty term, PrettyUni uni, Pretty fun, Pretty ann) =>
, pretty $ name1 ^. theUnique
, "is attempted to be referenced"
]
prettyBy _ (UnsupportedCaseBuiltin ann err) = hsep
[ "Unsupported 'case' of a value of a built-in type at"
, pretty ann <> ":"
, pretty err
]

instance (PrettyUni uni, Pretty fun, Pretty ann) =>
PrettyBy PrettyConfigPlc (Error uni fun ann) where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,20 @@ faster than the used in production. Also see Note [noinline for saving on
ticks]. -}
defaultCekParametersA :: Typeable ann => MachineParameters CekMachineCosts DefaultFun (CekValue DefaultUni DefaultFun ann)
defaultCekParametersA =
noinline mkMachineParameters DefaultFunSemanticsVariantA cekCostModelVariantA
MachineParameters def $
noinline mkMachineVariantParameters DefaultFunSemanticsVariantA cekCostModelVariantA

-- See Note [No inlining for MachineParameters]
defaultCekParametersB :: Typeable ann => MachineParameters CekMachineCosts DefaultFun (CekValue DefaultUni DefaultFun ann)
defaultCekParametersB =
noinline mkMachineParameters DefaultFunSemanticsVariantB cekCostModelVariantB
MachineParameters def $
noinline mkMachineVariantParameters DefaultFunSemanticsVariantB cekCostModelVariantB

-- See Note [No inlining for MachineParameters]
defaultCekParametersC :: Typeable ann => MachineParameters CekMachineCosts DefaultFun (CekValue DefaultUni DefaultFun ann)
defaultCekParametersC =
noinline mkMachineParameters DefaultFunSemanticsVariantC cekCostModelVariantC
MachineParameters def $
noinline mkMachineVariantParameters DefaultFunSemanticsVariantC cekCostModelVariantC

{- Note [noinline for saving on ticks]
We use 'noinline' purely for saving on simplifier ticks for definitions, whose performance doesn't
Expand Down Expand Up @@ -357,5 +360,6 @@ unitCostBuiltinCostModel = BuiltinCostModelBase
unitCekParameters :: Typeable ann => MachineParameters CekMachineCosts DefaultFun (CekValue DefaultUni DefaultFun ann)
unitCekParameters =
-- See Note [noinline for saving on ticks].
noinline mkMachineParameters def $
CostModel unitCekMachineCosts unitCostBuiltinCostModel
MachineParameters def $
noinline mkMachineVariantParameters def $
CostModel unitCekMachineCosts unitCostBuiltinCostModel
Loading
Loading