Skip to content

feat: add enum declarations and multi-arm enum match expressions#336

Open
stringhandler wants to merge 1 commit into
BlockstreamResearch:masterfrom
stringhandler:feat/multiple-match-arms
Open

feat: add enum declarations and multi-arm enum match expressions#336
stringhandler wants to merge 1 commit into
BlockstreamResearch:masterfrom
stringhandler:feat/multiple-match-arms

Conversation

@stringhandler
Copy link
Copy Markdown
Contributor

Introduces enum with explicit u8 discriminants and N-arm match over enum variants, desugared into jet::eq_8 comparison chains at the AST level. Missing witness values are zero-filled before Simplicity witness population; a post-prune check errors if any zero-filled witness appears on a surviving (non-pruned) branch.

Example

// last_will.simf


enum Action {
    Inherit=1,
    ColdSpend =2,
    HotSpend =3,
}

fn main() {
    match witness::ACTION {
        Action::Inherit => { 
            let inheritor_sig: Signature = witness::INHERITOR_SIG;
            inherit_spend(inheritor_sig)} ,
        Action::ColdSpend => {
            let cold_sig: Signature = witness::COLD_SIG;
            cold_spend(cold_sig) },
        Action::HotSpend => {
            let hot_sig: Signature = witness::HOT_SIG;
            refresh_spend(hot_sig) },
    }
}
// last_will.inherit.wit
{
    "ACTION": {
        "value": "1",
        "type": "u8"
    },
    "INHERITOR_SIG": {
        "value": "0x755201bb62b0a8b8d18fd12fc02951ea3998ba42bfc6664daaf8a0d2298cad43cdc21358c7c82f37654275dc2fea8c858adbe97bac92828b498a5a237004db6f",
        "type": "Signature"
    }
}

Implementation notes

Some things to note that are missing:

1. Enums with structured fields.

I originally wanted to implement enums as something like

enum Path {
   Inherit { signature: Signature}
}

or potentially even with tuples, like Inherit(<inner tuple>). This would certainly look cleaner in the witness file (i.e. as ACTION: { value = "Inherit("0x....")}, however the enum declaration is not present in scope when parsing the file and this makes decoding it difficult.
As a trade off enums must have a u8 encoding, so that they can be parsed from the witness. This explicit declaration should also help when the transaction is examined on the explorer.

Structured enums can possibly be implemented in a future PR.

2. Empty witness filling

By splitting the action/path witness variable into it's own u8, we now have to specify individual witness variables for each of the actions/paths taken in the program. This means that we would now have to specify values for all witnesses, even if they are not called.

I did a deep dive and confirmed that unused witness values are pruned, but calling a program with a witness file or programmatically is still inconvenient. To combat this, I've added a step to fill unspecified witness values with defaults of zero, and then an additional step to make sure that none of the filled values remain when pruned. This is done through satisfy_with_env, so lib callers should not have to do any extra lifting.

3. Enum values in code are out of scope

A limit of the implementation is that using enums in code is not supported. This should be done in another PR.
For example, the following is not supported:

enum Direction {
  Up =1,
  Down = 2
}

fn return_up() -> Direction {
  Direction::Up
}

This is unfortunate but the PR is already quite large and tricky to review.

4. Desugaring into jet::eq_u8 chains

Under the hood, the match statement is desugared into a list of ifs (actually match if you are specific), each with a jet::eq_u8.
While it might have been more efficient to utilize an Either::Left structure, hoping for a tree like evaluation, it was difficult to decode a u8 reliably into Left/Right nodes and allow for skipping enum values (e..g enum ACTION { One = 1, /* Two = 2 --deprecated */, Three = 3 } ). That said, it is not impossible, and I can (reluctantly) create another PR for that implementation if desired.

@stringhandler stringhandler requested a review from delta1 as a code owner May 28, 2026 12:40
@apoelstra
Copy link
Copy Markdown
Contributor

3666ffb needs rebase

@stringhandler stringhandler force-pushed the feat/multiple-match-arms branch from 3666ffb to ce66af6 Compare May 28, 2026 12:59
Introduces `enum` with explicit u8 discriminants and N-arm `match` over
enum variants, desugared into `jet::eq_8` comparison chains at the AST
level. Missing witness values are zero-filled before Simplicity witness
population; a post-prune check errors if any zero-filled witness appears
on a surviving (non-pruned) branch.
@stringhandler stringhandler force-pushed the feat/multiple-match-arms branch from ce66af6 to 0eb01e6 Compare May 28, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants