feat: support enums, distinct types, unions and matching

This commit is contained in:
2026-10-25 23:16:09 +01:00
parent 280059d83d
commit 9966a1f523
23 changed files with 3049 additions and 176 deletions
+242 -23
View File
@@ -102,10 +102,11 @@ lower_package proc(
}
.statement: {
if (typecheck.type_of(checked, id).kind == .type_value) continue
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
initializer :: try add_function(&module, ast.node_span(tree, id))
global :: u32(module.globals.items.len)
try arraylist.append(&module.globals, ir.Global{
type = lower_type(typecheck.type_of(checked, id)),
type = lower_type(&state, typecheck.type_of(checked, id)),
array_type = try array_shape(&state, typecheck.type_of(checked, id)),
initializer = initializer,
span = ast.node_span(tree, id),
@@ -121,6 +122,7 @@ lower_package proc(
match ast.decl_kind(tree, id) {
.function: try lower_function(&state, id)
.statement: {
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
}
.import, .invalid: {}
@@ -130,7 +132,10 @@ lower_package proc(
}
@hide:file
lower_type proc(type typecheck.Type) ir.Type {
lower_type proc(state @State, type typecheck.Type) ir.Type {
if (type.kind == .enumeration) {
return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
}
return match type.kind {
.int: .int
.usize: .usize
@@ -149,6 +154,7 @@ lower_type proc(type typecheck.Type) ir.Type {
.string: .string
.void: .void
.function: .function
.enumeration: unreachable
.none, .invalid, .range, .type_value: .invalid
}
}
@@ -169,7 +175,7 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
if (descriptor.element.kind == .array or descriptor.element.kind == .record) child = try array_shape(state, descriptor.element)
result :: try ir.add_array(
state.module,
lower_type(descriptor.element),
lower_type(state, descriptor.element),
child,
descriptor.len,
descriptor.mutable,
@@ -186,12 +192,24 @@ prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.A
defer arraylist.deinit(&fields)
try arraylist.reserve(&fields, descriptor.fields.len)
for (descriptor.fields) |field| {
tag := ir.Value{}
if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) {
members :: typecheck.enum_type(state.checked, descriptor.tag_type.enum_type).members
for members |member, index| {
if (ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?)) {
tag = enum_value(state, descriptor.tag_type, index)
break
}
}
}
try arraylist.append(&fields, ir.RecordField{
type = lower_type(field.type),
type = lower_type(state, field.type),
array_type = try array_shape(state, field.type),
tag = tag,
})
}
shape :: try ir.add_record(state.module, fields.items)
shape :: if (descriptor.is_union) try ir.add_union(state.module, fields.items, descriptor.tag_type.kind == .enumeration)
else try ir.add_record(state.module, fields.items)
state.records[usize(id)] = shape
return shape
}
@@ -225,7 +243,7 @@ find_entry proc(state @mut State, root_package ast.NodeId) void ! (mem.AllocErro
return
}
declaration :: ast.function_decl(tree, id)
result :: lower_type(typecheck.type_of(state.checked, declaration.return_type))
result :: lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
if (ast.function_param_count(tree, id) != 0 or result == .function) {
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
return
@@ -308,7 +326,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
state.function = usize(state.storage[usize(id)].index)
state.block = ir.BlockId(0)
state.terminated = false
state.module.functions.items[state.function].result = lower_type(typecheck.type_of(state.checked, declaration.return_type))
state.module.functions.items[state.function].result = lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
state.module.functions.items[state.function].result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
@@ -316,7 +334,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
local :: try ir.add_parameter(
state.module,
ir.FunctionId(state.function),
lower_type(type),
lower_type(state, type),
try array_shape(state, type),
)
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) }
@@ -344,11 +362,12 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
match ast.stmt_kind(state.tree, statement) {
.declaration: {
if (typecheck.type_of(state.checked, statement).kind == .type_value) continue
if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, statement).initializer) == .alias_expr) continue
type :: typecheck.type_of(state.checked, statement)
local :: try ir.add_local(
state.module,
ir.FunctionId(state.function),
lower_type(type),
lower_type(state, type),
try array_shape(state, type),
)
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(local) }
@@ -362,7 +381,7 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
.for_stmt: {
loop :: ast.for_stmt(state.tree, statement)
family :: typecheck.type_of(state.checked, loop.iterable).range_element
type :: lower_type(typecheck.Type{ kind = family })
type :: lower_type(state, typecheck.Type{ kind = family })
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) }
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
@@ -385,6 +404,7 @@ lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (m
declaration :: ast.block_statement(state.tree, block, i)
if (ast.stmt_kind(state.tree, declaration) != .declaration) continue
if (typecheck.type_of(state.checked, declaration).kind == .type_value) continue
if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, declaration).initializer) == .alias_expr) continue
_ = try emit(state, ir.Instruction{
op = .reset_local,
slot = state.storage[usize(declaration)].index,
@@ -498,7 +518,7 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
end :: try lower_expression(state, interval.end)
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
slot :: state.storage[usize(id)].index
family :: lower_type(typecheck.Type{ kind = typecheck.type_of(state.checked, loop.iterable).range_element })
family :: lower_type(state, typecheck.Type{ kind = typecheck.type_of(state.checked, loop.iterable).range_element })
_ = try emit(state, ir.Instruction{ op = .store_local, type = family, left = start, slot = slot, span = interval.span })
origin :: state.block
condition :: try add_block(state)
@@ -596,7 +616,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
binding :: sema.binding(state.resolution, assignment.target)
_ = try emit(state, ir.Instruction{
op = .assign_local,
type = lower_type(typecheck.type_of(state.checked, assignment.target)),
type = lower_type(state, typecheck.type_of(state.checked, assignment.target)),
array_type = try array_shape(state, typecheck.type_of(state.checked, assignment.target)),
left = value,
slot = state.storage[usize(binding.data.declaration)].index,
@@ -610,6 +630,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
.expression: _ = try lower_expression(state, ast.statement_expr(tree, id))
.declaration: {
if (typecheck.type_of(state.checked, id).kind == .type_value) return
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) return
if (trap_binding(state, id)) return
declaration :: ast.statement_decl(tree, id)
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
@@ -617,7 +638,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
if (state.terminated or trap_type(state, id)) return
_ = try emit(state, ir.Instruction{
op = .store_local,
type = lower_type(typecheck.type_of(state.checked, id)),
type = lower_type(state, typecheck.type_of(state.checked, id)),
array_type = try array_shape(state, typecheck.type_of(state.checked, id)),
left = value,
slot = state.storage[usize(id)].index,
@@ -676,7 +697,186 @@ lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
}
@hide:file
lower_record proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
enum_value proc(state @State, type typecheck.Type, index usize) ir.Value {
descriptor :: typecheck.enum_type(state.checked, type.enum_type)
member :: descriptor.members[index]
runtime :: lower_type(state, type)
if (ir.unsigned_integer(runtime)) return ir.Value{ type = runtime, data = ir.ValueData{ unsigned = member.magnitude } }
integer := i64(0)
if (member.negative) {
if (member.magnitude == u64(maxval!(i64)) + 1) integer = minval!(i64)
else integer = -i64(member.magnitude)
} else integer = i64(member.magnitude)
return ir.Value{ type = runtime, data = ir.ValueData{ integer = integer } }
}
@hide:file
match_condition proc(
state @mut State, subject ir.ValueId, pattern ast.NodeId,
) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, pattern)
if (ast.expr_kind(state.tree, pattern) == .range) {
interval :: ast.range_expr(state.tree, pattern)
start :: try lower_expression(state, interval.start)
if (state.terminated) return ir.no_value
end :: try lower_expression(state, interval.end)
if (state.terminated) return ir.no_value
lower_bound :: try emit(state, ir.Instruction{ op = .greater_equal, type = .bool, left = subject, right = start, span = span })
upper_op :: if (interval.inclusive) ir.Op.less_equal else ir.Op.less
upper :: try emit(state, ir.Instruction{ op = upper_op, type = .bool, left = subject, right = end, span = span })
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .bool, ir.no_array))
_ = try emit(state, ir.Instruction{ op = .store_local, left = lower_bound, slot = slot, span = span })
origin :: state.block
check_upper :: try add_block(state)
join :: try add_block(state)
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
kind = .branch, value = lower_bound, target = check_upper, alternative = join, span = span,
}
state.block = check_upper
_ = try emit(state, ir.Instruction{ op = .store_local, left = upper, slot = slot, span = span })
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = join, span = span,
}
state.block = join
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
}
value :: try lower_expression(state, pattern)
if (state.terminated) return ir.no_value
return try emit(state, ir.Instruction{ op = .equal, type = .bool, left = subject, right = value, span = span })
}
@hide:file
lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
span :: ast.node_span(tree, id)
view :: ast.match_expr(tree, id)
subject :: try lower_expression(state, view.subject)
if (state.terminated or trap_type(state, id)) return ir.no_value
subject_type :: typecheck.type_of(state.checked, view.subject)
tagged :: subject_type.kind == .record
discriminant := subject
if (tagged) {
tag_type :: typecheck.record_type(state.checked, subject_type.record_type).tag_type
discriminant = try emit(state, ir.Instruction{
op = .union_tag, type = lower_type(state, tag_type), left = subject, span = span,
})
}
type :: typecheck.type_of(state.checked, id)
runtime :: lower_type(state, type)
shape :: try array_shape(state, type)
result_slot := u32(0)
if (runtime != .void) result_slot = u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
join :: try add_block(state)
falls := false
count :: ast.match_arm_count(tree, id)
for 0..count |i| {
arm :: ast.match_arm(tree, id, i)
arm_view :: ast.match_arm_view(tree, arm)
body :: try add_block(state)
if (arm_view.capture) |capture| {
capture_type :: typecheck.type_of(state.checked, capture)
local :: try ir.add_local(state.module, ir.FunctionId(state.function), lower_type(state, capture_type), try array_shape(state, capture_type))
state.storage[usize(capture)] = Storage{ kind = .local, index = u32(local) }
}
if (!arm_view.expression) try predeclare_locals(state, arm_view.body)
patterns :: ast.match_pattern_count(tree, arm)
if (arm_view.catch_all) {
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = body, span = span,
}
} else {
for 0..patterns |j| {
pattern :: ast.match_pattern(tree, arm, j)
selected :: try add_block(state)
final :: i + 1 == count and j + 1 == patterns
next := join
if (!final) {
condition :: try match_condition(state, discriminant, pattern)
if (state.terminated) return ir.no_value
next = try add_block(state)
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .branch, value = condition, target = selected, alternative = next, span = ast.node_span(tree, pattern),
}
} else {
# checking proves exhaustiveness; the final pattern is the remaining case.
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = selected, span = span,
}
}
state.block = selected
if (arm_view.capture) |capture| {
capture_type :: typecheck.type_of(state.checked, capture)
captured := subject
if (tagged) captured = try emit(state, ir.Instruction{
op = .field, type = lower_type(state, capture_type), array_type = try array_shape(state, capture_type),
left = subject, slot = u32(typecheck.union_variant_index(state.checked, pattern)), span = ast.node_span(tree, pattern),
})
_ = try emit(state, ir.Instruction{
op = .store_local, left = captured, slot = state.storage[usize(capture)].index, span = span,
})
}
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = body, span = span,
}
state.block = next
}
}
next :: state.block
state.block = body
state.terminated = false
if (arm_view.expression) {
value :: try lower_expression(state, arm_view.body)
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
op = .store_local, left = value, slot = result_slot, span = span,
})
} else try lower_block(state, arm_view.body, true)
if (!state.terminated) {
falls = true
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = join, span = span,
}
}
state.block = next
state.terminated = false
}
state.block = join
state.terminated = !falls
if (!falls) state.module.functions.items[state.function].blocks.items[usize(join)].terminator = ir.Terminator{ kind = .unreachable }
if (!falls or runtime == .void) return ir.no_value
return try emit(state, ir.Instruction{
op = .load_local, type = runtime, array_type = shape, slot = result_slot, span = span,
})
}
@hide:file
lower_union proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
if (trap_type(state, id)) return ir.no_value
type :: typecheck.type_of(state.checked, id)
slot :: typecheck.union_variant_index(state.checked, id)
shape :: try array_shape(state, type)
field :: state.module.arrays.items[usize(shape)].fields[slot]
payload := ir.no_value
if (ast.expr_kind(tree, id) == .record_literal) {
view :: ast.record_literal(tree, id)
count :: ast.record_field_count(tree, id)
contextual := false
if (view.type) |annotation| contextual = ast.expr_kind(tree, annotation) == .enum_literal
if (contextual and field.type == .record and (count != 1 or ast.field(tree, ast.record_field(tree, id, 0)).name != null)) {
payload = try lower_record_as(state, id, typecheck.union_payload_type(state.checked, id))
} else if (count != 0) {
payload = try lower_expression(state, ast.field(tree, ast.record_field(tree, id, 0)).value?)
}
}
if (state.terminated) return ir.no_value
return try emit(state, ir.Instruction{
op = .union_literal, type = .record, array_type = shape, slot = u32(slot),
left = payload, constant = field.tag, span = ast.node_span(tree, id),
})
}
@hide:file
lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
scratch_start :: state.arguments.items.len
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
@@ -689,7 +889,6 @@ lower_record proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
state.arguments.items[scratch_start + i] = value
}
if (trap_type(state, id)) return ir.no_value
type :: typecheck.type_of(state.checked, id)
descriptor :: typecheck.record_type(state.checked, type.record_type)
ordered_start :: state.arguments.items.len
try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len)
@@ -758,7 +957,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
return try emit(state, ir.Instruction{
op = .field_address,
type = .address,
pointee = lower_type(checked),
pointee = lower_type(state, checked),
array_type = try array_shape(state, checked),
left = base,
slot = u32(typecheck.field_index(state.checked, id)),
@@ -774,7 +973,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
return try emit(state, ir.Instruction{
op = .index_address,
type = .address,
pointee = lower_type(checked),
pointee = lower_type(state, checked),
array_type = try array_shape(state, checked),
left = base,
right = index,
@@ -788,7 +987,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
return try emit(state, ir.Instruction{
op = operation,
type = .address,
pointee = lower_type(checked),
pointee = lower_type(state, checked),
array_type = try array_shape(state, checked),
slot = location.index,
span = span,
@@ -799,8 +998,13 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
span :: ast.node_span(tree, id)
type :: lower_type(typecheck.type_of(state.checked, id))
type :: lower_type(state, typecheck.type_of(state.checked, id))
shape :: try array_shape(state, typecheck.type_of(state.checked, id))
if (typecheck.is_enum_member(state.checked, id)) {
if (trap_type(state, id)) return ir.no_value
return try constant(state, enum_value(state, typecheck.type_of(state.checked, id), typecheck.field_index(state.checked, id)), span)
}
if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, id)
match ast.expr_kind(tree, id) {
.invalid: {
invalid :: ast.invalid(tree, id)
@@ -810,12 +1014,23 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
# ranges have no physical value; only lower_for decomposes an iterable.
if (!trap_type(state, id)) unreachable
}
.array_type, .record_type: {
.array_type, .record_type, .enum_type, .union_type, .distinct_type: {
if (!trap_type(state, id)) {
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
}
}
.record_literal: return try lower_record(state, id)
.alias_expr: return try lower_expression(state, ast.type_operand(tree, id))
.enum_literal: {
if (!trap_type(state, id)) unreachable
}
.match_expr: return try lower_match(state, id)
.record_literal: {
checked :: typecheck.type_of(state.checked, id)
if (checked.kind == .record and typecheck.record_type(state.checked, checked.record_type).is_union) {
return try lower_union(state, id)
}
return try lower_record_as(state, id, checked)
}
.array_literal: {
scratch_start :: state.arguments.items.len
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
@@ -948,7 +1163,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
}
.call: {
target :: sema.binding(state.resolution, ast.call_expr(tree, id).callee)
if (target.kind == .builtin) {
if (target.kind == .builtin or typecheck.is_type_constructor(state.checked, id)) {
operand := ir.no_value
for (0..ast.call_arg_count(tree, id)) |i| {
value :: try lower_expression(state, ast.call_arg(tree, id, i))
@@ -956,6 +1171,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
if (i == 0) operand = value
}
if (trap_type(state, id)) return ir.no_value
if (typecheck.is_type_constructor(state.checked, id)) {
source :: lower_type(state, typecheck.type_of(state.checked, ast.call_arg(tree, id, 0)))
if (source == type) return operand
}
return try emit(state, ir.Instruction{ op = .convert, type = type, left = operand, span = span })
}
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
@@ -1018,7 +1237,7 @@ exact_float_integer proc(text []u8, value f64) bool {
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id)
text :: ast.source_text(state.tree, span)
type :: lower_type(typecheck.type_of(state.checked, id))
type :: lower_type(state, typecheck.type_of(state.checked, id))
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
if (integer_magnitude(text, ir.integer_max(type))) |magnitude| {
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ integer = i64(magnitude) } }, span)
+4 -4
View File
@@ -132,14 +132,14 @@ executes_forward_lazy_globals_and_function_aliases test {
defer diagnostics.deinit(&diagnostic_store)
source ::
`forward :: later + 1
`alias :: add
`forwarded :: add
`later :: 18
`add proc(left int, right int) int {
` local_alias :: alias
` local_alias :: forwarded
` return left + right
`}
`main proc() int {
` invoke :: alias
` invoke :: forwarded
` return invoke(forward, 23)
`}
module := try compile_owned(source, &diagnostic_store)
@@ -1684,7 +1684,7 @@ array_context_inference_empty_shapes_and_scalar_leaf_families test {
"main proc() string {\n a [2]mut string := [\"one\", \"two\"]\n b := a\n a[1] = \"three\"\n return b[1]\n}\n",
"plus proc(n int) int { return n + 1 }\nminus proc(n int) int { return n - 1 }\nmain proc() bool {\n a :: [plus, minus]\n return a[0](41) == 42 and a[1](43) == 42\n}\n",
"make proc() [2]i64 { return [20, 22] }\nmain proc() bool {\n return make()[0] + ([20, 22])[1] == 42\n}\n",
"count :: alias + 1\nalias :: 1\nmain proc() bool {\n a [count][0]bool :: [[], []]\n return true\n}\n",
"count :: forwarded + 1\nforwarded :: 1\nmain proc() bool {\n a [count][0]bool :: [[], []]\n return true\n}\n",
]
for (sources) |source| {
diagnostic_store := diagnostics.init(mem.c_allocator)