Skip to content

Allow multidep enums - #414

Open
LesterEvSe wants to merge 3 commits into
BlockstreamResearch:masterfrom
LesterEvSe:feat/multifile-enums
Open

Allow multidep enums#414
LesterEvSe wants to merge 3 commits into
BlockstreamResearch:masterfrom
LesterEvSe:feat/multifile-enums

Conversation

@LesterEvSe

@LesterEvSe LesterEvSe commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

enum cannot be used in any program that the driver wraps in a module, which is
every program, since flattening wraps the entry file too. Two checks reject the
declaration: parse::Module::validate rejects enum inside a mod block, and
forbid_enum_dec_in_deps rejects enum in a dependency file. Both exist because an
enum's identity in the ABI was its bare declared name, which forced the name to be
unique program-wide.

This blocks enum in Simplex entirely, and in any multi-file
program.

Fix

The ABI no longer identifies an enum by its bare name. It identifies it by the path of
the file the enum was declared in, the module chain inside that file, and the full
expanded type.

The path is what makes this work across dependencies, so a consumer knows which file an
enum comes from.

Previous abi for last_will.simf file:

AbiMeta { witness_types: WitnessTypes({TemplateProgramWitness { inner: Witness("ACTION") }: Action}), param_types: Parameters({}) }

Current:

AbiOutput { witness_types: {"ACTION": "<path>(::<modules>): Action { Inherit([u8; 64]), ColdSpend([u8; 64]), HotSpend([u8; 64]) }"}, param_types: {} }

The <modules> segment is omitted when the enum is declared at the top level of its
file; the driver-assigned unit_N wrapper is never shown.

@LesterEvSe
LesterEvSe requested a review from KyrylR September 4, 2026 14:07
@LesterEvSe LesterEvSe self-assigned this Sep 4, 2026
@LesterEvSe
LesterEvSe requested a review from delta1 as a code owner September 4, 2026 14:07
@LesterEvSe LesterEvSe added bug Something isn't working enhancement New feature or request labels Sep 4, 2026
@apoelstra

Copy link
Copy Markdown
Contributor

Are there design documents or something about this way of identifying modules? Have you looked at https://community.simplicity-lang.org/t/nominal-types-and-module-identification-in-simplicityhl/62/3

@apoelstra

Copy link
Copy Markdown
Contributor

Also, in what sense are enums "special"? It seems like we are implementing a half-baked form of nominal typing for enums, and only enums. For example using the cast_preserves_enum_identity we attempt to reject casts between structurally-equivalent enums, but do so in a poorly-specified way that has multiple bugs.

@LesterEvSe

LesterEvSe commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

I think @KyrylR can give a better answer to the question "why are enums special."
Thanks for the link, I read it, now I have better understanding of the global problem.
Enums were written before that thread, I think it was an attempt to introduce nominal typing without intervening in the type system at all, so from that perspective it looks half-baked. It's still better than having no nominal typing at all, but it wasn't the end goal.

Regarding the module system: it currently supports inline mod, but it doesn't support importing a whole moduleб only individual items out of one (functions, type aliases, and enums).

So something like this doesn't currently work:

mod A {
    pub mod B {
        pub fn smth() {}
    }
}

use crate::A::B;
use B::smth; // error

fn main() {}

But we can get smth() directly with use crate::A::B::smth;.

On @stringhandler's main question, about single-file and multi-file programs having an exact equivalent, I think yes, and that's the main reason flattening exists at all (#337, more detailed in linked issue). To show it concretely rather than just assert it: I compiled both of the following programs, and they produce the identical CMR:

fn add_32(a: u32, b: u32) -> u32 {
    let (_, res): (bool, u32) = jet::add_32(a, b);
    res
}

fn main() {
    let a: u32 = witness::A;
    let b: u32 = witness::B;
    let c: u32 = witness::C;

    assert!(jet::eq_32(c, add_32(a, b)));
}

And:

mod X {
    pub fn add_32(a: u32, b: u32) -> u32 {
        let (_, res): (bool, u32) = jet::add_32(a, b);
        res
    }
}

use crate::X::add_32;

fn main() {
    let a: u32 = witness::A;
    let b: u32 = witness::B;
    let c: u32 = witness::C;

    assert!(jet::eq_32(c, add_32(a, b)));
}

Both give CMR f4b723f55dd43e37755c7fc049b793fd2ffc3d82276e243d7d84c03897a2656e.

@LesterEvSe

Copy link
Copy Markdown
Collaborator Author

Get back to enums.
If we plan to rewrite the types, we'll have to rewrite the enums to accommodate that anyway. For now, I haven't taken type conversion into account and have restricted enums to be strictly nominal. In the future, it will be easier to relax this restriction than to do the opposite.

Regarding their usage in std (u32_conver_test.simf), simplicity-lending, and price-oracle, it would be much more convenient and optimized to use enums instead of custom match branching

@apoelstra

Copy link
Copy Markdown
Contributor

It's still better than having no nominal typing at all, but it wasn't the end goal.

It's not, because it creates behavior that we need to maintain, doesn't make sense to users, and which will be difficult to shoehorn into a properly-designd system.

I don't think we should keep grafting ad-hoc extensions onto the existing enum implementation, nor onto the existing module system. We should fix the types and module systems properly.

@schoen

schoen commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

I have no particular opinion about the architectural/design question here, but I just wanted to mention that the issue that this solves is blocking a new Simplex demo that I made based on last_will.simf, which now uses enum. The problem there is that Simplex itself automatically wraps programs under test in a module, causing the enum feature as a whole to be incompatible with Simplex projects.

I can confirm from testing that this PR does effectively remove that limitation in Simplex, and so, with other minor Simplex changes, it would unblock my demo. I would love to see some solution to this issue land in SimplicityHL so that I could publish that new demo as part of Simplex (it's a lot more sophisticated than the existing basic demo project there).

We've already been building various examples on the enum feature, so it's unfortunate that it has this kind of limitation and it would be great to find a way to remove it.

@apoelstra

Copy link
Copy Markdown
Contributor

The PR description, at least, should have some description of the issue it solves.

@LesterEvSe

Copy link
Copy Markdown
Collaborator Author

The PR description, at least, should have some description of the issue it solves.

Updated the description

@LesterEvSe
LesterEvSe requested a review from apoelstra September 7, 2026 10:51
@stringhandler

Copy link
Copy Markdown
Contributor

May I suggest "Fix: enums can't appear in modules" as a PR title?

@stringhandler

Copy link
Copy Markdown
Contributor

I think I would prefer holding off on this fix until we have a firm direction in the community forum post. Is it possible to change Simplex's way of doing things?

@LesterEvSe

Copy link
Copy Markdown
Collaborator Author

May I suggest "Fix: enums can't appear in modules" as a PR title?

Sure

@KyrylR

KyrylR commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Also, in what sense are enums "special"? It seems like we are implementing a half-baked form of nominal typing for enums, and only enums. For example using the cast_preserves_enum_identity we attempt to reject casts between structurally-equivalent enums, but do so in a poorly-specified way that has multiple bugs.

Yep, I have tried to implement nominal typing for enums, though there are quite a few nuances with imports that prevented doing it with deps support from the beginning

Casting was also tough to deal with, so to keep the scope of original PR manageable, I have decided to postpone it

I am happy to start/continue the discussion on the forum this week

@LesterEvSe

Copy link
Copy Markdown
Collaborator Author

I think I would prefer holding off on this fix until we have a firm direction in the community forum post. Is it possible to change Simplex's way of doing things?

Simplex compiles through new_with_dep, and the driver wraps every file in a generated
mod, so a single-file Simplex program hits the "enum inside a module" rejection anyway. That is not something we can work around on the Simplex
side; wrapping was created to distinguish programs from each other.

On maintaining the behavior: enums are currently an unstable feature, same as modules, so nothing here is a commitment.

If we'd still rather wait, that's fine by me, but it means enums stay unusable in Simplex until the thread settles, and @schoen's last_will.simf demo stays blocked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants