error handling kickoff

This commit is contained in:
2026-06-29 22:55:56 +02:00
parent 98c303d22c
commit ae5af37b85
14 changed files with 1229 additions and 173 deletions
+299 -3
View File
@@ -72,11 +72,13 @@ Kind :: enum u8 {
Enum,
Struct,
Union,
Fallible,
}
Node :: struct {
kind: Kind,
child: Type,
extra: Type,
count: u64,
sentinel: u64,
explicit_size: u64,
@@ -110,10 +112,29 @@ Enum_Member :: struct {
value: i128,
}
Variant :: struct {
name: u32,
payload: Type,
id: u16,
}
Sum_Variant :: struct {
name: u32,
payload: Type,
id: u16,
}
Compose_Error :: enum u8 {
None,
Unsupported,
Conflict,
}
Store :: struct {
nodes: [dynamic]Node,
fields: [dynamic]Field,
enum_members: [dynamic]Enum_Member,
variants: [dynamic]Variant,
selected: target.Target,
allocator: mem.Allocator,
}
@@ -123,6 +144,7 @@ init_store :: proc(allocator := context.allocator) -> Store {
store.nodes.allocator = allocator
store.fields.allocator = allocator
store.enum_members.allocator = allocator
store.variants.allocator = allocator
store.selected = target.DEFAULT
store.allocator = allocator
return store
@@ -132,6 +154,7 @@ destroy_store :: proc(store: ^Store) {
delete(store.nodes)
delete(store.fields)
delete(store.enum_members)
delete(store.variants)
}
clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
@@ -139,6 +162,7 @@ clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
append(&store.nodes, ..source.nodes[:])
append(&store.fields, ..source.fields[:])
append(&store.enum_members, ..source.enum_members[:])
append(&store.variants, ..source.variants[:])
store.selected = source.selected
return store
}
@@ -230,6 +254,7 @@ define_record :: proc(
explicit_size: u64 = 0,
explicit_alignment: u32 = 0,
tag: Type = INVALID,
declared_tag: Type = INVALID,
) -> bool {
existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
@@ -247,6 +272,7 @@ define_record :: proc(
// structs leave it INVALID); the per-variant tag value is derived from the enum
// member whose name matches the variant, so no extra storage is needed.
store.nodes[index].child = tag
store.nodes[index].extra = declared_tag
store.nodes[index].field_start = u32(len(store.fields))
store.nodes[index].field_count = u32(len(fields))
append(&store.fields, ..fields)
@@ -268,6 +294,180 @@ enum_anonymous :: proc(store: ^Store, members: []Enum_Member, backing: Type) ->
})
}
union_anonymous :: proc(store: ^Store, fields: []Field, tag: Type) -> Type {
start := u32(len(store.fields))
append(&store.fields, ..fields)
return intern(store, Node{
kind=.Union,
child=tag,
field_start=start,
field_count=u32(len(fields)),
declared=true,
})
}
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
for variant in store.variants {
if variant.name == name && variant.payload == payload {
return variant.id, true
}
}
next := len(store.variants)+1
if next > 0xffff {
return 0, false
}
id := u16(next)
append(&store.variants, Variant{name=name, payload=payload, id=id})
return id, true
}
fallible :: proc(store: ^Store, success, error: Type) -> Type {
return intern(store, Node{kind=.Fallible, child=success, extra=error, declared=true})
}
fallible_success :: proc(value: Type, store: ^Store) -> Type {
item, ok := node(store, value)
return item.child if ok && item.kind == .Fallible else INVALID
}
fallible_error :: proc(value: Type, store: ^Store) -> Type {
item, ok := node(store, value)
return item.extra if ok && item.kind == .Fallible else INVALID
}
append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Variant) -> bool {
item, ok := node(store, value)
if !ok {
return false
}
if item.kind == .Enum {
if item.explicit_backing {
return false
}
for member in enum_members_for(store, value) {
if member.value <= 0 || member.value > 0xffff {
return false
}
append(out, Sum_Variant{name=member.name, payload=VOID, id=u16(member.value)})
}
return true
}
if item.kind != .Union || !is_enum(item.child, store) || item.c_layout {
return false
}
tag_members := enum_members_for(store, item.child)
for field in fields_for(store, value) {
found := false
for member in tag_members {
if member.name == field.name {
if member.value <= 0 || member.value > 0xffff {
return false
}
append(out, Sum_Variant{name=field.name, payload=field.type, id=u16(member.value)})
found = true
break
}
}
if !found {
return false
}
}
return true
}
compose_sum :: proc(store: ^Store, left, right: Type) -> (Type, Compose_Error) {
variants: [dynamic]Sum_Variant
variants.allocator = store.allocator
defer delete(variants)
if !append_sum_variants(store, left, &variants) {
return INVALID, .Unsupported
}
right_variants: [dynamic]Sum_Variant
right_variants.allocator = store.allocator
defer delete(right_variants)
if !append_sum_variants(store, right, &right_variants) {
return INVALID, .Unsupported
}
for candidate in right_variants {
merged := false
for existing in variants {
if existing.id == candidate.id {
merged = true
break
}
if existing.name == candidate.name && existing.payload != candidate.payload {
return INVALID, .Conflict
}
}
if !merged {
append(&variants, candidate)
}
}
all_void := true
for variant in variants {
all_void = all_void && variant.payload == VOID
}
members := make([]Enum_Member, len(variants), store.allocator)
defer delete(members, store.allocator)
for variant, index in variants {
members[index] = Enum_Member{name=variant.name, value=i128(variant.id)}
}
if all_void {
return enum_anonymous(store, members, U16), .None
}
fields := make([]Field, len(variants), store.allocator)
defer delete(fields, store.allocator)
for variant, index in variants {
fields[index] = Field{name=variant.name, type=variant.payload}
}
tag := enum_anonymous(store, members, U16)
return union_anonymous(store, fields, tag), .None
}
sum_has_name :: proc(store: ^Store, value: Type, name: u32) -> bool {
variants: [dynamic]Sum_Variant
variants.allocator = store.allocator
defer delete(variants)
if !append_sum_variants(store, value, &variants) {
return false
}
for variant in variants {
if variant.name == name {
return true
}
}
return false
}
can_sum_widen :: proc(from, to: Type, store: ^Store) -> bool {
if from == to {
return true
}
from_variants: [dynamic]Sum_Variant
from_variants.allocator = store.allocator
defer delete(from_variants)
to_variants: [dynamic]Sum_Variant
to_variants.allocator = store.allocator
defer delete(to_variants)
if !append_sum_variants(store, from, &from_variants) ||
!append_sum_variants(store, to, &to_variants) {
return false
}
for needed in from_variants {
found := false
for available in to_variants {
if available.id == needed.id && available.payload == needed.payload {
found = true
break
}
}
if !found {
return false
}
}
return true
}
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
return define_record(store, id, fields, c_layout, opaque)
}
@@ -514,7 +714,7 @@ is_concrete :: proc(value: Type, store: ^Store = nil) -> bool {
value_kind := kind(value, store)
if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer ||
value_kind == .Slice || value_kind == .Range || value_kind == .Optional ||
value_kind == .Enum {
value_kind == .Enum || value_kind == .Fallible {
return true
}
if value_kind == .Struct || value_kind == .Union {
@@ -575,6 +775,14 @@ union_tag_enum :: proc(value: Type, store: ^Store) -> Type {
return item.child
}
union_declared_tag_enum :: proc(value: Type, store: ^Store) -> Type {
if !is_tagged_union(value, store) {
return INVALID
}
item, _ := node(store, value)
return item.extra if is_valid(item.extra) else item.child
}
// union_payload_offset is the byte offset of a tagged union's payload carrier (after
// the discriminant), shared by `size` and the LLVM emitter so construction, field
// access, and layout agree. Zero for untagged unions.
@@ -591,6 +799,16 @@ union_payload_offset :: proc(value: Type, store: ^Store, selected := target.DEFA
return (tag_size+payload_align-1)/payload_align*payload_align
}
sum_tag_type :: proc(value: Type, store: ^Store) -> Type {
if is_enum(value, store) {
return value
}
if is_tagged_union(value, store) {
return union_tag_enum(value, store)
}
return INVALID
}
is_distinct :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Distinct
}
@@ -666,6 +884,13 @@ is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
item, ok := node(store, value)
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0)
}
if value_kind == .Fallible {
item, ok := node(store, value)
if !ok || !is_runtime_value(item.extra, store, depth+1) {
return false
}
return is_void(item.child) || is_runtime_value(item.child, store, depth+1)
}
if value_kind == .Distinct || value_kind == .Enum {
item, ok := node(store, value)
return ok && item.declared && is_runtime_value(item.child, store, depth+1)
@@ -689,6 +914,59 @@ runtime_representation :: proc(value: Type, store: ^Store, depth := 0) -> Type {
return runtime_representation(item.child, store, depth+1)
}
sum_payload_size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
if is_enum(value, store) {
return 0
}
if !is_tagged_union(value, store) {
return size(value, store, selected)
}
result: u64
for field in fields_for(store, value) {
result = max(result, size(field.type, store, selected))
}
return result
}
sum_payload_alignment :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
if is_enum(value, store) {
return 1
}
if !is_tagged_union(value, store) {
return u64(alignment_of(value, store, selected))
}
result: u64 = 1
for field in fields_for(store, value) {
result = max(result, u64(alignment_of(field.type, store, selected)))
}
return result
}
fallible_payload_offset :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
item, ok := node(store, value)
if !ok || item.kind != .Fallible {
return 0
}
payload_align := max(
u64(1),
max(
u64(alignment_of(item.child, store, selected)) if !is_void(item.child) else u64(1),
sum_payload_alignment(item.extra, store, selected),
),
)
tag_size := size(U16, store, selected)
return (tag_size+payload_align-1)/payload_align*payload_align
}
fallible_payload_size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
item, ok := node(store, value)
if !ok || item.kind != .Fallible {
return 0
}
success_size := u64(0) if is_void(item.child) else size(item.child, store, selected)
return max(success_size, sum_payload_size(item.extra, store, selected))
}
contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 {
return true
@@ -717,6 +995,10 @@ contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bo
if item.kind == .Array || item.kind == .Slice || item.kind == .Range || item.kind == .Optional {
return contains_c_struct_by_value(item.child, store, depth+1)
}
if item.kind == .Fallible {
return contains_c_struct_by_value(item.child, store, depth+1) ||
contains_c_struct_by_value(item.extra, store, depth+1)
}
return false
}
@@ -774,7 +1056,8 @@ contains_distinct_seen :: proc(value: Type, store: ^Store, seen: ^[dynamic]Type)
}
}
}
return is_valid(item.child) && contains_distinct_seen(item.child, store, seen)
return (is_valid(item.child) && contains_distinct_seen(item.child, store, seen)) ||
(is_valid(item.extra) && contains_distinct_seen(item.extra, store, seen))
}
is_c_integer_promotion_candidate :: proc(value: Type) -> bool {
@@ -1110,6 +1393,11 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
return (payload_offset+carrier_size+total_align-1)/total_align*total_align
}
return (carrier_size+max_align-1)/max_align*max_align
case .Fallible:
payload_offset := fallible_payload_offset(value, store, selected)
payload_size := fallible_payload_size(value, store, selected)
total_align := u64(alignment_of(value, store, selected))
return (payload_offset+payload_size+total_align-1)/total_align*total_align
case:
return 0
}
@@ -1150,6 +1438,14 @@ alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) ->
result = max(result, alignment_of(item.child, store, selected))
}
return result
case .Fallible:
item, _ := node(store, value)
result := alignment_of(U16, store, selected)
if !is_void(item.child) {
result = max(result, alignment_of(item.child, store, selected))
}
result = max(result, int(sum_payload_alignment(item.extra, store, selected)))
return result
case:
return 1
}
@@ -1271,6 +1567,6 @@ name :: proc(value: Type) -> string {
case C_DOUBLE: return "c_double"
case C_LONGDOUBLE: return "c_longdouble"
case:
return fmt.tprintf("<type %d>", value)
return "fallible" if kind(value) == .Fallible else fmt.tprintf("<type %d>", value)
}
}