You are browsing as a guest. Sign up (or log in) to start making projects!

3h 19m 7s logged

Devlog: Semantic Unions Become Actual Values (Part I)

In v1.4.6, I made semantic unions actual runtime values instead of just parsed/typechecked shapes.

Before this, Flower could understand:

type Value = int | string

as a type. It could point at it and say, “yes, that is a union,” but that was mostly theoretical.

The goal was to make this work-work for realsies:

type Value = int | string

value: Value = 7

if value is int:
    n: int = value as int
end

value = "Ivy"

if value is string:
    s: string = value as string
end

That meant Flower needed more than TypeInfo.is_union = true.

It needed a full path from syntax, to type proof, to lowered storage, to runtime extraction. Otherwise unions would just be fancy typechecker thingy-ma-bobbies, which is not very useful when the backend still emits C.

What changed

Instead of treating a union as a vague “one of these types, probably,” Flower now lowers it into a tagged structure.

Conceptually, this:

type Value = int | string

becomes something like:

struct Value {
    int tag;
    union {
        int as_int;
        flower_string as_string;
    } data;
};

Not the final sacred form forever, but that is the shape of it: a tag saying which variant is active, plus storage for the possible members. Since Flower emits C right now, the lowering is C-shaped. I am not fully sure how I want this to look in ASM yet, but I do know it would be way more technical, and uglier.

This required a few things to play nicely with one another:

  • if value is int creates a union proof
  • the guarded branch stores narrowing information
  • value as int is only allowed after proof
  • assignments pack values into the right tagged member
  • explicit casts extract the right member from storage

Otherwise the compiler is just writing checks its own backend cannot cash.

The part that makes it ACTUALLY semantic

For pointer narrowing, if value is T mostly refines how the compiler treats an existing value. For semantic unions, the check also becomes a runtime tag test.

So this:

if value is int:

means:

check the active tag of value, then inside this branch, treat it as the int variant.

Then this becomes valid:

n: int = value as int

because the branch already proved the active variant.

Without that proof, value as int should not be casually allowed. Flower is supposed to be explicit, not psychic, and I am not confident enough yet to implement a proper intuitive implicit checker without summoning something cursed.

Assignment and extraction

This:

value: Value = 7

now packs 7 into the int member and sets the tag to the int variant.

Then this:

value = "Ivy"

does the same thing, but for the string member.

So a semantic union is not just some bytes, but rather a tagged runtime object where Flower knows which member is active.

Then explicit casts like:

value as string

can lower into extracting the string member, but only when the typechecker has accepted that the operation is safe in that control-flow path.

Why this checkpoint matters

Flower can now store variant values, prove which variant is active, and require explicit extraction instead of relying on random magic I threw together.

The flower has grown another concerning little organ.

0
3

Comments 0

No comments yet. Be the first!