contextual payload construction

This commit is contained in:
2026-06-30 21:14:59 +02:00
parent 7ca7e33033
commit 5161e99af5
5 changed files with 450 additions and 85 deletions
+31 -12
View File
@@ -758,7 +758,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
if expr.left != ast.INVALID_EXPR {
append(&stack, expr.left)
}
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed:
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal:
append(&stack, expr.left)
case .Catch:
append(&stack, expr.left)
@@ -768,7 +768,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
mark_block_imports_used(checker, expr.body, file)
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name, .Enum_Literal:
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name:
}
}
}
@@ -1331,6 +1331,9 @@ infer_compound_expr :: proc(
case .Undefined:
return types.INVALID
case .Enum_Literal:
if expr.left != ast.INVALID_EXPR {
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
}
return types.INVALID
case .Address:
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
@@ -3256,29 +3259,45 @@ build_compound_expr :: proc(
)
return invalid_hir_expr(checker, expr.span, id, expected)
case .Enum_Literal:
// A bare enum literal in a tagged-union context constructs a variant. Only a
// void-payload variant can be built this way (it has no value); a payload variant
// must use `T{ variant = ... }`. (`.variant{...}` payload construction is the
// milestone-23 error-channel form.)
if types.is_tagged_union(expected, store) {
index, field, found := find_struct_field(checker, expected, expr.name)
if !found {
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
return invalid_hir_expr(checker, expr.span, id, expected)
}
if !types.is_void(field.type) {
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
symbol_text(checker, expr.name), type_label(checker, expected), symbol_text(checker, expr.name))
return invalid_hir_expr(checker, expr.span, id, expected)
}
values := make([]hir.Expr_Id, 1, checker.allocator)
values[0] = hir.INVALID_EXPR
if expr.left == ast.INVALID_EXPR {
if !types.is_void(field.type) {
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
symbol_text(checker, expr.name), type_label(checker, expected), symbol_text(checker, expr.name))
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, id, expected)
}
values[0] = hir.INVALID_EXPR
} else {
if types.is_void(field.type) {
id := source.addf(checker.diagnostics, expr.span, "void variant '%s' takes no value", symbol_text(checker, expr.name))
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, id, expected)
}
values[0] = build_nested_expr(checker, expr.left, locals, global_reads, calls, field.type, pkg, file)
values[0] = coerce_expr(checker, values[0], field.type, checker.module.exprs[values[0]].span)
}
return add_hir_expr(checker, hir.Expr{
kind=.Struct, span=expr.span, type=expected, args=values, integer=i64(index),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if expr.left != ast.INVALID_EXPR {
id := source.addf(
checker.diagnostics,
expr.span,
"'.%s{...}' requires a tagged-union context",
symbol_text(checker, expr.name),
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
if !types.is_enum(expected, store) {
id := source.addf(
checker.diagnostics,
+173 -59
View File
@@ -366,23 +366,40 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
return types.INVALID
}
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
left := parse_type_atom(parser)
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type_Syntax {
result := left
for current(parser).kind == .Pipe {
operator := advance(parser)
right := parse_type_atom(parser)
composed, compose_error := types.compose_sum(&parser.module.type_store, left, right)
composed, compose_error := types.compose_sum(&parser.module.type_store, result, right)
if compose_error == .Unsupported {
source.add(parser.diagnostics, operator.span, "only native unbacked enums and tagged unions can be composed with '|'")
left = types.INVALID
result = types.INVALID
} else if compose_error == .Conflict {
source.add(parser.diagnostics, operator.span, "sum composition contains the same variant name with different payload types")
left = types.INVALID
result = types.INVALID
} else {
left = composed
result = composed
}
}
return left
return result
}
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
return parse_type_pipe_tail(parser, parse_type_atom(parser))
}
parse_error_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
left := types.INVALID
#partial switch current(parser).kind {
case .Keyword_Enum:
left = parse_inline_enum_type(parser)
case .Keyword_Union:
left = parse_inline_union_type(parser)
case:
left = parse_type_atom(parser)
}
return parse_type_pipe_tail(parser, left)
}
skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
@@ -650,11 +667,36 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
return invalid_expr(parser, member.span, "expected an enum member after '.'")
}
advance(parser)
payload := ast.INVALID_EXPR
end := member.span
if _, ok := allow(parser, .Left_Brace); ok {
parser.delimiter_depth += 1
skip_newlines(parser)
if current(parser).kind == .Right_Brace {
source.add(parser.diagnostics, current(parser).span, "contextual variant payload requires exactly one expression")
} else {
payload = parse_expression_bp(parser, 0, nesting+1)
skip_newlines(parser)
if _, comma_ok := allow(parser, .Comma); comma_ok {
source.add(parser.diagnostics, current(parser).span, "contextual variant payload requires exactly one expression")
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
advance(parser)
}
}
}
right_brace, close_ok := allow(parser, .Right_Brace)
if !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after contextual variant payload")
right_brace = member
}
parser.delimiter_depth -= 1
end = right_brace.span
}
return add_expr(parser, ast.Expr{
kind=.Enum_Literal,
span=span_from(start.span, member.span),
span=span_from(start.span, end),
name=member.symbol,
left=ast.INVALID_EXPR,
left=payload,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
@@ -1871,7 +1913,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
result := parse_type(parser)
error_type := types.INVALID
if _, ok := allow(parser, .Bang); ok {
error_type = parse_type(parser)
error_type = parse_error_type(parser)
}
end := previous(parser)
ended_by_newline := current(parser).kind == .Newline
@@ -1917,6 +1959,67 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
})
}
parse_record_body :: proc(parser: ^Parser, fields: ^[dynamic]types.Field, expected_open: string) -> bool {
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, expected_open)
return false
}
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
if current(parser).kind != .Identifier {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
}
skip_newlines(parser)
continue
}
field_name := advance(parser)
field_type := parse_type(parser)
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
}
_ = finish_statement(parser, true)
}
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
}
return true
}
parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
advance(parser)
valid := true
if _, ok := allow(parser, .Left_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '(enum)' after inline union error type")
valid = false
} else {
if _, enum_ok := allow(parser, .Keyword_Enum); !enum_ok {
source.add(parser.diagnostics, current(parser).span, "inline union error types must use 'union(enum)'")
if current(parser).kind != .Right_Paren {
_ = parse_type(parser)
}
valid = false
}
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after union tag")
valid = false
}
}
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
defer delete(fields)
if !parse_record_body(parser, &fields, "expected '{' after inline union error type") || !valid {
return types.INVALID
}
tag := synthesize_union_tag(parser, fields[:])
return types.union_anonymous(&parser.module.type_store, fields[:], tag)
}
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_union := false) {
start := advance(parser)
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
@@ -1954,34 +2057,10 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
}
return
}
advance(parser)
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
defer delete(fields)
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
if current(parser).kind != .Identifier {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
}
skip_newlines(parser)
continue
}
field_name := advance(parser)
field_type := parse_type(parser)
append(&fields, types.Field{name=u32(field_name.symbol), type=field_type})
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
}
_ = finish_statement(parser, true)
}
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
}
_ = parse_record_body(parser, &fields, "expected '{' after struct fields")
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
tag = synthesize_union_tag(parser, fields[:])
}
@@ -2033,26 +2112,19 @@ parse_alias :: proc(parser: ^Parser, name: token.Token) {
_ = finish_statement(parser)
}
parse_enum :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
explicit_backing := false
backing := types.INVALID
if _, ok := allow(parser, .Left_Paren); ok {
explicit_backing = true
backing = parse_type(parser)
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after enum backing type")
}
}
parse_enum_body :: proc(
parser: ^Parser,
start: source.Span,
explicit_backing: bool,
backing: ^types.Type,
members: ^[dynamic]types.Enum_Member,
expected_open: string,
) -> bool {
skip_newlines(parser)
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '{' after enum declaration")
_ = finish_statement(parser)
return
source.add(parser.diagnostics, current(parser).span, expected_open)
return false
}
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
next_value: i128
previous_value: i128
has_previous := false
@@ -2070,7 +2142,7 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
}
member := advance(parser)
duplicate := false
for existing in members {
for existing in members^ {
if existing.name == u32(member.symbol) {
duplicate = true
break
@@ -2108,7 +2180,7 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
source.add(parser.diagnostics, member.span, "enum values must be strictly increasing")
}
if !duplicate {
append(&members, types.Enum_Member{name=u32(member.symbol), value=value})
append(members, types.Enum_Member{name=u32(member.symbol), value=value})
}
previous_value = value
has_previous = true
@@ -2122,19 +2194,61 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after enum members")
}
if len(members) == 0 {
source.add(parser.diagnostics, start.span, "enum declarations require at least one member")
if len(members^) == 0 {
source.add(parser.diagnostics, start, "enum declarations require at least one member")
}
if !explicit_backing {
backing = types.U16
for &member in members {
backing^ = types.U16
for &member in members^ {
id, ok := types.variant_id(&parser.module.type_store, member.name, types.VOID)
if !ok {
source.add(parser.diagnostics, start.span, "too many global sum variants for u16 tags")
source.add(parser.diagnostics, start, "too many global sum variants for u16 tags")
}
member.value = i128(id)
}
}
return true
}
parse_inline_enum_type :: proc(parser: ^Parser) -> types.Type {
start := advance(parser)
valid := true
if _, ok := allow(parser, .Left_Paren); ok {
source.add(parser.diagnostics, start.span, "inline enum error types cannot declare a backing type")
_ = parse_type(parser)
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after enum backing type")
}
valid = false
}
backing := types.INVALID
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
if !parse_enum_body(parser, start.span, false, &backing, &members, "expected '{' after inline enum error type") || !valid {
return types.INVALID
}
return types.enum_anonymous(&parser.module.type_store, members[:], backing)
}
parse_enum :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
explicit_backing := false
backing := types.INVALID
if _, ok := allow(parser, .Left_Paren); ok {
explicit_backing = true
backing = parse_type(parser)
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after enum backing type")
}
}
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
if !parse_enum_body(parser, start.span, explicit_backing, &backing, &members, "expected '{' after enum declaration") {
_ = finish_statement(parser)
return
}
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))