Differences from Haskell

The Plinth contract language said that Plinth is Haskell, restricted to the part the compiler can translate to UPLC. This page maps out that restriction: which Haskell features Plinth does not support, the error message each one triggers, and — because none of these limits is arbitrary — the reason behind each one.

Almost every reason traces back to two properties of the target language. UPLC is a small, strict lambda calculus with a fixed set of builtin types and functions; and a script is re-evaluated by every node in the network, possibly years after it was written, where it must produce bit-identical results within a fixed execution budget. Features are unsupported either because UPLC has no counterpart for them, or because they would break that determinism.

Semantic differences

Before the unsupported features, two things that compile but behave differently from Haskell:

  • Function applications are strict. Evaluating (\x -> 42) (3 + 4) evaluates 3 + 4 first, even though x is unused. A lazy pattern on the parameter does not change this.
  • Bindings are non-strict, but not lazy. A let binding is by default not evaluated until used — but it is re-evaluated on every use, not memoized as in Haskell. A bang pattern (let !x = ...) makes a binding strict; under the Strict extension (which the standard Plinth flags turn on) bindings are strict by default and a lazy pattern (~) opts out.

Keep the re-evaluation in mind: a non-strict binding used three times costs its evaluation three times of the script budget.

How the compiler reports an unsupported feature

The Plinth compiler runs after GHC’s type checker, so these errors appear when the module is compiled, not in an IDE that only type-checks. An error carries a source location, a PLINTH error code, the reason, and usually a suggestion:

Auction.hs:9:10: error: [PLINTH-00004]
    Plinth Compilation Error:
    Context: Compiling code at Auction.hs:9:10-62:
             GHC.Num.Integer.integerEq
    Error: Unsupported feature: GHC.Classes.Eq.==, use PlutusTx.Eq.Eq
  |
9 | code = $$(PlutusTx.compile [|| \x -> x == (42 :: Integer) ||])
  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The Context: lines show the chain of code the compiler was processing, innermost last. They show GHC Core — the intermediate language the compiler consumes — so the code can look different from the source (== above appears as integerEq, the function GHC resolved it to).

The error codes are:

Code Meaning
PLINTH-00001 Error from the Plutus Core compiler
PLINTH-00002 Error from the PIR compiler
PLINTH-00003 Internal error; please report it to the Plutus team
PLINTH-00004 Unsupported feature
PLINTH-00005 Reference to a name without an accessible definition
PLINTH-00006 Misused compiler marker
PLINTH-00007 Internal name lookup error; please report it

The plugin option -fplugin-opt Plinth.Plugin:preserve-source-locations makes locations more precise: the compiler then tracks the locations of variables, class methods and literals through the compilation, at a small compile-time cost.

The unsupported features, and why

Each section names a feature, shows the error it triggers, and explains the reason.

Machine integers: Int, Word, Word8, …, Word64

Error: Unsupported feature: Int: use Integer instead

UPLC has one integer type: arbitrary-precision integer, which Integer maps onto. Fixed-width types silently wrap around on overflow — behavior you do not want anywhere near code that moves funds — and UPLC has no fixed-width arithmetic to compile them to. Use Integer.

Floating point: Double, Float

Error: Unsupported feature: Type GHC.Types.Double is not supported in Plinth; use Integer or PlutusTx.Ratio.Rational instead

UPLC has no floating-point builtins, deliberately: floating-point results can differ between platforms and optimization levels, while every node must compute exactly the same result. Use Integer, or PlutusTx.Ratio.Rational for exact fractions.

Characters and String literals

Error: Unsupported feature: Literal string (maybe you need to use OverloadedStrings)
Error: Unsupported feature: Literal char

Haskell’s String is a linked list of Char, and UPLC has no character type. Text on-chain is the builtin string type, surfaced as BuiltinString. Enable OverloadedStrings so string literals produce BuiltinString values; use them mainly for trace messages, and BuiltinByteString for data.

Data.Text and Data.ByteString

Error: Unsupported feature: Type Data.Text.Internal.Text is not supported in Plinth; use BuiltinString instead
Error: Unsupported feature: Type Data.ByteString.Internal.Type.ByteString is not supported in Plinth; use BuiltinByteString instead

These types wrap memory buffers managed by the GHC runtime, and there is no GHC runtime on-chain. Their on-chain equivalents are the builtins BuiltinString and BuiltinByteString.

Type classes from base: Eq, Ord, Show, Enum

Error: Unsupported feature: GHC.Classes.Eq.==, use PlutusTx.Eq.Eq
Error: Unsupported feature: GHC.Show.Show.show, use PlutusTx.Show.Show

The base instances of these classes are compiled for the GHC runtime: they work on machine integers and produce String values. PlutusTx.Prelude provides on-chain versions — PlutusTx.Eq, PlutusTx.Ord, PlutusTx.Show, PlutusTx.Enum — whose methods compile to UPLC builtins. Import it and you keep writing == and compare; they simply resolve to the on-chain classes.

Range syntax

Error: Unsupported feature: Range syntax, use PlutusTx.Enum.enumFromTo or PlutusTx.Enum.enumFromThenTo
Error: Unsupported feature: Unbounded range syntax: unbounded ranges are not supported

[a .. b] is sugar for methods of the base Enum class, which is unsupported. Use PlutusTx.Enum.enumFromTo or enumFromThenTo — and note they build the whole list, at a budget cost proportional to its length. An unbounded range such as [a ..] describes an infinite list; under strict, budgeted evaluation an infinite structure can never be evaluated, so there is no useful way to compile one.

error and undefined from Prelude

Error: Unsupported feature: GHC.Err.error, use PlutusTx.Prelude.error or PlutusTx.Prelude.traceError

Prelude.error takes a String message and an implicit call stack, and raises a Haskell runtime exception. None of that exists on-chain: script failure is the UPLC error term, and diagnostic messages are trace strings. Use PlutusTx.Prelude.error, or traceError to attach a message.

IO and FFI

Error: Unsupported feature: IO actions are not supported in Plinth

A validator is a pure function. Every node re-runs it and must reach the same verdict, and there is no runtime system on-chain to perform effects. Do effectful work off-chain and pass the results to the script as arguments.

Pattern matching on Integer literals

Error: Unsupported feature: Cannot pattern match on a value of type 'Integer'.

Pattern matching compiles to case analysis on data constructors, and the builtin integer type has no constructors. A literal pattern like f 42 = ... desugars into matches on the internal representation of Integer, which exposes machine words. Use equality with guards instead:

f n | n == 42   = ...
    | otherwise = ...

GHC optimizations can also produce such matches from innocent-looking code; the full error message lists the flags that prevent this (the standard Plinth flags include them).

Recursive newtypes

Error: Unsupported feature: Recursive newtypes, use data: Auction.Stream

A newtype compiles to a transparent type alias, which is what makes it free at runtime — and a recursive alias would unfold forever. A data type compiles to a real datatype, which supports recursion; use data for recursive types.

Mutually recursive data types

Error: Error from the PIR compiler:
       Unsupported construct: Mutually recursive datatypes: Forest, Rose ({ Auction.hs:9:1-9:27 })

The compiler’s intermediate language encodes a recursive datatype as a fixed point of a single type. There is no encoding for a group of types that recurse through each other yet. Merge the group into a single datatype, or break the cycle by inlining one type into the other.

Existential types and GADTs

Error: Unsupported feature: Existential quantification in data constructor Auction.Box
Error: Unsupported feature: Following extensions are not supported: GADTs

On-chain datatypes are plain sums of products: a constructor takes value arguments whose types only mention the datatype’s own type parameters. A constructor cannot bind its own type variables (existentials) or refine the result type (GADTs) — the datatype encoding has no representation for either. The GADTs extension is rejected as a whole in modules that contain compiled code.

Type families

Error: Unsupported feature: Irreducible type family application: Auction.F

There is no type-level computation on-chain. A type family application compiles only when GHC fully reduces it during type checking; an application that remains — an open family with no matching instance, say — cannot be translated.

Kind polymorphism (PolyKinds)

Error: Unsupported feature: Following extensions are not supported: PolyKinds

On-chain kinds are * and arrow kinds only; there are no kind variables.

Functions without unfoldings

Error: Reference to a name which is not a local, a builtin, or an external function with an available definition: Variable Auction.opaque
       No unfolding
       The definition of ‘Auction.opaque’ is not available. Remove its NOINLINE or OPAQUE pragma.

The compiler translates the GHC Core definition (the “unfolding”) of every function the compiled code references. plinthc keeps the unfoldings of all definitions without INLINEABLE pragmas, except when a NOINLINE or OPAQUE pragma hides the definition — see Unfoldings and INLINEABLE.

Further reading