default struct fields
This commit is contained in:
@@ -304,6 +304,14 @@ Declaration_Alias :: struct {
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
|
||||
Struct_Field_Default :: struct {
|
||||
record: Type_Syntax,
|
||||
field: symbol.Id,
|
||||
expr: Expr_Id,
|
||||
pkg: Package_Id,
|
||||
file: File_Id,
|
||||
}
|
||||
|
||||
File :: struct {
|
||||
source: source.Source_Id,
|
||||
pkg: Package_Id,
|
||||
@@ -357,6 +365,7 @@ Module :: struct {
|
||||
c_trampolines: [dynamic]Trampoline,
|
||||
strings: [dynamic]string,
|
||||
type_fields: [dynamic]types.Field,
|
||||
struct_field_defaults: [dynamic]Struct_Field_Default,
|
||||
type_uses: [dynamic]Type_Use,
|
||||
type_store: types.Store,
|
||||
allocator: mem.Allocator,
|
||||
@@ -379,6 +388,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
||||
module.c_trampolines.allocator = allocator
|
||||
module.strings.allocator = allocator
|
||||
module.type_fields.allocator = allocator
|
||||
module.struct_field_defaults.allocator = allocator
|
||||
module.type_uses.allocator = allocator
|
||||
return module
|
||||
}
|
||||
@@ -436,6 +446,7 @@ destroy_module :: proc(module: ^Module) {
|
||||
delete(module.c_trampolines)
|
||||
delete(module.strings)
|
||||
delete(module.type_fields)
|
||||
delete(module.struct_field_defaults)
|
||||
delete(module.type_uses)
|
||||
types.destroy_store(&module.type_store)
|
||||
}
|
||||
|
||||
@@ -4471,12 +4471,19 @@ infer_compound_expr :: proc(
|
||||
}
|
||||
return value
|
||||
}
|
||||
fields := types.fields_for(store, value)
|
||||
item, item_ok := types.node(store, value)
|
||||
initialized := make([]bool, len(fields), checker.allocator)
|
||||
defer delete(initialized, checker.allocator)
|
||||
for keyed in expr.args {
|
||||
keyed_expr := checker.ast_module.exprs[keyed]
|
||||
if keyed_expr.left == ast.INVALID_EXPR {
|
||||
continue
|
||||
}
|
||||
slot, field, ok := find_struct_field_slot(checker, value, keyed_expr.name)
|
||||
if ok && item_ok {
|
||||
initialized[slot-int(item.field_start)] = true
|
||||
}
|
||||
if !ok || !is_inferred_record_field(checker, slot) {
|
||||
_ = infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types)
|
||||
continue
|
||||
@@ -4491,6 +4498,19 @@ infer_compound_expr :: proc(
|
||||
)
|
||||
}
|
||||
}
|
||||
if item_ok && item.kind == .Struct && !item.tuple {
|
||||
for field, index in fields {
|
||||
if initialized[index] {
|
||||
continue
|
||||
}
|
||||
if field_default, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
|
||||
_ = infer_nested_expr(
|
||||
checker, field_default.expr, nil, field_default.pkg, field_default.file,
|
||||
demanded, expected=field.type,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
case .Keyed:
|
||||
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
@@ -6098,6 +6118,27 @@ infer_all :: proc(checker: ^Checker) {
|
||||
checker.record_field_demands_dirty = false
|
||||
spec_count := len(checker.specs)
|
||||
|
||||
for field_default in checker.ast_module.struct_field_defaults {
|
||||
slot, field, ok := find_struct_field_slot(checker, field_default.record, field_default.field)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
expected := field.type if is_runtime_type(checker, field.type) else types.INVALID
|
||||
inferred := infer_expr(
|
||||
checker, field_default.expr, nil, field_default.pkg, field_default.file,
|
||||
expected=expected,
|
||||
)
|
||||
_ = record_field_expr_candidate(
|
||||
checker, slot, field_default.expr, inferred, nil, field_default.pkg, field_default.file,
|
||||
)
|
||||
if is_runtime_type(checker, checker.module.types.fields[slot].type) {
|
||||
_ = record_demand(
|
||||
checker, field_default.expr, checker.module.types.fields[slot].type,
|
||||
nil, nil, field_default.pkg, field_default.file,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Backward demands: a global pushes its own (declared or already-resolved) type
|
||||
// onto open numeric slots reachable through names and numeric arithmetic.
|
||||
for global, index in checker.ast_module.globals {
|
||||
@@ -6753,6 +6794,21 @@ find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symb
|
||||
return 0, {}, false
|
||||
}
|
||||
|
||||
find_struct_field_default :: proc(
|
||||
checker: ^Checker,
|
||||
struct_type: types.Type,
|
||||
name: symbol.Id,
|
||||
) -> (ast.Struct_Field_Default, bool) {
|
||||
resolved := types.resolve_alias(struct_type, &checker.module.types)
|
||||
for field_default in checker.ast_module.struct_field_defaults {
|
||||
if types.resolve_alias(field_default.record, &checker.module.types) == resolved &&
|
||||
field_default.field == name {
|
||||
return field_default, true
|
||||
}
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
|
||||
find_tuple_field :: proc(checker: ^Checker, tuple_type: types.Type, index: u64) -> (int, types.Field, bool) {
|
||||
item, ok := types.node(&checker.module.types, tuple_type)
|
||||
if !ok || item.kind != .Struct || !item.tuple || index >= u64(item.field_count) {
|
||||
@@ -7895,6 +7951,17 @@ build_compound_expr :: proc(
|
||||
if values[index] != hir.INVALID_EXPR {
|
||||
continue
|
||||
}
|
||||
if field_default, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
|
||||
values[index] = build_nested_expr(
|
||||
checker, field_default.expr, nil, global_reads, calls,
|
||||
field.type, field_default.pkg, field_default.file,
|
||||
)
|
||||
values[index] = coerce_expr(
|
||||
checker, values[index], field.type,
|
||||
checker.ast_module.exprs[field_default.expr].span,
|
||||
)
|
||||
continue
|
||||
}
|
||||
id := source.addf(checker.diagnostics, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
|
||||
delete(values, checker.allocator)
|
||||
return invalid_hir_expr(checker, expr.span, id, struct_type)
|
||||
|
||||
@@ -1505,9 +1505,25 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
|
||||
}
|
||||
if !union_record {
|
||||
for field, index in fields {
|
||||
if values[index] == INVALID_CT_VALUE {
|
||||
if values[index] != INVALID_CT_VALUE {
|
||||
continue
|
||||
}
|
||||
field_default, has_default := find_struct_field_default(checker, struct_type, symbol.Id(field.name))
|
||||
if !has_default {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
|
||||
}
|
||||
previous_pkg, previous_file := state.pkg, state.file
|
||||
state.pkg, state.file = field_default.pkg, field_default.file
|
||||
value, flow, ok := ct_eval_expr(state, field_default.expr, field.type, depth+1)
|
||||
state.pkg, state.file = previous_pkg, previous_file
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
value, ok = ct_coerce_value(state, value, field.type, checker.ast_module.exprs[field_default.expr].span)
|
||||
if !ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
values[index] = value
|
||||
}
|
||||
}
|
||||
start := u32(len(state.children))
|
||||
|
||||
@@ -1886,6 +1886,9 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
||||
for &field in module.type_fields {
|
||||
field.type = canonical_type(module, field.type, mapping, visiting)
|
||||
}
|
||||
for &field_default in module.struct_field_defaults {
|
||||
field_default.record = canonical_type(module, field_default.record, mapping, visiting)
|
||||
}
|
||||
for index := 0; index < original_count; index += 1 {
|
||||
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
|
||||
}
|
||||
|
||||
@@ -2532,6 +2532,7 @@ parse_record_body :: proc(
|
||||
allow_anonymous_struct_payload := false,
|
||||
allow_keyword_names := false,
|
||||
tuple_result: ^bool = nil,
|
||||
defaults: ^[dynamic]ast.Expr_Id = nil,
|
||||
) -> bool {
|
||||
if _, ok := allow(parser, .Left_Brace); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, expected_open)
|
||||
@@ -2557,6 +2558,9 @@ parse_record_body :: proc(
|
||||
mode = 2
|
||||
field_type := parse_type(parser)
|
||||
append(fields, types.Field{name=0, type=field_type})
|
||||
if defaults != nil {
|
||||
append(defaults, ast.INVALID_EXPR)
|
||||
}
|
||||
} else {
|
||||
if mode == 2 {
|
||||
source.add(parser.diagnostics, current(parser).span, "struct fields cannot mix named and unnamed forms")
|
||||
@@ -2575,6 +2579,14 @@ parse_record_body :: proc(
|
||||
}
|
||||
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
|
||||
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
|
||||
if defaults != nil {
|
||||
value := ast.INVALID_EXPR
|
||||
if _, ok := allow(parser, .Equal); ok {
|
||||
skip_newlines(parser)
|
||||
value = parse_expression(parser)
|
||||
}
|
||||
append(defaults, value)
|
||||
}
|
||||
}
|
||||
if _, ok := allow(parser, .Comma); ok {
|
||||
skip_newlines(parser)
|
||||
@@ -2678,6 +2690,9 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden:
|
||||
fields: [dynamic]types.Field
|
||||
fields.allocator = parser.module.allocator
|
||||
defer delete(fields)
|
||||
defaults: [dynamic]ast.Expr_Id
|
||||
defaults.allocator = parser.module.allocator
|
||||
defer delete(defaults)
|
||||
allow_anonymous_struct_payload := is_union && (inferred_tag || types.is_valid(declared_tag))
|
||||
tuple := false
|
||||
_ = parse_record_body(
|
||||
@@ -2687,7 +2702,13 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden:
|
||||
allow_anonymous_struct_payload,
|
||||
allow_anonymous_struct_payload,
|
||||
&tuple,
|
||||
&defaults,
|
||||
)
|
||||
for value in defaults {
|
||||
if value != ast.INVALID_EXPR && (c_layout || is_union || tuple) {
|
||||
source.add(parser.diagnostics, parser.module.exprs[value].span, "field defaults are only supported by named native structs")
|
||||
}
|
||||
}
|
||||
if tuple && (c_layout || is_union) {
|
||||
source.add(parser.diagnostics, start.span, "unnamed fields are only supported by native structs")
|
||||
tuple = false
|
||||
@@ -2697,6 +2718,19 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden:
|
||||
}
|
||||
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag, tuple=tuple) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
} else if !c_layout && !is_union && !tuple {
|
||||
for value, index in defaults {
|
||||
if value == ast.INVALID_EXPR {
|
||||
continue
|
||||
}
|
||||
append(&parser.module.struct_field_defaults, ast.Struct_Field_Default{
|
||||
record=id,
|
||||
field=symbol.Id(fields[index].name),
|
||||
expr=value,
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user