Lyric
A statically typed application language with a bytecode VM — standalone, and embeddable in a host with a capability-based sandbox.
What changed in v2.11.0 — 2026-08-22
Bytecode format 3.5. One new section, and the whole release is about a name that used to get lost.
Added
A module records the
opaque typea field was declared with (section 14, format 3.5). An opaque alias is a distinct type in the language and its underlying type everywhere below the checker — that is what makesx as Entityfree and lets a handle cross a native boundary unchanged. The cost showed up on the other side: a host reading the shape of an attributed class saw@Saved class Holder { hero: world.Entity, stage: int } → i64, i64and a save writer that WANTS to refuse a handle — the slot it names belongs to something else after a restart — had nothing to refuse it by. Now:
foreach (var field in module.Attributes.FieldsOf(saved.Target)!) if (field.OpaqueName is { } opaque) throw new InvalidOperationException($"{field.Name} is a '{opaque}' handle");FieldsOfreturnsAttributeFieldrecords instead of tuples;NameandTypeare unchanged,OpaqueNameis the addition and isnullfor every ordinary field. The name is the leaf through arrays and optionals — a field of typeEntity[]answersEntity, and its type still says it is an array. A transparent alias resolves through to what it names, because that one is not a type of its own.Nothing about the program changes. The field is the same
i64it was, no instruction moved, and a runtime that ignores the section is a runtime that knows what it knew before.lyric disasmshows the names where they exist:names Holder(hero: Entity, stage).
Format
- 3.5 against 3.4: one new section, OpaqueFields (id 14), skippable in the plainest way — no other section refers to it and none of it affects execution. A 3.4 reader loads a 3.5 module unchanged, and the other way round; the only difference is whether a host can tell a handle from the number it is made of. Unlike 3.4, this one asks nothing of anybody.