anonymous struct payloads

This commit is contained in:
2026-06-30 23:01:18 +02:00
parent eb06b2ee62
commit d6f9c24314
6 changed files with 317 additions and 38 deletions
+18 -6
View File
@@ -1417,6 +1417,9 @@ infer_compound_expr :: proc(
for keyed in expr.args {
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded, local_types)
}
if !symbol.is_valid(expr.name) {
return types.INVALID
}
target_pkg, available := expr_package(checker, expr, pkg, file)
value := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
return types.resolve_alias(value, store)
@@ -3659,12 +3662,21 @@ build_compound_expr :: proc(
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Struct_Literal:
target_pkg, available := expr_package(checker, expr, pkg, file, true)
struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
struct_type = types.resolve_alias(struct_type, store)
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
return invalid_hir_expr(checker, expr.span, id)
struct_type := types.INVALID
if symbol.is_valid(expr.name) {
target_pkg, available := expr_package(checker, expr, pkg, file, true)
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
struct_type = types.resolve_alias(struct_type, store)
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
return invalid_hir_expr(checker, expr.span, id)
}
} else {
struct_type = types.resolve_alias(expected, store)
if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) {
id := source.add(checker.diagnostics, expr.span, "keyed contextual payload requires a struct payload")
return invalid_hir_expr(checker, expr.span, id, expected)
}
}
fields := types.fields_for(store, struct_type)
union_record := types.is_union(struct_type, store)
+74 -19
View File
@@ -494,13 +494,12 @@ parse_array_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
})
}
parse_struct_literal :: proc(
parse_keyed_initializers :: proc(
parser: ^Parser,
qualifier: symbol.Id,
first, name: token.Token,
left_brace: token.Token,
nesting: int,
) -> ast.Expr_Id {
left_brace := advance(parser)
close_message: string,
) -> ([]ast.Expr_Id, token.Token) {
parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1
args: [dynamic]ast.Expr_Id
@@ -539,9 +538,20 @@ parse_struct_literal :: proc(
}
right_brace, ok := allow(parser, .Right_Brace)
if !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct literal")
source.add(parser.diagnostics, current(parser).span, close_message)
right_brace = left_brace
}
return args[:], right_brace
}
parse_struct_literal :: proc(
parser: ^Parser,
qualifier: symbol.Id,
first, name: token.Token,
nesting: int,
) -> ast.Expr_Id {
left_brace := advance(parser)
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
return add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=source.Span{file=name.span.file, start=first.span.start, end=right_brace.span.end},
@@ -669,11 +679,32 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
advance(parser)
payload := ast.INVALID_EXPR
end := member.span
if _, ok := allow(parser, .Left_Brace); ok {
if left_brace, ok := allow(parser, .Left_Brace); ok {
parser.delimiter_depth += 1
skip_newlines(parser)
if current(parser).kind == .Right_Brace {
if current(parser).kind == .Identifier && peek(parser).kind == .Equal {
parser.delimiter_depth -= 1
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after contextual variant payload")
payload = add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=span_from(left_brace.span, right_brace.span),
args=args,
qualifier=symbol.INVALID,
name=symbol.INVALID,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
end = right_brace.span
} else if current(parser).kind == .Right_Brace {
source.add(parser.diagnostics, current(parser).span, "contextual variant payload requires exactly one expression")
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
} else {
payload = parse_expression_bp(parser, 0, nesting+1)
skip_newlines(parser)
@@ -683,14 +714,14 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
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
}
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,
@@ -1959,7 +1990,19 @@ 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 {
parse_record_field_type :: proc(parser: ^Parser, allow_anonymous_struct_payload: bool) -> types.Type {
if allow_anonymous_struct_payload && current(parser).kind == .Keyword_Struct {
return parse_inline_struct_payload_type(parser)
}
return parse_type(parser)
}
parse_record_body :: proc(
parser: ^Parser,
fields: ^[dynamic]types.Field,
expected_open: string,
allow_anonymous_struct_payload := false,
) -> bool {
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, expected_open)
return false
@@ -1977,7 +2020,7 @@ parse_record_body :: proc(parser: ^Parser, fields: ^[dynamic]types.Field, expect
continue
}
field_name := advance(parser)
field_type := parse_type(parser)
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
@@ -1991,6 +2034,17 @@ parse_record_body :: proc(parser: ^Parser, fields: ^[dynamic]types.Field, expect
return true
}
parse_inline_struct_payload_type :: proc(parser: ^Parser) -> types.Type {
advance(parser)
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
defer delete(fields)
if !parse_record_body(parser, &fields, "expected '{' after anonymous struct payload") {
return types.INVALID
}
return types.struct_anonymous(&parser.module.type_store, fields[:])
}
parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
advance(parser)
valid := true
@@ -2013,7 +2067,7 @@ parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
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 {
if !parse_record_body(parser, &fields, "expected '{' after inline union error type", true) || !valid {
return types.INVALID
}
tag := synthesize_union_tag(parser, fields[:])
@@ -2060,7 +2114,8 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
defer delete(fields)
_ = parse_record_body(parser, &fields, "expected '{' after struct fields")
allow_anonymous_struct_payload := is_union && (inferred_tag || types.is_valid(declared_tag))
_ = parse_record_body(parser, &fields, "expected '{' after struct fields", allow_anonymous_struct_payload)
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
tag = synthesize_union_tag(parser, fields[:])
}
+36
View File
@@ -306,6 +306,42 @@ union_anonymous :: proc(store: ^Store, fields: []Field, tag: Type) -> Type {
})
}
anonymous_struct_fields_equal :: proc(store: ^Store, item: Node, fields: []Field) -> bool {
if item.field_count != u32(len(fields)) {
return false
}
start := int(item.field_start)
end := start+int(item.field_count)
if start < 0 || end > len(store.fields) {
return false
}
for field, index in fields {
existing := store.fields[start+index]
if existing.name != field.name || existing.type != field.type {
return false
}
}
return true
}
struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
for existing, index in store.nodes {
if existing.kind == .Struct && existing.name == 0 && existing.declared &&
!existing.c_layout && !existing.opaque &&
anonymous_struct_fields_equal(store, existing, fields) {
return DYNAMIC_START+Type(index)
}
}
start := u32(len(store.fields))
append(&store.fields, ..fields)
return intern(store, Node{
kind=.Struct,
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 {