struct type construction

This commit is contained in:
2026-07-19 21:35:48 +02:00
parent 944648fce6
commit 59bbb197e0
15 changed files with 331415 additions and 253804 deletions
+7 -2
View File
@@ -177,20 +177,24 @@ exactness follows floating-point equality. Other float NaN and infinity behavior
underlying IEEE operations. Ordinary float `/` remains unchecked and therefore preserves IEEE
infinity/NaN behavior.
Only the six bang calls are intrinsic. Bare calls such as `divfloor(a, b)` and qualified calls such
as `math.divfloor(a, b)` resolve to ordinary functions.
Only these six division bang calls select integer-division behavior. Bare calls such as
`divfloor(a, b)` and qualified calls such as `math.divfloor(a, b)` resolve to ordinary functions.
### functions, C interop, and linking
- demand-monomorphized Brolang and C-ABI functions
- integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value and omitted from the runtime ABI
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
- later comptime value parameters may depend on earlier type parameters, as in `factory func($T type, $default T) type`
- comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities; equal structural values and aliases of one function declaration share specializations, while distinct declarations remain distinct and pointers, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity
- comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, exact type `==`/`!=`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
- `struct_type!(layout, names, types, defaults)` constructs a nominal record type from comptime fixed arrays or tuples and is valid in every type position; layout is `.auto` or `.c`, bare `none` means a required field, and `some!(none)` installs an optional `none` default
- `some!(value)` explicitly constructs the present branch of an expected optional, including nested optionals where `some!(none)` differs from outer `none`
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
- anonymous keyed records use `{x = 1, name = "bro"}`; without context their declaration-ordered names and inferred value types form a structurally interned record type, while a record context applies that type's coercions and field defaults; `{}` remains an empty tuple without context and constructs an empty contextual record when a record is expected
- `typeinfo!`, `field!`, `compile_error!`, and semantic `expand for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and expand-loop `break` / `continue` must be selected entirely at comptime
- `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name
- bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
@@ -213,6 +217,7 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist`
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order, where outer `none` means no field default
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
+198 -15
View File
@@ -166,6 +166,15 @@ Generated_Type_Entry :: struct {
result: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
defaults: []Ct_Value_Id,
}
Resolved_Field_Default :: struct {
expr: ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
static_value: Ct_Value_Id,
span: source.Span,
}
Type_Factory_Origin :: struct {
@@ -1227,6 +1236,9 @@ is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0)
if depth > 256 || !types.is_valid(value) {
return false
}
if is_type_metatype_syntax(checker, value) {
return true
}
if is_runtime_type(checker, value) {
return true
}
@@ -1930,12 +1942,15 @@ call_mapping_mode :: proc(function: ast.Function, mapping: []int) -> Call_Argume
return .Explicit
}
comptime_binding_index :: proc(function: ast.Function, _: int, name: symbol.Id) -> (int, bool) {
comptime_binding_index :: proc(function: ast.Function, prefix: int, name: symbol.Id) -> (int, bool) {
ordinal := 0
for param in function.params {
if !param.comptime_value {
continue
}
if ordinal >= prefix {
return -1, false
}
if param.name == name {
return ordinal, true
}
@@ -2299,6 +2314,7 @@ type_pattern_mentions_comptime :: proc(
prefix: int,
pattern: types.Type,
depth := 0,
type_params_only := false,
) -> bool {
if depth > 64 {
return false
@@ -2309,8 +2325,14 @@ type_pattern_mentions_comptime :: proc(
}
if item.qualifier == 0 && item.name != 0 {
if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found {
if !type_params_only {
return true
}
param, param_ok := comptime_param_for_name(function, symbol.Id(item.name))
if param_ok && is_comptime_type_param(checker, param) {
return true
}
}
}
if item.kind == .Array && item.unresolved_count {
expr_id := ast.Expr_Id(item.count_expr)
@@ -2318,8 +2340,14 @@ type_pattern_mentions_comptime :: proc(
expr := checker.ast_module.exprs[expr_id]
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
if _, found := comptime_binding_index(function, prefix, expr.name); found {
if !type_params_only {
return true
}
param, param_ok := comptime_param_for_name(function, expr.name)
if param_ok && is_comptime_type_param(checker, param) {
return true
}
}
}
}
}
@@ -2333,21 +2361,27 @@ type_pattern_mentions_comptime :: proc(
arg := checker.ast_module.exprs[arg_id]
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
if _, found := comptime_binding_index(function, prefix, arg.name); found {
if !type_params_only {
return true
}
param, param_ok := comptime_param_for_name(function, arg.name)
if param_ok && is_comptime_type_param(checker, param) {
return true
}
}
}
}
}
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1) {
}
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1, type_params_only) {
return true
}
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1) {
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1, type_params_only) {
return true
}
if item.kind == .Function {
for field in types.params_for(&checker.module.types, pattern) {
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1) {
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1, type_params_only) {
return true
}
}
@@ -2690,6 +2724,10 @@ infer_call_comptime_values :: proc(
) && matched
}
}
all_bound = true
for value_bound in bound {
all_bound = all_bound && value_bound
}
// Concrete arguments bind first. Numeric constants are contextual and therefore
// only contribute their default type after stronger evidence has had a chance.
weak_passes := [2]bool{false, true}
@@ -2709,6 +2747,14 @@ infer_call_comptime_values :: proc(
if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
continue
}
// Once the result type has fixed every comptime parameter, an anonymous
// keyed record must be checked against the specialized parameter type.
// Its provisional structural type intentionally contains only the supplied
// fields, so comparing that type here would reject omitted defaulted fields.
arg_expr := checker.ast_module.exprs[arg_id]
if all_bound && arg_expr.kind == .Struct_Literal && !arg_expr.tuple && !symbol.is_valid(arg_expr.name) {
continue
}
if weak {
if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok &&
item.qualifier == 0 && item.name != 0 {
@@ -2951,6 +2997,19 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
source.add(checker.diagnostics, expr.span, "type position requires a direct type-factory call")
return types.INVALID
}
if expr.intrinsic {
if !is_intrinsic_call(checker, expr, "struct_type") {
return types.INVALID
}
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values)
value, flow, ok := ct_eval_call_expr(&state, expr, types.INVALID, 0, expr_id)
result := types.INVALID
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
result = types.Type(state.values[value].index)
}
ct_state_destroy(&state)
return result
}
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
_ = add_package_resolution_diagnostic(checker, expr, file)
@@ -3009,7 +3068,7 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
resolving=true,
})
state := ct_state_make(checker, pkg, file)
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0)
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0, expr_id)
result := types.INVALID
if eval_ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
result = types.Type(state.values[value].index)
@@ -3502,12 +3561,16 @@ validate_declarations :: proc(checker: ^Checker) {
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
comptime_prefix := 0
for param in function.params {
param_type := types.INVALID
if param.comptime_value || !has_comptime {
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
}
if param.comptime_value && !signature_poisoned {
dependent := type_pattern_mentions_comptime(
checker, function, comptime_prefix, param.type, type_params_only=true,
)
if function.c_abi {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
@@ -3516,7 +3579,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
if !is_type_metatype_syntax(checker, param.type) &&
!is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
!dependent && !is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
@@ -3538,6 +3601,9 @@ validate_declarations :: proc(checker: ^Checker) {
continue
}
}
if param.comptime_value {
comptime_prefix += 1
}
if param.type == types.VOID {
source.add(
checker.diagnostics,
@@ -4594,6 +4660,26 @@ infer_compound_expr :: proc(
}
return value
}
if !types.is_valid(value) {
fields := make([]types.Field, len(expr.args), checker.allocator)
defer delete(fields, checker.allocator)
valid := true
for keyed, index in expr.args {
keyed_expr := checker.ast_module.exprs[keyed]
fields[index].name = u32(keyed_expr.name)
if keyed_expr.left == ast.INVALID_EXPR {
valid = false
continue
}
for previous in fields[:index] {
valid = valid && previous.name != fields[index].name
}
fields[index].type = infer_nested_expr(
checker, keyed_expr.left, locals, pkg, file, demanded, local_types,
)
}
return types.struct_anonymous(store, fields) if valid else types.INVALID
}
fields := types.fields_for(store, value)
item, item_ok := types.node(store, value)
initialized := make([]bool, len(fields), checker.allocator)
@@ -4627,6 +4713,9 @@ infer_compound_expr :: proc(
continue
}
if field_default, default_values, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
if field_default.static_value != INVALID_CT_VALUE {
continue
}
previous := checker.current_comptime_values
checker.current_comptime_values = default_values
_ = infer_nested_expr(
@@ -4893,6 +4982,17 @@ infer_expr :: proc(
_ = pop(&stack)
continue
}
if is_intrinsic_call(checker, expr, "some") {
if len(expr.args) == 1 && types.is_optional(frame.expected, &checker.module.types) {
child := types.child_type(frame.expected, &checker.module.types)
_ = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, child)
last = frame.expected
} else {
last = types.INVALID
}
_ = pop(&stack)
continue
}
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
ok && types.is_enum(enum_type, &checker.module.types) {
@@ -6925,12 +7025,18 @@ find_struct_field_default :: proc(
checker: ^Checker,
struct_type: types.Type,
name: symbol.Id,
) -> (ast.Struct_Field_Default, []Comptime_Value, bool) {
) -> (Resolved_Field_Default, []Comptime_Value, 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, nil, true
return Resolved_Field_Default{
expr=field_default.expr,
pkg=field_default.pkg,
file=field_default.file,
static_value=INVALID_CT_VALUE,
span=checker.ast_module.exprs[field_default.expr].span,
}, nil, true
}
}
for entry in checker.generated_types {
@@ -6941,18 +7047,32 @@ find_struct_field_default :: proc(
expr := checker.ast_module.exprs[entry.expr]
fields := types.fields_for(&checker.module.types, resolved)
for field, index in fields {
if field.name != u32(name) || index >= len(expr.args) || expr.args[index] == ast.INVALID_EXPR {
if field.name != u32(name) {
continue
}
return ast.Struct_Field_Default{
record=resolved,
field=name,
if index < len(entry.defaults) && entry.defaults[index] != INVALID_CT_VALUE {
return Resolved_Field_Default{
expr=ast.INVALID_EXPR,
pkg=entry.pkg,
file=entry.file,
static_value=entry.defaults[index],
span=expr.span,
}, entry.values, true
}
if is_intrinsic_call(checker, expr, "struct_type") {
continue
}
if index < len(expr.args) && expr.args[index] != ast.INVALID_EXPR {
return Resolved_Field_Default{
expr=expr.args[index],
pkg=entry.pkg,
file=entry.file,
static_value=INVALID_CT_VALUE,
span=checker.ast_module.exprs[expr.args[index]].span,
}, entry.values, true
}
}
}
return {}, nil, false
}
@@ -8083,10 +8203,11 @@ build_compound_expr :: proc(
return invalid_hir_expr(checker, expr.span, id)
}
} else if expr.tuple {
struct_type = types.INVALID
struct_type = types.resolve_alias(expected, store)
} else {
struct_type = types.resolve_alias(expected, store)
if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) {
if types.is_valid(struct_type) &&
(!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)
}
@@ -8131,6 +8252,34 @@ build_compound_expr :: proc(
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if !types.is_valid(struct_type) {
fields := make([]types.Field, len(expr.args), checker.allocator)
defer delete(fields, checker.allocator)
values := make([]hir.Expr_Id, len(expr.args), checker.allocator)
for keyed, index in expr.args {
keyed_expr := checker.ast_module.exprs[keyed]
fields[index].name = u32(keyed_expr.name)
for previous in fields[:index] {
if previous.name == fields[index].name {
source.addf(
checker.diagnostics, keyed_expr.span,
"duplicate initializer for struct field '%s'",
symbol_text(checker, keyed_expr.name),
)
}
}
values[index] = build_nested_expr(
checker, keyed_expr.left, locals, global_reads, calls, types.INVALID, pkg, file,
)
fields[index].type = checker.module.exprs[values[index]].type
}
struct_type = types.struct_anonymous(store, fields)
return add_hir_expr(checker, hir.Expr{
kind=.Struct, span=expr.span, type=struct_type, args=values,
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
fields := types.fields_for(store, struct_type)
union_record := types.is_union(struct_type, store)
if union_record && len(expr.args) != 1 {
@@ -8177,6 +8326,16 @@ build_compound_expr :: proc(
continue
}
if field_default, default_values, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
if field_default.static_value != INVALID_CT_VALUE {
if int(field_default.static_value) >= len(checker.static_state.values) {
continue
}
values[index] = build_static_value(
checker, checker.static_state.values[field_default.static_value], field_default.span, field.type,
)
values[index] = coerce_expr(checker, values[index], field.type, field_default.span)
continue
}
previous := checker.current_comptime_values
checker.current_comptime_values = default_values
values[index] = build_nested_expr(
@@ -8186,7 +8345,7 @@ build_compound_expr :: proc(
checker.current_comptime_values = previous
values[index] = coerce_expr(
checker, values[index], field.type,
checker.ast_module.exprs[field_default.expr].span,
field_default.span,
)
continue
}
@@ -8565,6 +8724,29 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
if is_intrinsic_call(checker, expr, "some") {
if len(expr.args) != 1 {
id := source.addf(checker.diagnostics, expr.span, "some! expects 1 argument, got %d", len(expr.args))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if !types.is_optional(frame.expected, &checker.module.types) {
id := source.add(checker.diagnostics, expr.span, "some! requires an optional context")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
child_type := types.child_type(frame.expected, &checker.module.types)
child := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, child_type, pkg, file)
child = coerce_expr(checker, child, child_type, checker.ast_module.exprs[expr.args[0]].span)
last = add_hir_expr(checker, hir.Expr{
kind=.Optional_Some, span=expr.span, type=frame.expected, left=child,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
continue
}
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
ok && types.is_enum(enum_type, &checker.module.types) {
@@ -13436,6 +13618,7 @@ check :: proc(
}
for entry in checker.generated_types {
delete(entry.values, allocator)
delete(entry.defaults, allocator)
}
for origin in checker.type_factory_origins {
delete(origin.values, allocator)
+304 -3
View File
@@ -1323,7 +1323,7 @@ ct_eval_expr :: proc(
}
return ct_eval_expr(state, expr.right, types.BOOL, depth+1)
case .Call:
return ct_eval_call_expr(state, expr, expected, depth+1)
return ct_eval_call_expr(state, expr, expected, depth+1, expr_id)
case .Cast:
target := type_from_syntax(checker, expr.type, state.pkg, state.file)
value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
@@ -1531,6 +1531,14 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
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)))
}
if field_default.static_value != INVALID_CT_VALUE {
value := ct_clone_graph(state, &checker.static_state, field_default.static_value)
if value == INVALID_CT_VALUE {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
values[index] = value
continue
}
previous_pkg, previous_file := state.pkg, state.file
previous_comptime := checker.current_comptime_values
state.pkg, state.file = field_default.pkg, field_default.file
@@ -1541,7 +1549,7 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
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)
value, ok = ct_coerce_value(state, value, field.type, field_default.span)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
@@ -2612,7 +2620,277 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
}), ct_flow(.Normal), true
}
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
valid_generated_field_name :: proc(name: string) -> bool {
if len(name) == 0 {
return false
}
is_start := proc(value: byte) -> bool {
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'
}
if !is_start(name[0]) {
return false
}
for value in transmute([]byte)name[1:] {
if !is_start(value) && !(value >= '0' && value <= '9') {
return false
}
}
return true
}
ct_collection_children :: proc(
state: ^Ct_State,
expr_id: ast.Expr_Id,
depth: int,
label: string,
) -> ([]Ct_Value_Id, Ct_Flow, bool) {
value, flow, ok := ct_eval_expr(state, expr_id, types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return nil, flow, ok
}
if value == INVALID_CT_VALUE || int(value) >= len(state.values) {
return nil, ct_flow(.Normal), false
}
item := state.values[value]
if item.kind != .Array && item.kind != .Struct {
return nil, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, state.checker.ast_module.exprs[expr_id].span,
"struct_type! %s must be a fixed array or tuple", label,
)
}
if item.kind == .Struct {
node, node_ok := types.node(&state.checker.module.types, item.type)
if !node_ok || !node.tuple {
return nil, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, state.checker.ast_module.exprs[expr_id].span,
"struct_type! %s must be a fixed array or tuple", label,
)
}
}
children := ct_child_slice(state, item)
result := make([]Ct_Value_Id, len(children), state.checker.allocator)
copy(result, children)
return result, ct_flow(.Normal), true
}
ct_literal_collection :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> ([]ast.Expr_Id, bool) {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return nil, false
}
expr := checker.ast_module.exprs[expr_id]
if expr.kind == .Array || expr.kind == .Struct_Literal && expr.tuple {
return expr.args, true
}
return nil, false
}
ct_struct_type :: proc(
state: ^Ct_State,
expr_id: ast.Expr_Id,
expr: ast.Expr,
depth: int,
) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if len(expr.args) != 4 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span,
"struct_type! expects 4 arguments, got %d", len(expr.args),
)
}
for entry in checker.generated_types {
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(entry.result)}), ct_flow(.Normal), true
}
}
layout_expr := checker.ast_module.exprs[expr.args[0]]
c_layout := false
layout_ok := false
if layout_expr.kind == .Enum_Literal {
name := symbol_text(checker, layout_expr.name)
c_layout = name == "c"
layout_ok = name == "auto" || name == "c"
} else {
layout_value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
if layout_value != INVALID_CT_VALUE && int(layout_value) < len(state.values) {
value := state.values[layout_value]
if value.kind == .Integer && types.is_enum(value.type, &checker.module.types) {
if name, found := enum_member_name_from_value(checker, value.type, value.integer); found {
c_layout = name == "c"
layout_ok = name == "auto" || name == "c"
}
}
}
}
if !layout_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, layout_expr.span,
"struct_type! layout must be .auto or .c",
)
}
name_values, name_flow, names_ok := ct_collection_children(state, expr.args[1], depth+1, "field names")
defer delete(name_values, checker.allocator)
if !names_ok || name_flow.kind != .Normal {
return INVALID_CT_VALUE, name_flow, names_ok
}
names := make([]string, len(name_values), checker.allocator)
defer delete(names, checker.allocator)
for value, index in name_values {
name, ok := ct_value_bytes(state, value)
if !ok || !valid_generated_field_name(name) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, checker.ast_module.exprs[expr.args[1]].span,
"struct_type! field names must be valid comptime immutable byte strings",
)
}
for previous in names[:index] {
if previous == name {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, checker.ast_module.exprs[expr.args[1]].span,
"duplicate struct_type! field name '%s'", name,
)
}
}
names[index] = name
}
field_types: []types.Type
if type_exprs, literal := ct_literal_collection(checker, expr.args[2]); literal {
field_types = make([]types.Type, len(type_exprs), checker.allocator)
for item, index in type_exprs {
field_types[index], _ = resolve_type_argument(checker, item, state.pkg, state.file)
}
} else {
type_values, flow, ok := ct_collection_children(state, expr.args[2], depth+1, "field types")
defer delete(type_values, checker.allocator)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
field_types = make([]types.Type, len(type_values), checker.allocator)
for value, index in type_values {
if value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type {
field_types[index] = types.Type(state.values[value].index)
}
}
}
defer delete(field_types, checker.allocator)
if len(names) != len(field_types) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span,
"struct_type! collection lengths differ: %d names and %d types", len(names), len(field_types),
)
}
if c_layout && len(names) == 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "c struct types require at least one field")
}
fields := make([]types.Field, len(names), checker.allocator)
defer delete(fields, checker.allocator)
for field_type, index in field_types {
valid := types.is_valid(field_type) && !types.is_void(field_type)
if c_layout {
valid = valid && types.is_runtime_value(field_type, &checker.module.types) &&
types.is_c_record_field_type(field_type, &checker.module.types)
} else {
valid = valid && is_runtime_type(checker, field_type)
}
if !valid {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, checker.ast_module.exprs[expr.args[2]].span,
"struct_type! field '%s' has invalid %s type %s",
names[index], "C-layout" if c_layout else "value", type_label(checker, field_type),
)
}
fields[index] = types.Field{name=u32(symbol.intern(checker.symbols, names[index])), type=field_type}
}
default_exprs, literal_defaults := ct_literal_collection(checker, expr.args[3])
default_values: []Ct_Value_Id
if !literal_defaults {
flow: Ct_Flow
ok: bool
default_values, flow, ok = ct_collection_children(state, expr.args[3], depth+1, "field defaults")
if !ok || flow.kind != .Normal {
delete(default_values, checker.allocator)
return INVALID_CT_VALUE, flow, ok
}
}
defer delete(default_values, checker.allocator)
default_count := len(default_exprs) if literal_defaults else len(default_values)
if default_count != len(fields) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span,
"struct_type! collection lengths differ: %d fields and %d defaults", len(fields), default_count,
)
}
persistent_defaults := make([]Ct_Value_Id, len(fields), checker.allocator)
for &value in persistent_defaults {
value = INVALID_CT_VALUE
}
for field, index in fields {
expected := types.optional(&checker.module.types, field.type)
value := INVALID_CT_VALUE
if literal_defaults {
flow: Ct_Flow
ok: bool
value, flow, ok = ct_eval_expr(state, default_exprs[index], expected, depth+1)
if !ok || flow.kind != .Normal {
delete(persistent_defaults, checker.allocator)
return INVALID_CT_VALUE, flow, ok
}
} else {
value = default_values[index]
}
if value == INVALID_CT_VALUE {
delete(persistent_defaults, checker.allocator)
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, expr.span,
"struct_type! field defaults contain an invalid comptime value",
)
}
coerced, ok := ct_coerce_value(state, value, expected, expr.span)
value = coerced
key := strings.builder_make(checker.allocator)
undefined := ct_value_contains_undefined(state, value)
key_ok := ok && ct_write_comptime_key(state, value, &key)
stable := ok && !undefined && key_ok
strings.builder_destroy(&key)
if !stable {
delete(persistent_defaults, checker.allocator)
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span,
"struct_type! default for field '%s' must have a stable comptime identity coercible to ?%s",
names[index], type_label(checker, field.type),
)
}
default := state.values[value]
if default.kind == .None {
continue
}
children := ct_child_slice(state, default)
if default.kind != .Optional_Some || len(children) != 1 {
delete(persistent_defaults, checker.allocator)
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
persistent_defaults[index] = ct_clone_graph(&checker.static_state, state, children[0])
}
result := types.struct_generated(&checker.module.types, fields, c_layout=c_layout)
append(&checker.generated_types, Generated_Type_Entry{
expr=expr_id,
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
result=result,
pkg=state.pkg,
file=state.file,
defaults=persistent_defaults,
})
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(result)}), ct_flow(.Normal), true
}
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int, expr_id := ast.INVALID_EXPR) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if expr.left != ast.INVALID_EXPR {
callee, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
@@ -2636,6 +2914,29 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, message)
}
if is_intrinsic_call(checker, expr, "some") {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "some! expects 1 argument, got %d", len(expr.args))
}
if !types.is_optional(expected, &checker.module.types) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "some! requires an optional context")
}
child_type := types.child_type(expected, &checker.module.types)
child, flow, ok := ct_eval_expr(state, expr.args[0], child_type, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
child, ok = ct_coerce_value(state, child, child_type, checker.ast_module.exprs[expr.args[0]].span)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
start := u32(len(state.children))
append(&state.children, child)
return ct_add_value(state, Ct_Value{kind=.Optional_Some, type=expected, start=start, count=1}), ct_flow(.Normal), true
}
if is_intrinsic_call(checker, expr, "struct_type") {
return ct_struct_type(state, expr_id, expr, depth+1)
}
if is_intrinsic_call(checker, expr, "field") {
if len(expr.args) != 2 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "field! expects 2 arguments, got %d", len(expr.args))
+37 -2
View File
@@ -434,6 +434,18 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
u32(parser.file),
!symbol.is_valid(qualifier) && file_hidden_name(parser, name.symbol),
)
if current(parser).kind == .Bang && peek(parser).kind == .Left_Paren {
advance(parser)
if symbol.is_valid(qualifier) {
source.add(parser.diagnostics, first.span, "intrinsic calls must be unqualified")
return types.INVALID
}
call := parse_call(parser, qualifier, first, name, 0, true)
return types.intern(&parser.module.type_store, types.Node{
kind=.Type_Call,
count_expr=u32(call),
})
}
if current(parser).kind == .Left_Paren {
call := parse_call(parser, qualifier, first, name, 0, false)
return types.intern(&parser.module.type_store, types.Node{
@@ -620,6 +632,7 @@ parse_keyed_initializers :: proc(
left_brace: token.Token,
nesting: int,
close_message: string,
require_values := false,
) -> ([]ast.Expr_Id, token.Token) {
parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1
@@ -640,6 +653,8 @@ parse_keyed_initializers :: proc(
skip_newlines(parser)
value = parse_expression_bp(parser, 0, nesting+1)
key_end = parser.module.exprs[value].span
} else if require_values {
source.add(parser.diagnostics, field.span, "anonymous record fields require '= value'")
}
append(&args, add_expr(parser, ast.Expr{
kind=.Keyed,
@@ -696,6 +711,15 @@ brace_starts_tuple :: proc(parser: ^Parser) -> bool {
if current(parser).kind != .Left_Brace {
return false
}
first := parser.cursor+1
for first < len(parser.tokens.items) && parser.tokens.items[first].kind == .Newline {
first += 1
}
if first+1 < len(parser.tokens.items) &&
(parser.tokens.items[first].kind == .Identifier || token.is_keyword(parser.tokens.items[first].kind)) &&
parser.tokens.items[first+1].kind == .Equal {
return true
}
depth := 0
for cursor := parser.cursor; cursor < len(parser.tokens.items); cursor += 1 {
#partial switch parser.tokens.items[cursor].kind {
@@ -720,12 +744,23 @@ brace_starts_tuple :: proc(parser: ^Parser) -> bool {
parse_tuple_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
left_brace := advance(parser)
args, right_brace := parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
skip_newlines(parser)
keyed := (current(parser).kind == .Identifier || token.is_keyword(current(parser).kind)) &&
peek(parser).kind == .Equal
args: []ast.Expr_Id
right_brace: token.Token
if keyed {
args, right_brace = parse_keyed_initializers(
parser, left_brace, nesting, "expected '}' after anonymous record literal", true,
)
} else {
args, right_brace = parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
}
return add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=span_from(left_brace.span, right_brace.span),
args=args,
tuple=true,
tuple=!keyed,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
+2 -1
View File
@@ -369,7 +369,7 @@ struct_anonymous :: proc(store: ^Store, fields: []Field, tuple := false) -> Type
// Generated structs are nominal per comptime type-expression specialization.
// The checker owns canonicalization; this routine deliberately creates a fresh node.
struct_generated :: proc(store: ^Store, fields: []Field, tuple := false) -> Type {
struct_generated :: proc(store: ^Store, fields: []Field, tuple := false, c_layout := false) -> Type {
start := u32(len(store.fields))
append(&store.fields, ..fields)
id := DYNAMIC_START+Type(len(store.nodes))
@@ -378,6 +378,7 @@ struct_generated :: proc(store: ^Store, fields: []Field, tuple := false) -> Type
field_start=start,
field_count=u32(len(fields)),
tuple=tuple,
c_layout=c_layout,
declared=true,
})
return id
+324
View File
@@ -13723,3 +13723,327 @@ main func() void {
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
anonymous_keyed_records_infer_structural_types_and_preserve_tuples :: proc(t: ^testing.T) {
text := `first :: {x = 1, name = "bro"}
second :: {x = 2, name = "hey"}
empty :: {}
pair :: {1, 2}
main func() i32 {
if first.x + second.x != 3 { return 1 }
if first.name.len != 3 or second.name.len != 3 { return 2 }
return pair.0 - 1
}
`
source_file := source.Source{path="anonymous_records.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
first_expr := ast_module.exprs[ast_module.globals[0].expr]
empty_expr := ast_module.exprs[ast_module.globals[2].expr]
pair_expr := ast_module.exprs[ast_module.globals[3].expr]
testing.expect_value(t, first_expr.kind, ast.Expr_Kind.Struct_Literal)
testing.expect(t, !first_expr.tuple)
testing.expect(t, empty_expr.tuple)
testing.expect(t, pair_expr.tuple)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, types.equal(hir_module.globals[0].type, hir_module.globals[1].type))
testing.expect(t, types.is_record(hir_module.globals[0].type, &hir_module.types))
empty_type, empty_ok := types.node(&hir_module.types, hir_module.globals[2].type)
pair_type, pair_ok := types.node(&hir_module.types, hir_module.globals[3].type)
testing.expect(t, empty_ok && empty_type.tuple)
testing.expect(t, pair_ok && pair_type.tuple)
bare_source := source.Source{path="bare_anonymous_record.bro", text="bad :: {value = 1, missing}\n"}
bare_diagnostics := source.init_diagnostics(&bare_source)
defer source.destroy_diagnostics(&bare_diagnostics)
bare_symbols := symbol.init_table()
defer symbol.destroy_table(&bare_symbols)
bare_stream := lexer.lex(&bare_source, &bare_diagnostics, &bare_symbols)
defer delete(bare_stream.items)
bare_module := parser.parse(&bare_stream, &bare_source, &bare_diagnostics)
defer ast.destroy_module(&bare_module)
found_bare := false
for diagnostic in bare_diagnostics.items {
found_bare = found_bare || strings.contains(diagnostic.message, "anonymous record fields require '= value'")
}
testing.expect(t, found_bare)
}
@(test)
enum_field_struct_and_contextual_anonymous_records_compile_and_run :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-enum-field-struct"
main_path := "/tmp/brolang-test-enum-field-struct/main.bro"
output := "/tmp/brolang-test-enum-field-struct-output"
text := `meta :: import "@std/meta"
TokenKind :: enum(u8) {
ident = 3
int = 8
eof = 21
}
Simple :: enum { first, second }
Names :: alias meta.EnumFieldStruct(TokenKind, ?[]u8, some!(none))
Flags :: alias meta.EnumFieldStruct(Simple, bool, false)
Direct :: alias struct_type!(.auto, {"x", "name"}, {i32, []u8}, {none, "bro"})
CPoint :: alias struct_type!(.c, {"x"}, {c_int}, {none})
ArrayInput :: alias struct_type!(.auto, ["value"], [i32], [7])
Empty :: alias struct_type!(.auto, {}, {}, {})
direct_position struct_type!(.auto, {"value"}, {i32}, {none}) = {value = 5}
make_direct func() struct_type!(.auto, {"value"}, {i32}, {none}) {
return {value = 6}
}
Generated func($T type) type {
names [1]mut []u8 = undefined
field_types [1]mut type = undefined
defaults [1]mut ?T = undefined
names[0] = "value"
field_types[0] = T
defaults[0] = 42
return struct_type!(.auto, names, field_types, defaults)
}
GeneratedInt :: alias Generated(i32)
ordered Names = {}
Map func($E, $V type) type { return struct { count usize } }
init func($E, $V type, values meta.EnumFieldStruct(E, ?V, some!(none))) Map(E, V) {
count usize = 0
match typeinfo!(E) {
.enum |info|: expand for info.fields |field| {
if field!(values, field.name) |_| { count += 1 }
}
else: compile_error!("EnumMap key must be an enum")
}
return Map(E, V) {count = count}
}
main func() i32 {
_ = ordered
inferred :: {x = 40, name = "bro"}
if inferred.x != 40 or inferred.name.len != 3 { return 1 }
direct Direct = {x = 7}
if direct.x != 7 or direct.name.len != 3 { return 10 }
generated GeneratedInt = {}
if generated.value != 42 { return 11 }
generated_again Generated(i32) = generated
if generated_again.value != 42 { return 15 }
array_input ArrayInput = {}
if array_input.value != 7 { return 16 }
empty_record Empty = {}
_ = empty_record
if direct_position.value != 5 or make_direct().value != 6 { return 17 }
c_point CPoint = {x = 9}
if c_point.x != 9 { return 12 }
nested ??i32 = some!(none)
if nested |inner| {
if inner |_| { return 13 }
} else { return 14 }
names Names = {
ident = "identifier",
int = "integer",
}
if field!(names, "ident") |value| {
if value.len != 10 { return 2 }
} else { return 3 }
if field!(names, "int") |value| {
if value.len != 7 { return 4 }
} else { return 5 }
if field!(names, "eof") |_| { return 6 }
empty Names = {}
if field!(empty, "ident") |_| { return 7 }
flags Flags = {second = true}
if flags.first or !flags.second { return 8 }
map Map(TokenKind, []u8) = init({
ident = "identifier",
int = "integer",
})
if map.count != 2 { return 9 }
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
module, loaded := loader.load(
directory, &sources, &diagnostics, &symbols, project_root_path=".",
)
defer ast.destroy_module(&module)
hir_module := checker.check(&module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
names_type := types.INVALID
ordered_symbol := symbol.intern(&symbols, "ordered")
for global in hir_module.globals {
if global.name == ordered_symbol {
names_type = types.resolve_alias(global.type, &hir_module.types)
break
}
}
names_fields := types.fields_for(&hir_module.types, names_type)
testing.expect_value(t, len(names_fields), 3)
if len(names_fields) == 3 {
testing.expect_value(t, names_fields[0].name, u32(symbol.intern(&symbols, "ident")))
testing.expect_value(t, names_fields[1].name, u32(symbol.intern(&symbols, "int")))
testing.expect_value(t, names_fields[2].name, u32(symbol.intern(&symbols, "eof")))
}
testing.expect_value(t, compiler_core.compile_package(
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
std_meta_tests_compile_and_run :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-std-meta"
main_path := "/tmp/brolang-test-std-meta/main.bro"
output := "/tmp/brolang-test-std-meta-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
text := `test import "@std/meta"
`
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(
directory, output, nil, target.DEFAULT, cimport.Options{}, ".", .Test,
), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
anonymous_records_and_struct_type_report_targeted_errors :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-bad-struct-type"
main_path := "/tmp/brolang-test-bad-struct-type/main.bro"
text := `meta :: import "@std/meta"
E :: enum { value }
Fields :: alias struct_type!(.auto, {"value"}, {i32}, {none})
RequiredEnum :: alias meta.EnumFieldStruct(E, i32, none)
NotEnum :: alias meta.EnumFieldStruct(i32, i32, none)
BadField :: alias struct_type!(.auto, {"value"}, {void}, {none})
BadComptimeField :: alias struct_type!(.auto, {"T"}, {type}, {none})
BadDefault :: alias struct_type!(.auto, {"value"}, {i32}, {"no"})
UnstableDefault :: alias struct_type!(.auto, {"value"}, {i32}, {undefined})
BadLengths :: alias struct_type!(.auto, {"one", "two"}, {i32}, {none})
BadNames :: alias struct_type!(.auto, {"not-valid"}, {i32}, {none})
DuplicateNames :: alias struct_type!(.auto, {"same", "same"}, {i32, i32}, {none, none})
BadLayout :: alias struct_type!(.packed, {"value"}, {i32}, {none})
BadC :: alias struct_type!(.c, {"value"}, {[]u8}, {none})
EmptyC :: alias struct_type!(.c, {}, {}, {})
unknown Fields = {missing = 1}
duplicate Fields = {value = 1, value = 2}
missing Fields = {}
missing_enum RequiredEnum = {}
not_enum NotEnum = {}
bad_field BadField = {}
bad_comptime_field BadComptimeField = {}
bad_default BadDefault = {}
unstable_default UnstableDefault = {}
bad_lengths BadLengths = {}
bad_names BadNames = {}
duplicate_names DuplicateNames = {}
bad_layout BadLayout = {}
bad_c BadC = {}
empty_c EmptyC = {}
main func() void {
_ = some!(1)
_ = unknown
_ = duplicate
_ = missing
_ = missing_enum
_ = not_enum
_ = bad_field
_ = bad_comptime_field
_ = bad_default
_ = unstable_default
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
ast_module, loaded := loader.load(directory, &sources, &diagnostics, &symbols, project_root_path=".")
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect(t, loaded)
found_non_enum := false
found_bad_field := false
found_bad_comptime_field := false
found_bad_default := false
found_unstable := false
found_unknown := false
found_duplicate := false
found_lengths := false
found_names := false
found_layout := false
found_c := false
found_some_context := false
found_missing := false
found_empty_c := false
for diagnostic in diagnostics.items {
found_non_enum = found_non_enum || strings.contains(diagnostic.message, "EnumFieldStruct key must be an enum")
found_bad_field = found_bad_field || strings.contains(diagnostic.message, "field 'value' has invalid value type")
found_bad_comptime_field = found_bad_comptime_field || strings.contains(diagnostic.message, "field 'T' has invalid value type")
found_bad_default = found_bad_default || strings.contains(diagnostic.message, "cannot implicitly convert")
found_unstable = found_unstable || strings.contains(diagnostic.message, "must have a stable comptime identity")
found_unknown = found_unknown || strings.contains(diagnostic.message, "unknown struct field 'missing'")
found_duplicate = found_duplicate || strings.contains(diagnostic.message, "duplicate initializer for struct field 'value'")
found_lengths = found_lengths || strings.contains(diagnostic.message, "collection lengths differ")
found_names = found_names || strings.contains(diagnostic.message, "field names must be valid") || strings.contains(diagnostic.message, "duplicate struct_type! field name")
found_layout = found_layout || strings.contains(diagnostic.message, "layout must be .auto or .c")
found_c = found_c || strings.contains(diagnostic.message, "invalid C-layout type")
found_some_context = found_some_context || strings.contains(diagnostic.message, "some! requires an optional context")
found_missing = found_missing || strings.contains(diagnostic.message, "missing initializer for struct field 'value'")
found_empty_c = found_empty_c || strings.contains(diagnostic.message, "c struct types require at least one field")
}
testing.expect(t, found_non_enum)
testing.expect(t, found_bad_field)
testing.expect(t, found_bad_comptime_field)
testing.expect(t, found_bad_default)
testing.expect(t, found_unstable)
testing.expect(t, found_unknown)
testing.expect(t, found_duplicate)
testing.expect(t, found_lengths)
testing.expect(t, found_names)
testing.expect(t, found_layout)
testing.expect(t, found_c)
testing.expect(t, found_some_context)
testing.expect(t, found_missing)
testing.expect(t, found_empty_c)
}
+17
View File
@@ -39,3 +39,20 @@ TypeInfo :: union(enum) {
fallible void
distinct void
}
EnumFieldStruct func($E, $Field type, $default ?Field) type {
match typeinfo!(E) {
.enum |info|: {
names [field!(typeinfo!(E), "enum").fields.len]mut []u8 = undefined
field_types [field!(typeinfo!(E), "enum").fields.len]mut type = undefined
defaults [field!(typeinfo!(E), "enum").fields.len]mut ?Field = undefined
expand for info.fields |field, index| {
names[index] = field.name
field_types[index] = Field
defaults[index] = default
}
return struct_type!(.auto, names, field_types, defaults)
}
else: compile_error!("EnumFieldStruct key must be an enum")
}
}
+34
View File
@@ -0,0 +1,34 @@
testing :: import "@std/testing"
TestTokenKind :: enum(u8) {
ident = 3
int = 8
eof = 21
}
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(none))
enum_field_struct_defaults test {
names TestNames = {
ident = "identifier",
int = "integer",
}
if field!(names, "ident") |value| {
try testing.expect(value.len == 10)
} else {
try testing.expect(false)
}
if field!(names, "int") |value| {
try testing.expect(value.len == 7)
} else {
try testing.expect(false)
}
if field!(names, "eof") |_| {
try testing.expect(false)
}
empty TestNames = {}
if field!(empty, "ident") |_| {
try testing.expect(false)
}
}
+40 -10
View File
@@ -33,9 +33,8 @@ module.exports = grammar({
[$.array_type, $.expression],
[$.array_type, $.array_literal],
[$.expression_statement, $.parenthesized_expression],
[$.block, $.tuple_literal],
[$.block, $.tuple_literal, $.anonymous_record_literal],
[$.field_initializer, $.expression],
[$.tuple_literal],
[$.capture_list, $.expression],
[$.match_capture, $.expression],
],
@@ -188,6 +187,7 @@ module.exports = grammar({
_type_atom: $ => choice(
$.builtin_type,
$.intrinsic_type,
$.named_type,
$.optional_type,
$.pointer_type,
@@ -198,9 +198,11 @@ module.exports = grammar({
parenthesized_type: $ => seq('(', repeat($._newline), $.type, repeat($._newline), ')'),
named_type: $ => prec.right(seq(
$.qualified_identifier,
optional($.argument_list),
named_type: $ => prec.right(seq($.qualified_identifier, optional($.argument_list))),
intrinsic_type: $ => prec(PREC.POSTFIX, seq(
field('function', alias('struct_type!', $.identifier)),
field('arguments', $.argument_list),
)),
optional_type: $ => seq('?', $.type),
@@ -441,6 +443,7 @@ module.exports = grammar({
$.slice_expression,
$.postfix_expression,
$.struct_literal,
$.anonymous_record_literal,
$.tuple_literal,
$.comptime_block,
$.function_literal,
@@ -556,17 +559,27 @@ module.exports = grammar({
),
field_initializer: $ => seq(
field('name', $.identifier),
field('name', $._field_name),
optional(seq('=', repeat($._newline), field('value', $.expression))),
),
anonymous_record_literal: $ => prec(2, seq(
'{',
repeat($._newline),
$.keyed_field_initializer,
repeat(seq(repeat($._newline), ',', repeat($._newline), $.keyed_field_initializer)),
optional(seq(repeat($._newline), ',')),
repeat($._newline),
'}',
)),
tuple_literal: $ => prec(1, choice(
seq('{', repeat($._newline), '}'),
seq(
'{', repeat($._newline), $.expression,
',', repeat($._newline),
repeat(seq($.expression, ',', repeat($._newline))),
optional($.expression), repeat($._newline), '}',
repeat(seq(repeat($._newline), ',', repeat($._newline), $.expression)),
optional(seq(repeat($._newline), ',')),
repeat($._newline), '}',
),
)),
@@ -597,7 +610,7 @@ module.exports = grammar({
),
keyed_field_initializer: $ => seq(
field('name', $.identifier),
field('name', $._field_name),
'=',
repeat($._newline),
field('value', $.expression),
@@ -635,6 +648,23 @@ module.exports = grammar({
undefined: _ => 'undefined',
sink: _ => '_',
_field_name: $ => prec(2, choice(
$.identifier,
alias(choice(
'test', 'func', 'c_func', 'struct', 'c_struct', 'opaque', 'union', 'enum',
'distinct', 'alias', 'import', 'hide', 'return', 'try', 'catch', 'mut',
'none', 'undefined', 'orelse', 'and', 'or', 'xor', 'if', 'while', 'for',
'expand', 'break', 'continue', 'defer', 'errdefer', 'yield', 'match', 'else',
'true', 'false',
'void', 'type', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
'isize', 'usize', 'f32', 'f64',
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
'c_int', 'c_uint', 'c_long', 'c_ulong', 'c_longlong',
'c_ulonglong', 'c_float', 'c_double', 'c_longdouble',
), $.identifier),
)),
identifier: _ => /[A-Za-z_][A-Za-z0-9_]*/,
integer: _ => /[0-9]+/,
float: _ => token(prec(1, /[0-9]+\.[0-9]+/)),
@@ -39,6 +39,7 @@
(parameter name: (identifier) @variable.parameter)
(intrinsic_call_expression function: (identifier) @function.builtin)
(intrinsic_type function: (identifier) @function.builtin)
(call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property)
+451 -19
View File
@@ -1149,6 +1149,10 @@
"type": "SYMBOL",
"name": "builtin_type"
},
{
"type": "SYMBOL",
"name": "intrinsic_type"
},
{
"type": "SYMBOL",
"name": "named_type"
@@ -1231,6 +1235,36 @@
]
}
},
"intrinsic_type": {
"type": "PREC",
"value": 11,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "function",
"content": {
"type": "ALIAS",
"content": {
"type": "STRING",
"value": "struct_type!"
},
"named": true,
"value": "identifier"
}
},
{
"type": "FIELD",
"name": "arguments",
"content": {
"type": "SYMBOL",
"name": "argument_list"
}
}
]
}
},
"optional_type": {
"type": "SEQ",
"members": [
@@ -3105,6 +3139,10 @@
"type": "SYMBOL",
"name": "struct_literal"
},
{
"type": "SYMBOL",
"name": "anonymous_record_literal"
},
{
"type": "SYMBOL",
"name": "tuple_literal"
@@ -4268,7 +4306,7 @@
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
"name": "_field_name"
}
},
{
@@ -4305,6 +4343,95 @@
}
]
},
"anonymous_record_literal": {
"type": "PREC",
"value": 2,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "SYMBOL",
"name": "keyed_field_initializer"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "STRING",
"value": ","
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "SYMBOL",
"name": "keyed_field_initializer"
}
]
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "STRING",
"value": ","
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "STRING",
"value": "}"
}
]
}
},
"tuple_literal": {
"type": "PREC",
"value": 1,
@@ -4349,25 +4476,17 @@
"type": "SYMBOL",
"name": "expression"
},
{
"type": "STRING",
"value": ","
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "expression"
"name": "_newline"
}
},
{
"type": "STRING",
@@ -4379,6 +4498,10 @@
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "SYMBOL",
"name": "expression"
}
]
}
@@ -4387,8 +4510,20 @@
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "expression"
"name": "_newline"
}
},
{
"type": "STRING",
"value": ","
}
]
},
{
"type": "BLANK"
@@ -4564,7 +4699,7 @@
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
"name": "_field_name"
}
},
{
@@ -4876,6 +5011,305 @@
"type": "STRING",
"value": "_"
},
"_field_name": {
"type": "PREC",
"value": 2,
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "ALIAS",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "test"
},
{
"type": "STRING",
"value": "func"
},
{
"type": "STRING",
"value": "c_func"
},
{
"type": "STRING",
"value": "struct"
},
{
"type": "STRING",
"value": "c_struct"
},
{
"type": "STRING",
"value": "opaque"
},
{
"type": "STRING",
"value": "union"
},
{
"type": "STRING",
"value": "enum"
},
{
"type": "STRING",
"value": "distinct"
},
{
"type": "STRING",
"value": "alias"
},
{
"type": "STRING",
"value": "import"
},
{
"type": "STRING",
"value": "hide"
},
{
"type": "STRING",
"value": "return"
},
{
"type": "STRING",
"value": "try"
},
{
"type": "STRING",
"value": "catch"
},
{
"type": "STRING",
"value": "mut"
},
{
"type": "STRING",
"value": "none"
},
{
"type": "STRING",
"value": "undefined"
},
{
"type": "STRING",
"value": "orelse"
},
{
"type": "STRING",
"value": "and"
},
{
"type": "STRING",
"value": "or"
},
{
"type": "STRING",
"value": "xor"
},
{
"type": "STRING",
"value": "if"
},
{
"type": "STRING",
"value": "while"
},
{
"type": "STRING",
"value": "for"
},
{
"type": "STRING",
"value": "expand"
},
{
"type": "STRING",
"value": "break"
},
{
"type": "STRING",
"value": "continue"
},
{
"type": "STRING",
"value": "defer"
},
{
"type": "STRING",
"value": "errdefer"
},
{
"type": "STRING",
"value": "yield"
},
{
"type": "STRING",
"value": "match"
},
{
"type": "STRING",
"value": "else"
},
{
"type": "STRING",
"value": "true"
},
{
"type": "STRING",
"value": "false"
},
{
"type": "STRING",
"value": "void"
},
{
"type": "STRING",
"value": "type"
},
{
"type": "STRING",
"value": "anyopaque"
},
{
"type": "STRING",
"value": "bool"
},
{
"type": "STRING",
"value": "int"
},
{
"type": "STRING",
"value": "uint"
},
{
"type": "STRING",
"value": "float"
},
{
"type": "STRING",
"value": "range"
},
{
"type": "STRING",
"value": "i8"
},
{
"type": "STRING",
"value": "i16"
},
{
"type": "STRING",
"value": "i32"
},
{
"type": "STRING",
"value": "i64"
},
{
"type": "STRING",
"value": "u8"
},
{
"type": "STRING",
"value": "u16"
},
{
"type": "STRING",
"value": "u32"
},
{
"type": "STRING",
"value": "u64"
},
{
"type": "STRING",
"value": "isize"
},
{
"type": "STRING",
"value": "usize"
},
{
"type": "STRING",
"value": "f32"
},
{
"type": "STRING",
"value": "f64"
},
{
"type": "STRING",
"value": "c_char"
},
{
"type": "STRING",
"value": "c_schar"
},
{
"type": "STRING",
"value": "c_uchar"
},
{
"type": "STRING",
"value": "c_short"
},
{
"type": "STRING",
"value": "c_ushort"
},
{
"type": "STRING",
"value": "c_int"
},
{
"type": "STRING",
"value": "c_uint"
},
{
"type": "STRING",
"value": "c_long"
},
{
"type": "STRING",
"value": "c_ulong"
},
{
"type": "STRING",
"value": "c_longlong"
},
{
"type": "STRING",
"value": "c_ulonglong"
},
{
"type": "STRING",
"value": "c_float"
},
{
"type": "STRING",
"value": "c_double"
},
{
"type": "STRING",
"value": "c_longdouble"
}
]
},
"named": true,
"value": "identifier"
}
]
}
},
"identifier": {
"type": "PATTERN",
"value": "[A-Za-z_][A-Za-z0-9_]*"
@@ -5029,15 +5463,13 @@
],
[
"block",
"tuple_literal"
"tuple_literal",
"anonymous_record_literal"
],
[
"field_initializer",
"expression"
],
[
"tuple_literal"
],
[
"capture_list",
"expression"
+68 -4
View File
@@ -15,6 +15,21 @@
}
}
},
{
"type": "anonymous_record_literal",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "keyed_field_initializer",
"named": true
}
]
}
},
{
"type": "argument_list",
"named": true,
@@ -748,6 +763,10 @@
"multiple": false,
"required": true,
"types": [
{
"type": "anonymous_record_literal",
"named": true
},
{
"type": "array_literal",
"named": true
@@ -1443,6 +1462,32 @@
}
}
},
{
"type": "intrinsic_type",
"named": true,
"fields": {
"arguments": {
"multiple": false,
"required": true,
"types": [
{
"type": "argument_list",
"named": true
}
]
},
"function": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
}
},
{
"type": "keyed_field_initializer",
"named": true,
@@ -1608,6 +1653,16 @@
]
}
},
{
"type": "none",
"named": true,
"fields": {}
},
{
"type": "opaque_type",
"named": true,
"fields": {}
},
{
"type": "optional_type",
"named": true,
@@ -2158,6 +2213,10 @@
"type": "function_type",
"named": true
},
{
"type": "intrinsic_type",
"named": true
},
{
"type": "named_type",
"named": true
@@ -2273,6 +2332,11 @@
}
}
},
{
"type": "undefined",
"named": true,
"fields": {}
},
{
"type": "union_type",
"named": true,
@@ -2862,11 +2926,11 @@
},
{
"type": "none",
"named": true
"named": false
},
{
"type": "opaque_type",
"named": true
"type": "opaque",
"named": false
},
{
"type": "or",
@@ -2934,7 +2998,7 @@
},
{
"type": "undefined",
"named": true
"named": false
},
{
"type": "union",
+329846 -253739
View File
File diff suppressed because it is too large Load Diff
@@ -138,6 +138,47 @@ sum func(a, b i32) i32 ! Status {
(enum_literal
(identifier))))))))))))
==================
Anonymous keyed records
==================
inferred :: {
x = 1,
int = "bro",
}
empty :: {}
pair :: {1, 2}
---
(source_file
(global_constant_declaration
(identifier)
(expression
(anonymous_record_literal
(keyed_field_initializer
(identifier)
(expression
(integer)))
(keyed_field_initializer
(identifier)
(expression
(string
(string_content)))))))
(global_constant_declaration
(identifier)
(expression
(tuple_literal)))
(global_constant_declaration
(identifier)
(expression
(tuple_literal
(expression
(integer))
(expression
(integer))))))
==================
Intrinsic calls
==================
@@ -166,6 +207,39 @@ main func() void {
(expression
(builtin_type))))))))))
==================
Intrinsic type construction
==================
Generated :: alias struct_type!(.auto, {"value"}, {i32}, {none})
---
(source_file
(type_declaration
(identifier)
(alias_type
(type
(intrinsic_type
(identifier)
(argument_list
(expression
(enum_literal
(identifier)))
(expression
(tuple_literal
(expression
(string
(string_content)))))
(expression
(tuple_literal
(expression
(builtin_type))))
(expression
(tuple_literal
(expression
(none))))))))))
==================
Errdefer
==================
@@ -19,3 +19,6 @@ clear func($K, $V type) void {
# ^^^ keyword
# ^ operator
}
Generated :: alias struct_type!(.auto, {"value"}, {i32}, {none})
# ^^^^^^^^^^^^ function.builtin