void-payloads, multi-pattern arms, and range patterns (match statements)
This commit is contained in:
@@ -178,10 +178,14 @@ Stmt :: struct {
|
||||
// `Defer` statements use `update` as the deferred statement (which may itself
|
||||
// be a `Block`).
|
||||
// `Match` statements use `expr` as the subject and `body` as the list of arm
|
||||
// statements (each a `Match_Arm`). A `Match_Arm` uses `expr` as its pattern
|
||||
// (`INVALID_EXPR` marks the `else` arm), `captures` for the optional payload
|
||||
// capture (0 or 1 name, tagged-union variants only), and `body` as the arm body.
|
||||
// statements (each a `Match_Arm`). A `Match_Arm` uses `patterns` as its pattern
|
||||
// list (empty marks the `else` arm; more than one is a multi-pattern arm),
|
||||
// `captures` for the optional payload capture (0 or 1 name, tagged-union variants
|
||||
// only) with `pointer_capture` distinguishing `|@cap|` from `|cap|`, and `body`
|
||||
// as the arm body.
|
||||
captures: []symbol.Id,
|
||||
// `Match_Arm` pattern list; empty ⇒ the `else` arm.
|
||||
patterns: []Expr_Id,
|
||||
guard: Expr_Id,
|
||||
body: []Stmt_Id,
|
||||
else_body: []Stmt_Id,
|
||||
@@ -303,6 +307,7 @@ destroy_module :: proc(module: ^Module) {
|
||||
}
|
||||
for statement in module.statements {
|
||||
delete(statement.captures, module.allocator)
|
||||
delete(statement.patterns, module.allocator)
|
||||
delete(statement.body, module.allocator)
|
||||
delete(statement.else_body, module.allocator)
|
||||
}
|
||||
|
||||
+224
-65
@@ -788,8 +788,11 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
|
||||
mark_block_imports_used(checker, deferred[:], file)
|
||||
case .Match, .Match_Arm:
|
||||
// `Match` carries the subject in `expr` and arms in `body`; each `Match_Arm`
|
||||
// carries its pattern in `expr` and the arm body in `body`.
|
||||
// carries its patterns in `patterns` and the arm body in `body`.
|
||||
mark_expr_imports_used(checker, statement.expr, file)
|
||||
for pattern in statement.patterns {
|
||||
mark_expr_imports_used(checker, pattern, file)
|
||||
}
|
||||
mark_block_imports_used(checker, statement.body, file)
|
||||
case .Break, .Continue:
|
||||
case .Invalid:
|
||||
@@ -1048,8 +1051,14 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
||||
"c_struct definitions require at least one field",
|
||||
)
|
||||
}
|
||||
// A tagged union may carry `void`-payload variants (`.quit void`): the
|
||||
// variant has no runtime value, only a tag. Allowed only here, not for
|
||||
// structs, untagged unions, or c_structs.
|
||||
tagged_union := item.kind == .Union && types.is_enum(item.child, &checker.module.types)
|
||||
for field in types.fields_for(&checker.module.types, id) {
|
||||
if !types.is_runtime_value(field.type, &checker.module.types) {
|
||||
if tagged_union && types.is_void(field.type) {
|
||||
// void variant: no payload to validate.
|
||||
} else if !types.is_runtime_value(field.type, &checker.module.types) {
|
||||
source.add(
|
||||
checker.diagnostics,
|
||||
source.Span{},
|
||||
@@ -3202,6 +3211,10 @@ build_compound_expr :: proc(
|
||||
id := source.addf(checker.diagnostics, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name))
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
if types.is_void(field.type) {
|
||||
id := source.addf(checker.diagnostics, expr.span, "variant '%s' has no payload to read", symbol_text(checker, expr.name))
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base,
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
@@ -3402,8 +3415,20 @@ build_compound_expr :: proc(
|
||||
}
|
||||
initialized[index] = true
|
||||
value_index := 0 if union_record else index
|
||||
values[value_index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file)
|
||||
values[value_index] = coerce_expr(checker, values[value_index], field.type, keyed_expr.span)
|
||||
is_void_field := union_record && types.is_void(field.type)
|
||||
if keyed_expr.left == ast.INVALID_EXPR {
|
||||
// Bare key `T{ variant }`: valid only to construct a void-payload
|
||||
// variant (no value); the payload slot stays INVALID_EXPR.
|
||||
if !is_void_field {
|
||||
source.addf(checker.diagnostics, keyed_expr.span, "field '%s' requires a value", symbol_text(checker, keyed_expr.name))
|
||||
}
|
||||
} else {
|
||||
if is_void_field {
|
||||
source.addf(checker.diagnostics, keyed_expr.span, "void variant '%s' takes no value", symbol_text(checker, keyed_expr.name))
|
||||
}
|
||||
values[value_index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file)
|
||||
values[value_index] = coerce_expr(checker, values[value_index], field.type, keyed_expr.span)
|
||||
}
|
||||
}
|
||||
if !union_record {
|
||||
for field, index in fields {
|
||||
@@ -3417,16 +3442,21 @@ build_compound_expr :: proc(
|
||||
}
|
||||
active_field: i64
|
||||
if union_record {
|
||||
if values[0] == hir.INVALID_EXPR {
|
||||
delete(values, checker.allocator)
|
||||
return invalid_hir_expr(checker, expr.span, source.add(checker.diagnostics, expr.span, "union literal requires a known field"), struct_type)
|
||||
}
|
||||
// The active variant is the one initialized field. Its payload slot
|
||||
// (`values[0]`) may legitimately be INVALID_EXPR for a void variant, so
|
||||
// detect "no field" via `initialized`, not the payload value.
|
||||
found_any := false
|
||||
for value, index in initialized {
|
||||
if value {
|
||||
active_field = i64(index)
|
||||
found_any = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found_any {
|
||||
delete(values, checker.allocator)
|
||||
return invalid_hir_expr(checker, expr.span, source.add(checker.diagnostics, expr.span, "union literal requires a known field"), struct_type)
|
||||
}
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Struct, span=expr.span, type=struct_type, args=values, integer=active_field,
|
||||
@@ -5347,6 +5377,32 @@ Match_Built_Arm :: struct {
|
||||
terminal: bool,
|
||||
}
|
||||
|
||||
// match_subject_location yields a fresh location expr for the `match` subject: a direct
|
||||
// read of the value temp, or a deref of the pointer temp when an arm pointer-captures (so
|
||||
// captures alias the original storage). Either way its lowered address is the subject's.
|
||||
match_subject_location :: proc(checker: ^Checker, subj_local: hir.Local_Id, is_pointer: bool, subject_type, ptr_type: types.Type, span: source.Span) -> hir.Expr_Id {
|
||||
if !is_pointer {
|
||||
return slot_read(checker, subj_local, subject_type, span)
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind = .Deref, span = span, type = subject_type,
|
||||
left = slot_read(checker, subj_local, ptr_type, span),
|
||||
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
// match_or ORs a fresh dispatch comparison into an arm's accumulating condition (for a
|
||||
// multi-pattern arm), or returns it directly for the first pattern.
|
||||
match_or :: proc(checker: ^Checker, condition, cmp: hir.Expr_Id, span: source.Span) -> hir.Expr_Id {
|
||||
if condition == hir.INVALID_EXPR {
|
||||
return cmp
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind = .Or, span = span, type = types.BOOL,
|
||||
left = condition, right = cmp, target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
// emit_match desugars a `match` into a single subject spill, one dispatch key read, and
|
||||
// an `if`/`else if` chain. `as_value` (with `slot`/`slot_type`) routes each arm body
|
||||
// through the value-branch machinery so the construct produces a value; otherwise arm
|
||||
@@ -5374,8 +5430,10 @@ emit_match :: proc(
|
||||
return false
|
||||
}
|
||||
|
||||
// 1. Subject, spilled into an addressable temp so the tag read and any payload
|
||||
// captures reference one evaluation.
|
||||
// 1. Subject. A pointer capture (`|@cap|`) must alias the original storage, so when
|
||||
// any arm requests one we spill the subject's *address* (it must be an addressable
|
||||
// lvalue) and route reads through a deref; otherwise we spill the value as a copy.
|
||||
// Either spill evaluates the subject exactly once.
|
||||
subject := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
||||
subject_type := checker.module.exprs[subject].type
|
||||
if checker.module.exprs[subject].kind == .Invalid {
|
||||
@@ -5392,24 +5450,56 @@ emit_match :: proc(
|
||||
"'match' subject must be a tagged union, enum, or scalar value, not '%s'", type_label(checker, subject_type)))
|
||||
}
|
||||
|
||||
ok := true
|
||||
|
||||
wants_pointer := false
|
||||
for arm_id in statement.body {
|
||||
arm := checker.ast_module.statements[arm_id]
|
||||
if arm.kind == .Match_Arm && arm.pointer_capture {
|
||||
wants_pointer = true
|
||||
break
|
||||
}
|
||||
}
|
||||
subj_is_pointer := false
|
||||
subj_writable := false
|
||||
ptr_type := types.INVALID
|
||||
if wants_pointer && is_tagged {
|
||||
if hir_is_location(checker, subject) {
|
||||
subj_is_pointer = true
|
||||
subj_writable = hir_location_writable(checker, subject, ctx.locals^[:])
|
||||
ptr_type = types.pointer(store, subject_type, subj_writable, false)
|
||||
} else {
|
||||
source.add(checker.diagnostics, span, "a pointer capture requires an addressable 'match' subject (bind it to a variable first)")
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
spill_type := ptr_type if subj_is_pointer else subject_type
|
||||
spill_value := subject
|
||||
if subj_is_pointer {
|
||||
spill_value = add_hir_expr(checker, hir.Expr{
|
||||
kind = .Address, span = span, type = ptr_type, left = subject,
|
||||
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
subj_local := hir.local_id(len(ctx.hir_locals^))
|
||||
append(ctx.hir_locals, hir.Local{name = checker.sink_symbol, type = subject_type, mutable = false})
|
||||
append(ctx.hir_locals, hir.Local{name = checker.sink_symbol, type = spill_type, mutable = false})
|
||||
append(out, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .Declaration, span = span, local = subj_local, expr = subject,
|
||||
kind = .Declaration, span = span, local = subj_local, expr = spill_value,
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
|
||||
// 2. Dispatch key: a tagged union reads its discriminant into its own temp; an enum
|
||||
// or scalar compares the subject directly.
|
||||
key_local := subj_local
|
||||
key_type := subject_type
|
||||
key_type := spill_type
|
||||
tag_enum := types.INVALID
|
||||
if is_tagged {
|
||||
tag_enum = types.union_tag_enum(subject_type, store)
|
||||
tag_read := add_hir_expr(checker, hir.Expr{
|
||||
kind = .Union_Tag, span = span, type = tag_enum,
|
||||
left = slot_read(checker, subj_local, subject_type, span),
|
||||
left = match_subject_location(checker, subj_local, subj_is_pointer, subject_type, ptr_type, span),
|
||||
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
tag_local := hir.local_id(len(ctx.hir_locals^))
|
||||
@@ -5423,7 +5513,8 @@ emit_match :: proc(
|
||||
key_type = tag_enum
|
||||
}
|
||||
|
||||
// 3. Build each arm (forward, for source-order diagnostics).
|
||||
// 3. Build each arm (forward, for source-order diagnostics). An arm's `patterns` may
|
||||
// list several alternatives (`.a, .b:` / `0, 1:`); their conditions are OR'd.
|
||||
built: [dynamic]Match_Built_Arm
|
||||
built.allocator = checker.allocator
|
||||
defer delete(built)
|
||||
@@ -5431,7 +5522,6 @@ emit_match :: proc(
|
||||
covered.allocator = checker.allocator
|
||||
defer delete(covered)
|
||||
has_else := false
|
||||
ok := true
|
||||
|
||||
for arm_id in statement.body {
|
||||
arm := checker.ast_module.statements[arm_id]
|
||||
@@ -5443,7 +5533,7 @@ emit_match :: proc(
|
||||
source.add(checker.diagnostics, arm.span, "arms after 'else' are unreachable")
|
||||
ok = false
|
||||
}
|
||||
is_else := arm.expr == ast.INVALID_EXPR
|
||||
is_else := len(arm.patterns) == 0
|
||||
condition := hir.INVALID_EXPR
|
||||
field_index := -1
|
||||
payload_type := types.INVALID
|
||||
@@ -5456,69 +5546,123 @@ emit_match :: proc(
|
||||
}
|
||||
has_else = true
|
||||
} else if is_tagged || is_enum_subject {
|
||||
pattern := checker.ast_module.exprs[arm.expr]
|
||||
if pattern.kind != .Enum_Literal {
|
||||
source.add(checker.diagnostics, arm.span, "an enum or tagged-union 'match' arm must be a '.variant' pattern")
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
if contains_name(covered[:], pattern.name) {
|
||||
source.addf(checker.diagnostics, arm.span, "duplicate 'match' arm for '.%s'", symbol_text(checker, pattern.name))
|
||||
ok = false
|
||||
} else {
|
||||
append(&covered, pattern.name)
|
||||
}
|
||||
if is_tagged {
|
||||
index, field, found := find_struct_field(checker, subject_type, pattern.name)
|
||||
if !found {
|
||||
source.addf(checker.diagnostics, arm.span, "unknown variant '.%s' on '%s'", symbol_text(checker, pattern.name), type_label(checker, subject_type))
|
||||
// The capture payload (if any) must be one type across every listed variant.
|
||||
capture_field := -1
|
||||
capture_payload := types.INVALID
|
||||
capture_conflict := types.INVALID
|
||||
member_enum := tag_enum if is_tagged else subject_type
|
||||
for pat_id in arm.patterns {
|
||||
pattern := checker.ast_module.exprs[pat_id]
|
||||
if pattern.kind == .Range {
|
||||
source.add(checker.diagnostics, arm.span, "range patterns only apply to scalar 'match' subjects")
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
field_index = index
|
||||
payload_type = field.type
|
||||
member := enum_member_hir(checker, tag_enum, pattern.name, arm.span)
|
||||
condition = add_hir_expr(checker, hir.Expr{
|
||||
if pattern.kind != .Enum_Literal {
|
||||
source.add(checker.diagnostics, arm.span, "an enum or tagged-union 'match' arm must be a '.variant' pattern")
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
if contains_name(covered[:], pattern.name) {
|
||||
source.addf(checker.diagnostics, arm.span, "duplicate 'match' arm for '.%s'", symbol_text(checker, pattern.name))
|
||||
ok = false
|
||||
} else {
|
||||
append(&covered, pattern.name)
|
||||
}
|
||||
if is_tagged {
|
||||
index, field, found := find_struct_field(checker, subject_type, pattern.name)
|
||||
if !found {
|
||||
source.addf(checker.diagnostics, arm.span, "unknown variant '.%s' on '%s'", symbol_text(checker, pattern.name), type_label(checker, subject_type))
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
if capture_field < 0 {
|
||||
capture_field = index
|
||||
capture_payload = field.type
|
||||
} else if !types.equal(capture_payload, field.type) {
|
||||
capture_conflict = field.type
|
||||
}
|
||||
} else {
|
||||
if _, found := find_enum_member(checker, subject_type, pattern.name); !found {
|
||||
source.addf(checker.diagnostics, arm.span, "unknown member '.%s' on '%s'", symbol_text(checker, pattern.name), type_label(checker, subject_type))
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
}
|
||||
member := enum_member_hir(checker, member_enum, pattern.name, arm.span)
|
||||
cmp := add_hir_expr(checker, hir.Expr{
|
||||
kind = .Eq, span = arm.span, type = types.BOOL,
|
||||
left = slot_read(checker, key_local, key_type, arm.span), right = member,
|
||||
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
if has_capture {
|
||||
condition = match_or(checker, condition, cmp, arm.span)
|
||||
}
|
||||
if has_capture {
|
||||
if !is_tagged {
|
||||
source.add(checker.diagnostics, arm.span, "only tagged-union variants can capture a payload")
|
||||
ok = false
|
||||
}
|
||||
if _, found := find_enum_member(checker, subject_type, pattern.name); !found {
|
||||
source.addf(checker.diagnostics, arm.span, "unknown member '.%s' on '%s'", symbol_text(checker, pattern.name), type_label(checker, subject_type))
|
||||
} else if types.is_valid(capture_conflict) {
|
||||
source.addf(checker.diagnostics, arm.span, "capture group with incompatible types '%s' and '%s'",
|
||||
type_label(checker, capture_payload), type_label(checker, capture_conflict))
|
||||
ok = false
|
||||
continue
|
||||
} else if types.is_void(capture_payload) {
|
||||
source.add(checker.diagnostics, arm.span, "this variant has a void payload; there is nothing to capture")
|
||||
ok = false
|
||||
} else {
|
||||
field_index = capture_field
|
||||
payload_type = capture_payload
|
||||
}
|
||||
member := enum_member_hir(checker, subject_type, pattern.name, arm.span)
|
||||
condition = add_hir_expr(checker, hir.Expr{
|
||||
kind = .Eq, span = arm.span, type = types.BOOL,
|
||||
left = slot_read(checker, key_local, key_type, arm.span), right = member,
|
||||
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Scalar subject: each pattern is a literal or a range, compared to the subject.
|
||||
if has_capture {
|
||||
source.add(checker.diagnostics, arm.span, "only tagged-union variants can capture a payload")
|
||||
ok = false
|
||||
}
|
||||
pattern := build_expr(checker, arm.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, subject_type, ctx.pkg, ctx.file)
|
||||
pattern = coerce_expr(checker, pattern, subject_type, arm.span)
|
||||
if checker.module.exprs[pattern].kind == .Invalid {
|
||||
ok = false
|
||||
continue
|
||||
for pat_id in arm.patterns {
|
||||
pat_ast := checker.ast_module.exprs[pat_id]
|
||||
cmp := hir.INVALID_EXPR
|
||||
if pat_ast.kind == .Range {
|
||||
lo := build_expr(checker, pat_ast.left, ctx.locals^[:], ctx.global_reads, ctx.calls, subject_type, ctx.pkg, ctx.file)
|
||||
lo = coerce_expr(checker, lo, subject_type, arm.span)
|
||||
hi := build_expr(checker, pat_ast.right, ctx.locals^[:], ctx.global_reads, ctx.calls, subject_type, ctx.pkg, ctx.file)
|
||||
hi = coerce_expr(checker, hi, subject_type, arm.span)
|
||||
if checker.module.exprs[lo].kind == .Invalid || checker.module.exprs[hi].kind == .Invalid {
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
ge := add_hir_expr(checker, hir.Expr{
|
||||
kind = .Ge, span = arm.span, type = types.BOOL,
|
||||
left = slot_read(checker, key_local, key_type, arm.span), right = lo,
|
||||
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
hi_cmp := add_hir_expr(checker, hir.Expr{
|
||||
kind = .Le if pat_ast.integer == 1 else .Lt, span = arm.span, type = types.BOOL,
|
||||
left = slot_read(checker, key_local, key_type, arm.span), right = hi,
|
||||
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
cmp = add_hir_expr(checker, hir.Expr{
|
||||
kind = .And, span = arm.span, type = types.BOOL,
|
||||
left = ge, right = hi_cmp, target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
pattern := build_expr(checker, pat_id, ctx.locals^[:], ctx.global_reads, ctx.calls, subject_type, ctx.pkg, ctx.file)
|
||||
pattern = coerce_expr(checker, pattern, subject_type, arm.span)
|
||||
if checker.module.exprs[pattern].kind == .Invalid {
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
cmp = add_hir_expr(checker, hir.Expr{
|
||||
kind = .Eq, span = arm.span, type = types.BOOL,
|
||||
left = slot_read(checker, key_local, key_type, arm.span), right = pattern,
|
||||
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
condition = match_or(checker, condition, cmp, arm.span)
|
||||
}
|
||||
condition = add_hir_expr(checker, hir.Expr{
|
||||
kind = .Eq, span = arm.span, type = types.BOOL,
|
||||
left = slot_read(checker, key_local, key_type, arm.span), right = pattern,
|
||||
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
arm_body, body_ok := build_match_arm_body(ctx, arm, subject_type, subj_local, field_index, payload_type, as_value, slot, slot_type, span)
|
||||
arm_body, body_ok := build_match_arm_body(ctx, arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable, field_index, payload_type, as_value, slot, slot_type, span)
|
||||
if !body_ok {
|
||||
ok = false
|
||||
}
|
||||
@@ -5622,6 +5766,9 @@ build_match_arm_body :: proc(
|
||||
arm: ast.Stmt,
|
||||
subject_type: types.Type,
|
||||
subj_local: hir.Local_Id,
|
||||
subj_is_pointer: bool,
|
||||
ptr_type: types.Type,
|
||||
subj_writable: bool,
|
||||
field_index: int,
|
||||
payload_type: types.Type,
|
||||
as_value: bool,
|
||||
@@ -5637,17 +5784,29 @@ build_match_arm_body :: proc(
|
||||
if len(arm.captures) > 0 && field_index >= 0 {
|
||||
capture := arm.captures[0]
|
||||
if capture != checker.sink_symbol {
|
||||
cap_local := hir.local_id(len(ctx.hir_locals^))
|
||||
append(ctx.hir_locals, hir.Local{name = capture, type = payload_type, mutable = false})
|
||||
append(ctx.locals, Build_Local{name = capture, type = payload_type, mutable = false, id = cap_local})
|
||||
// The payload sits at the subject's shared carrier offset. A value capture
|
||||
// loads it; a `|@cap|` capture binds a pointer to it (mutability follows the
|
||||
// subject), aliasing the original storage via the subject location.
|
||||
field_read := add_hir_expr(checker, hir.Expr{
|
||||
kind = .Field, span = span, type = payload_type, integer = i64(field_index),
|
||||
left = slot_read(checker, subj_local, subject_type, span),
|
||||
left = match_subject_location(checker, subj_local, subj_is_pointer, subject_type, ptr_type, span),
|
||||
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
cap_type := payload_type
|
||||
cap_value := field_read
|
||||
if arm.pointer_capture {
|
||||
cap_type = types.pointer(&checker.module.types, payload_type, subj_writable, false)
|
||||
cap_value = add_hir_expr(checker, hir.Expr{
|
||||
kind = .Address, span = span, type = cap_type, left = field_read,
|
||||
target = hir.INVALID_REF, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
cap_local := hir.local_id(len(ctx.hir_locals^))
|
||||
append(ctx.hir_locals, hir.Local{name = capture, type = cap_type, mutable = false})
|
||||
append(ctx.locals, Build_Local{name = capture, type = cap_type, mutable = false, id = cap_local})
|
||||
append(&result, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .Declaration, span = span, local = cap_local, expr = field_read,
|
||||
kind = .Declaration, span = span, local = cap_local, expr = cap_value,
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
+14
-7
@@ -704,8 +704,11 @@ emit_instruction_stream :: proc(
|
||||
if item.kind == .Union {
|
||||
fields := types.fields_for(&emitter.module.types, instruction.type)
|
||||
field_index := int(instruction.integer)
|
||||
// A void-payload variant carries no value (`T{ variant }`): only the tag
|
||||
// is stored, so there is no operand to validate or write.
|
||||
void_payload := field_index >= 0 && field_index < len(fields) && types.is_void(fields[field_index].type)
|
||||
if field_index < 0 || field_index >= len(fields) ||
|
||||
!valid_value(instructions, instruction.args[0], fields[field_index].type, &emitter.module.types) {
|
||||
(!void_payload && !valid_value(instructions, instruction.args[0], fields[field_index].type, &emitter.module.types)) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid union aggregate operands")
|
||||
continue
|
||||
}
|
||||
@@ -725,13 +728,17 @@ emit_instruction_stream :: proc(
|
||||
}
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " store %s %d, ptr %%union_slot%d\n", llvm_type(tag_enum, &emitter.module.types), tag_value, instruction_index)
|
||||
offset := types.union_payload_offset(instruction.type, &emitter.module.types, emitter.module.target)
|
||||
fmt.sbprintf(&emitter.builder, " %%union_payload%d = getelementptr i8, ptr %%union_slot%d, i64 %d\n", instruction_index, instruction_index, offset)
|
||||
payload_ptr = fmt.tprintf("%%union_payload%d", instruction_index)
|
||||
if !void_payload {
|
||||
offset := types.union_payload_offset(instruction.type, &emitter.module.types, emitter.module.target)
|
||||
fmt.sbprintf(&emitter.builder, " %%union_payload%d = getelementptr i8, ptr %%union_slot%d, i64 %d\n", instruction_index, instruction_index, offset)
|
||||
payload_ptr = fmt.tprintf("%%union_payload%d", instruction_index)
|
||||
}
|
||||
}
|
||||
if !void_payload {
|
||||
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(fields[field_index].type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.args[0], fields[field_index].type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", ptr %s\n", payload_ptr)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(fields[field_index].type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.args[0], fields[field_index].type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", ptr %s\n", payload_ptr)
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%union_slot%d\n", instruction_index, type_name, instruction_index)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -211,7 +211,9 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
||||
case .Array, .Struct:
|
||||
args := make([]ir.Instruction_Id, len(expr.args), state.allocator)
|
||||
for arg, index in expr.args {
|
||||
args[index] = lower_nested_expr(state, arg)
|
||||
// A void union variant (`T{ variant }`) has no payload operand; leave it
|
||||
// invalid so codegen emits only the tag, not a trapping recovery value.
|
||||
args[index] = lower_nested_expr(state, arg) if arg != hir.INVALID_EXPR else ir.INVALID_INSTRUCTION
|
||||
}
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Aggregate, span=expr.span, type=expr.type, args=args, integer=expr.integer,
|
||||
|
||||
+30
-11
@@ -477,14 +477,18 @@ parse_struct_literal :: proc(
|
||||
break
|
||||
}
|
||||
advance(parser)
|
||||
if _, ok := allow(parser, .Equal); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '=' after struct field name")
|
||||
// A bare key (`T{ variant }`, no `= value`) constructs a void-payload union
|
||||
// variant; the checker validates that the field actually has a void type.
|
||||
value := ast.INVALID_EXPR
|
||||
key_end := field.span
|
||||
if _, ok := allow(parser, .Equal); ok {
|
||||
skip_newlines(parser)
|
||||
value = parse_expression_bp(parser, 0, nesting+1)
|
||||
key_end = parser.module.exprs[value].span
|
||||
}
|
||||
skip_newlines(parser)
|
||||
value := parse_expression_bp(parser, 0, nesting+1)
|
||||
append(&args, add_expr(parser, ast.Expr{
|
||||
kind=.Keyed,
|
||||
span=span_from(field.span, parser.module.exprs[value].span),
|
||||
span=span_from(field.span, key_end),
|
||||
name=field.symbol,
|
||||
left=value,
|
||||
right=ast.INVALID_EXPR,
|
||||
@@ -1544,20 +1548,33 @@ parse_arm_body :: proc(parser: ^Parser) -> []ast.Stmt_Id {
|
||||
return single
|
||||
}
|
||||
|
||||
// parse_match_arm parses one `<pattern> [|capture|]: <body>` arm (or `else: <body>`).
|
||||
// The pattern is `INVALID_EXPR` for `else`; `captures` holds the optional 0-or-1
|
||||
// payload capture name (tagged-union variants only).
|
||||
// parse_match_arm parses one `<pattern,...> [|[@]capture|]: <body>` arm (or
|
||||
// `else: <body>`). `patterns` is empty for `else`, one expr for a single pattern, or
|
||||
// several for a multi-pattern arm; `captures` holds the optional 0-or-1 payload capture
|
||||
// name with `pointer_capture` set for the `|@cap|` form (validated in the checker).
|
||||
parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
start := current(parser).span
|
||||
pattern := ast.INVALID_EXPR
|
||||
patterns: [dynamic]ast.Expr_Id
|
||||
patterns.allocator = parser.module.allocator
|
||||
captures: [dynamic]symbol.Id
|
||||
captures.allocator = parser.module.allocator
|
||||
pointer_capture := false
|
||||
if _, is_else := allow(parser, .Keyword_Else); !is_else {
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
pattern = parse_expression(parser)
|
||||
append(&patterns, parse_expression(parser))
|
||||
for {
|
||||
if _, ok := allow(parser, .Comma); !ok {
|
||||
break
|
||||
}
|
||||
skip_newlines(parser)
|
||||
append(&patterns, parse_expression(parser))
|
||||
}
|
||||
parser.no_struct_literal = saved
|
||||
if _, ok := allow(parser, .Pipe); ok {
|
||||
if _, at_ok := allow(parser, .At); at_ok {
|
||||
pointer_capture = true
|
||||
}
|
||||
name_tok := current(parser)
|
||||
if name_tok.kind == .Identifier || name_tok.kind == .Underscore {
|
||||
advance(parser)
|
||||
@@ -1578,8 +1595,10 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Match_Arm,
|
||||
span=span_from(start, previous(parser).span),
|
||||
expr=pattern,
|
||||
expr=ast.INVALID_EXPR,
|
||||
patterns=patterns[:],
|
||||
captures=captures[:],
|
||||
pointer_capture=pointer_capture,
|
||||
body=body,
|
||||
target=ast.INVALID_EXPR,
|
||||
update=ast.INVALID_STMT,
|
||||
|
||||
Reference in New Issue
Block a user