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 underlying IEEE operations. Ordinary float `/` remains unchecked and therefore preserves IEEE
infinity/NaN behavior. infinity/NaN behavior.
Only the six bang calls are intrinsic. Bare calls such as `divfloor(a, b)` and qualified calls such Only these six division bang calls select integer-division behavior. Bare calls such as
as `math.divfloor(a, b)` resolve to ordinary functions. `divfloor(a, b)` and qualified calls such as `math.divfloor(a, b)` resolve to ordinary functions.
### functions, C interop, and linking ### functions, C interop, and linking
- demand-monomorphized Brolang and C-ABI functions - 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 - 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 - 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 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 - 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 }` - 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 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 - 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` - 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 - `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 - `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 - 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` - 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/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/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 - `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` - 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` - `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
+205 -22
View File
@@ -166,6 +166,15 @@ Generated_Type_Entry :: struct {
result: types.Type, result: types.Type,
pkg: ast.Package_Id, pkg: ast.Package_Id,
file: ast.File_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 { 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) { if depth > 256 || !types.is_valid(value) {
return false return false
} }
if is_type_metatype_syntax(checker, value) {
return true
}
if is_runtime_type(checker, value) { if is_runtime_type(checker, value) {
return true return true
} }
@@ -1930,12 +1942,15 @@ call_mapping_mode :: proc(function: ast.Function, mapping: []int) -> Call_Argume
return .Explicit 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 ordinal := 0
for param in function.params { for param in function.params {
if !param.comptime_value { if !param.comptime_value {
continue continue
} }
if ordinal >= prefix {
return -1, false
}
if param.name == name { if param.name == name {
return ordinal, true return ordinal, true
} }
@@ -2299,6 +2314,7 @@ type_pattern_mentions_comptime :: proc(
prefix: int, prefix: int,
pattern: types.Type, pattern: types.Type,
depth := 0, depth := 0,
type_params_only := false,
) -> bool { ) -> bool {
if depth > 64 { if depth > 64 {
return false return false
@@ -2309,7 +2325,13 @@ type_pattern_mentions_comptime :: proc(
} }
if item.qualifier == 0 && item.name != 0 { if item.qualifier == 0 && item.name != 0 {
if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found { if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found {
return true 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 { if item.kind == .Array && item.unresolved_count {
@@ -2318,7 +2340,13 @@ type_pattern_mentions_comptime :: proc(
expr := checker.ast_module.exprs[expr_id] expr := checker.ast_module.exprs[expr_id]
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) { if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
if _, found := comptime_binding_index(function, prefix, expr.name); found { if _, found := comptime_binding_index(function, prefix, expr.name); found {
return true 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] arg := checker.ast_module.exprs[arg_id]
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) { if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
if _, found := comptime_binding_index(function, prefix, arg.name); found { if _, found := comptime_binding_index(function, prefix, arg.name); found {
return true 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 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 return true
} }
if item.kind == .Function { if item.kind == .Function {
for field in types.params_for(&checker.module.types, pattern) { 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 return true
} }
} }
@@ -2690,6 +2724,10 @@ infer_call_comptime_values :: proc(
) && matched ) && 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 // Concrete arguments bind first. Numeric constants are contextual and therefore
// only contribute their default type after stronger evidence has had a chance. // only contribute their default type after stronger evidence has had a chance.
weak_passes := [2]bool{false, true} 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) { if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
continue 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 weak {
if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok && if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok &&
item.qualifier == 0 && item.name != 0 { 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") source.add(checker.diagnostics, expr.span, "type position requires a direct type-factory call")
return types.INVALID 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) target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available { if !available {
_ = add_package_resolution_diagnostic(checker, expr, file) _ = 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, resolving=true,
}) })
state := ct_state_make(checker, pkg, file) 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 result := types.INVALID
if eval_ok && flow.kind == .Normal && value != INVALID_CT_VALUE && int(value) < len(state.values) && state.values[value].kind == .Type { 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) result = types.Type(state.values[value].index)
@@ -3502,12 +3561,16 @@ validate_declarations :: proc(checker: ^Checker) {
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
locals: [dynamic]symbol.Id locals: [dynamic]symbol.Id
locals.allocator = checker.allocator locals.allocator = checker.allocator
comptime_prefix := 0
for param in function.params { for param in function.params {
param_type := types.INVALID param_type := types.INVALID
if param.comptime_value || !has_comptime { if param.comptime_value || !has_comptime {
param_type = type_from_syntax(checker, param.type, function.pkg, function.file) param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
} }
if param.comptime_value && !signature_poisoned { 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 { if function.c_abi {
checker.template_diagnostics[function_id] = source.add( checker.template_diagnostics[function_id] = source.add(
checker.diagnostics, checker.diagnostics,
@@ -3516,7 +3579,7 @@ validate_declarations :: proc(checker: ^Checker) {
) )
} }
if !is_type_metatype_syntax(checker, param.type) && 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.template_diagnostics[function_id] = source.addf(
checker.diagnostics, checker.diagnostics,
param.span, param.span,
@@ -3538,6 +3601,9 @@ validate_declarations :: proc(checker: ^Checker) {
continue continue
} }
} }
if param.comptime_value {
comptime_prefix += 1
}
if param.type == types.VOID { if param.type == types.VOID {
source.add( source.add(
checker.diagnostics, checker.diagnostics,
@@ -4594,6 +4660,26 @@ infer_compound_expr :: proc(
} }
return value 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) fields := types.fields_for(store, value)
item, item_ok := types.node(store, value) item, item_ok := types.node(store, value)
initialized := make([]bool, len(fields), checker.allocator) initialized := make([]bool, len(fields), checker.allocator)
@@ -4627,6 +4713,9 @@ infer_compound_expr :: proc(
continue continue
} }
if field_default, default_values, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok { 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 previous := checker.current_comptime_values
checker.current_comptime_values = default_values checker.current_comptime_values = default_values
_ = infer_nested_expr( _ = infer_nested_expr(
@@ -4893,6 +4982,17 @@ infer_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
continue 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 field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file); if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
ok && types.is_enum(enum_type, &checker.module.types) { ok && types.is_enum(enum_type, &checker.module.types) {
@@ -6925,12 +7025,18 @@ find_struct_field_default :: proc(
checker: ^Checker, checker: ^Checker,
struct_type: types.Type, struct_type: types.Type,
name: symbol.Id, 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) resolved := types.resolve_alias(struct_type, &checker.module.types)
for field_default in checker.ast_module.struct_field_defaults { for field_default in checker.ast_module.struct_field_defaults {
if types.resolve_alias(field_default.record, &checker.module.types) == resolved && if types.resolve_alias(field_default.record, &checker.module.types) == resolved &&
field_default.field == name { 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 { for entry in checker.generated_types {
@@ -6941,16 +7047,30 @@ find_struct_field_default :: proc(
expr := checker.ast_module.exprs[entry.expr] expr := checker.ast_module.exprs[entry.expr]
fields := types.fields_for(&checker.module.types, resolved) fields := types.fields_for(&checker.module.types, resolved)
for field, index in fields { 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 continue
} }
return ast.Struct_Field_Default{ if index < len(entry.defaults) && entry.defaults[index] != INVALID_CT_VALUE {
record=resolved, return Resolved_Field_Default{
field=name, expr=ast.INVALID_EXPR,
expr=expr.args[index], pkg=entry.pkg,
pkg=entry.pkg, file=entry.file,
file=entry.file, static_value=entry.defaults[index],
}, entry.values, true 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 return {}, nil, false
@@ -8083,10 +8203,11 @@ build_compound_expr :: proc(
return invalid_hir_expr(checker, expr.span, id) return invalid_hir_expr(checker, expr.span, id)
} }
} else if expr.tuple { } else if expr.tuple {
struct_type = types.INVALID struct_type = types.resolve_alias(expected, store)
} else { } else {
struct_type = types.resolve_alias(expected, store) 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") id := source.add(checker.diagnostics, expr.span, "keyed contextual payload requires a struct payload")
return invalid_hir_expr(checker, expr.span, id, expected) return invalid_hir_expr(checker, expr.span, id, expected)
} }
@@ -8131,6 +8252,34 @@ build_compound_expr :: proc(
diagnostic=source.INVALID_DIAGNOSTIC, 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) fields := types.fields_for(store, struct_type)
union_record := types.is_union(struct_type, store) union_record := types.is_union(struct_type, store)
if union_record && len(expr.args) != 1 { if union_record && len(expr.args) != 1 {
@@ -8177,6 +8326,16 @@ build_compound_expr :: proc(
continue continue
} }
if field_default, default_values, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok { 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 previous := checker.current_comptime_values
checker.current_comptime_values = default_values checker.current_comptime_values = default_values
values[index] = build_nested_expr( values[index] = build_nested_expr(
@@ -8186,7 +8345,7 @@ build_compound_expr :: proc(
checker.current_comptime_values = previous checker.current_comptime_values = previous
values[index] = coerce_expr( values[index] = coerce_expr(
checker, values[index], field.type, checker, values[index], field.type,
checker.ast_module.exprs[field_default.expr].span, field_default.span,
) )
continue continue
} }
@@ -8565,6 +8724,29 @@ build_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
continue 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 field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file); if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
ok && types.is_enum(enum_type, &checker.module.types) { ok && types.is_enum(enum_type, &checker.module.types) {
@@ -13436,6 +13618,7 @@ check :: proc(
} }
for entry in checker.generated_types { for entry in checker.generated_types {
delete(entry.values, allocator) delete(entry.values, allocator)
delete(entry.defaults, allocator)
} }
for origin in checker.type_factory_origins { for origin in checker.type_factory_origins {
delete(origin.values, allocator) 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) return ct_eval_expr(state, expr.right, types.BOOL, depth+1)
case .Call: 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: case .Cast:
target := type_from_syntax(checker, expr.type, state.pkg, state.file) target := type_from_syntax(checker, expr.type, state.pkg, state.file)
value, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) 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 { 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))) 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_pkg, previous_file := state.pkg, state.file
previous_comptime := checker.current_comptime_values previous_comptime := checker.current_comptime_values
state.pkg, state.file = field_default.pkg, field_default.file 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 { if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok 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 { if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false 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_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 checker := state.checker
if expr.left != ast.INVALID_EXPR { if expr.left != ast.INVALID_EXPR {
callee, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1) 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) 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 is_intrinsic_call(checker, expr, "field") {
if len(expr.args) != 2 { 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)) 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), u32(parser.file),
!symbol.is_valid(qualifier) && file_hidden_name(parser, name.symbol), !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 { if current(parser).kind == .Left_Paren {
call := parse_call(parser, qualifier, first, name, 0, false) call := parse_call(parser, qualifier, first, name, 0, false)
return types.intern(&parser.module.type_store, types.Node{ return types.intern(&parser.module.type_store, types.Node{
@@ -620,6 +632,7 @@ parse_keyed_initializers :: proc(
left_brace: token.Token, left_brace: token.Token,
nesting: int, nesting: int,
close_message: string, close_message: string,
require_values := false,
) -> ([]ast.Expr_Id, token.Token) { ) -> ([]ast.Expr_Id, token.Token) {
parser.delimiter_depth += 1 parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1 defer parser.delimiter_depth -= 1
@@ -640,6 +653,8 @@ parse_keyed_initializers :: proc(
skip_newlines(parser) skip_newlines(parser)
value = parse_expression_bp(parser, 0, nesting+1) value = parse_expression_bp(parser, 0, nesting+1)
key_end = parser.module.exprs[value].span 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{ append(&args, add_expr(parser, ast.Expr{
kind=.Keyed, kind=.Keyed,
@@ -696,6 +711,15 @@ brace_starts_tuple :: proc(parser: ^Parser) -> bool {
if current(parser).kind != .Left_Brace { if current(parser).kind != .Left_Brace {
return false 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 depth := 0
for cursor := parser.cursor; cursor < len(parser.tokens.items); cursor += 1 { for cursor := parser.cursor; cursor < len(parser.tokens.items); cursor += 1 {
#partial switch parser.tokens.items[cursor].kind { #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 { parse_tuple_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
left_brace := advance(parser) 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{ return add_expr(parser, ast.Expr{
kind=.Struct_Literal, kind=.Struct_Literal,
span=span_from(left_brace.span, right_brace.span), span=span_from(left_brace.span, right_brace.span),
args=args, args=args,
tuple=true, tuple=!keyed,
left=ast.INVALID_EXPR, left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC, 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. // Generated structs are nominal per comptime type-expression specialization.
// The checker owns canonicalization; this routine deliberately creates a fresh node. // 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)) start := u32(len(store.fields))
append(&store.fields, ..fields) append(&store.fields, ..fields)
id := DYNAMIC_START+Type(len(store.nodes)) 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_start=start,
field_count=u32(len(fields)), field_count=u32(len(fields)),
tuple=tuple, tuple=tuple,
c_layout=c_layout,
declared=true, declared=true,
}) })
return id return id
+324
View File
@@ -13723,3 +13723,327 @@ main func() void {
state := run_executable(output) state := run_executable(output)
testing.expect_value(t, state.exit_code, 0) 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 fallible void
distinct 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, $.expression],
[$.array_type, $.array_literal], [$.array_type, $.array_literal],
[$.expression_statement, $.parenthesized_expression], [$.expression_statement, $.parenthesized_expression],
[$.block, $.tuple_literal], [$.block, $.tuple_literal, $.anonymous_record_literal],
[$.field_initializer, $.expression], [$.field_initializer, $.expression],
[$.tuple_literal],
[$.capture_list, $.expression], [$.capture_list, $.expression],
[$.match_capture, $.expression], [$.match_capture, $.expression],
], ],
@@ -188,6 +187,7 @@ module.exports = grammar({
_type_atom: $ => choice( _type_atom: $ => choice(
$.builtin_type, $.builtin_type,
$.intrinsic_type,
$.named_type, $.named_type,
$.optional_type, $.optional_type,
$.pointer_type, $.pointer_type,
@@ -198,9 +198,11 @@ module.exports = grammar({
parenthesized_type: $ => seq('(', repeat($._newline), $.type, repeat($._newline), ')'), parenthesized_type: $ => seq('(', repeat($._newline), $.type, repeat($._newline), ')'),
named_type: $ => prec.right(seq( named_type: $ => prec.right(seq($.qualified_identifier, optional($.argument_list))),
$.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), optional_type: $ => seq('?', $.type),
@@ -441,6 +443,7 @@ module.exports = grammar({
$.slice_expression, $.slice_expression,
$.postfix_expression, $.postfix_expression,
$.struct_literal, $.struct_literal,
$.anonymous_record_literal,
$.tuple_literal, $.tuple_literal,
$.comptime_block, $.comptime_block,
$.function_literal, $.function_literal,
@@ -556,17 +559,27 @@ module.exports = grammar({
), ),
field_initializer: $ => seq( field_initializer: $ => seq(
field('name', $.identifier), field('name', $._field_name),
optional(seq('=', repeat($._newline), field('value', $.expression))), 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( tuple_literal: $ => prec(1, choice(
seq('{', repeat($._newline), '}'), seq('{', repeat($._newline), '}'),
seq( seq(
'{', repeat($._newline), $.expression, '{', repeat($._newline), $.expression,
',', repeat($._newline), repeat(seq(repeat($._newline), ',', repeat($._newline), $.expression)),
repeat(seq($.expression, ',', repeat($._newline))), optional(seq(repeat($._newline), ',')),
optional($.expression), repeat($._newline), '}', repeat($._newline), '}',
), ),
)), )),
@@ -597,7 +610,7 @@ module.exports = grammar({
), ),
keyed_field_initializer: $ => seq( keyed_field_initializer: $ => seq(
field('name', $.identifier), field('name', $._field_name),
'=', '=',
repeat($._newline), repeat($._newline),
field('value', $.expression), field('value', $.expression),
@@ -635,6 +648,23 @@ module.exports = grammar({
undefined: _ => 'undefined', undefined: _ => 'undefined',
sink: _ => '_', 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_]*/, identifier: _ => /[A-Za-z_][A-Za-z0-9_]*/,
integer: _ => /[0-9]+/, integer: _ => /[0-9]+/,
float: _ => token(prec(1, /[0-9]+\.[0-9]+/)), float: _ => token(prec(1, /[0-9]+\.[0-9]+/)),
@@ -39,6 +39,7 @@
(parameter name: (identifier) @variable.parameter) (parameter name: (identifier) @variable.parameter)
(intrinsic_call_expression function: (identifier) @function.builtin) (intrinsic_call_expression function: (identifier) @function.builtin)
(intrinsic_type function: (identifier) @function.builtin)
(call_expression function: (expression (identifier) @function)) (call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function))) (call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property) (field_expression field: (identifier) @property)
+453 -21
View File
@@ -1149,6 +1149,10 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "builtin_type" "name": "builtin_type"
}, },
{
"type": "SYMBOL",
"name": "intrinsic_type"
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "named_type" "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": { "optional_type": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@@ -3105,6 +3139,10 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "struct_literal" "name": "struct_literal"
}, },
{
"type": "SYMBOL",
"name": "anonymous_record_literal"
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "tuple_literal" "name": "tuple_literal"
@@ -4268,7 +4306,7 @@
"name": "name", "name": "name",
"content": { "content": {
"type": "SYMBOL", "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": { "tuple_literal": {
"type": "PREC", "type": "PREC",
"value": 1, "value": 1,
@@ -4349,25 +4476,17 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "expression" "name": "expression"
}, },
{
"type": "STRING",
"value": ","
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{ {
"type": "REPEAT", "type": "REPEAT",
"content": { "content": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{ {
"type": "SYMBOL", "type": "REPEAT",
"name": "expression" "content": {
"type": "SYMBOL",
"name": "_newline"
}
}, },
{ {
"type": "STRING", "type": "STRING",
@@ -4379,6 +4498,10 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "_newline" "name": "_newline"
} }
},
{
"type": "SYMBOL",
"name": "expression"
} }
] ]
} }
@@ -4387,8 +4510,20 @@
"type": "CHOICE", "type": "CHOICE",
"members": [ "members": [
{ {
"type": "SYMBOL", "type": "SEQ",
"name": "expression" "members": [
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "STRING",
"value": ","
}
]
}, },
{ {
"type": "BLANK" "type": "BLANK"
@@ -4564,7 +4699,7 @@
"name": "name", "name": "name",
"content": { "content": {
"type": "SYMBOL", "type": "SYMBOL",
"name": "identifier" "name": "_field_name"
} }
}, },
{ {
@@ -4876,6 +5011,305 @@
"type": "STRING", "type": "STRING",
"value": "_" "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": { "identifier": {
"type": "PATTERN", "type": "PATTERN",
"value": "[A-Za-z_][A-Za-z0-9_]*" "value": "[A-Za-z_][A-Za-z0-9_]*"
@@ -5029,15 +5463,13 @@
], ],
[ [
"block", "block",
"tuple_literal" "tuple_literal",
"anonymous_record_literal"
], ],
[ [
"field_initializer", "field_initializer",
"expression" "expression"
], ],
[
"tuple_literal"
],
[ [
"capture_list", "capture_list",
"expression" "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", "type": "argument_list",
"named": true, "named": true,
@@ -748,6 +763,10 @@
"multiple": false, "multiple": false,
"required": true, "required": true,
"types": [ "types": [
{
"type": "anonymous_record_literal",
"named": true
},
{ {
"type": "array_literal", "type": "array_literal",
"named": true "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", "type": "keyed_field_initializer",
"named": true, "named": true,
@@ -1608,6 +1653,16 @@
] ]
} }
}, },
{
"type": "none",
"named": true,
"fields": {}
},
{
"type": "opaque_type",
"named": true,
"fields": {}
},
{ {
"type": "optional_type", "type": "optional_type",
"named": true, "named": true,
@@ -2158,6 +2213,10 @@
"type": "function_type", "type": "function_type",
"named": true "named": true
}, },
{
"type": "intrinsic_type",
"named": true
},
{ {
"type": "named_type", "type": "named_type",
"named": true "named": true
@@ -2273,6 +2332,11 @@
} }
} }
}, },
{
"type": "undefined",
"named": true,
"fields": {}
},
{ {
"type": "union_type", "type": "union_type",
"named": true, "named": true,
@@ -2862,11 +2926,11 @@
}, },
{ {
"type": "none", "type": "none",
"named": true "named": false
}, },
{ {
"type": "opaque_type", "type": "opaque",
"named": true "named": false
}, },
{ {
"type": "or", "type": "or",
@@ -2934,7 +2998,7 @@
}, },
{ {
"type": "undefined", "type": "undefined",
"named": true "named": false
}, },
{ {
"type": "union", "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 (enum_literal
(identifier)))))))))))) (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 Intrinsic calls
================== ==================
@@ -166,6 +207,39 @@ main func() void {
(expression (expression
(builtin_type)))))))))) (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 Errdefer
================== ==================
@@ -19,3 +19,6 @@ clear func($K, $V type) void {
# ^^^ keyword # ^^^ keyword
# ^ operator # ^ operator
} }
Generated :: alias struct_type!(.auto, {"value"}, {i32}, {none})
# ^^^^^^^^^^^^ function.builtin