stlib arraylist
This commit is contained in:
+5
-2
@@ -59,6 +59,7 @@ roadmap and milestone history.
|
|||||||
- 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
|
||||||
- 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`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
||||||
|
- 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
|
||||||
- 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
|
||||||
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
|
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
|
||||||
- native function pointer values and types with `@func(...) R`, fallible `@func(...) R ! E`, optional `?@func(...) R`, and non-variadic native indirect calls
|
- native function pointer values and types with `@func(...) R`, fallible `@func(...) R ! E`, optional `?@func(...) R`, and non-variadic native indirect calls
|
||||||
@@ -74,7 +75,8 @@ roadmap and milestone history.
|
|||||||
|
|
||||||
### standard packages
|
### standard packages
|
||||||
|
|
||||||
- `std/mem` allocator contract with a context pointer plus shared `AllocatorVTable`, raw byte operations `raw_alloc` / `raw_realloc` / `raw_free`, fallible typed `alloc(T, allocator, count)`, and typed `free(T, allocator, memory)`; failed nonzero raw reallocation preserves the original allocation, while zero size frees it
|
- `std/mem` 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
|
||||||
|
|
||||||
### compiler behavior
|
### compiler behavior
|
||||||
|
|
||||||
@@ -91,7 +93,8 @@ roadmap and milestone history.
|
|||||||
- tuples and native Brolang variadic functions
|
- tuples and native Brolang variadic functions
|
||||||
- exporting Brolang functions to C and broader target-specific C ABI lowering
|
- exporting Brolang functions to C and broader target-specific C ABI lowering
|
||||||
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
|
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
|
||||||
- typed heap allocation helpers, arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
|
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
|
||||||
|
- recursive type factories, type reflection, inferred type arguments, and type-producing unions/enums
|
||||||
- broader Zig-style pointer/result casts beyond V1 `ptr_cast(T, ptr)`
|
- broader Zig-style pointer/result casts beyond V1 `ptr_cast(T, ptr)`
|
||||||
- sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism
|
- sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism
|
||||||
- backed/C enum composition and must-consume fallible linting
|
- backed/C enum composition and must-consume fallible linting
|
||||||
|
|||||||
@@ -179,8 +179,10 @@ Current prototype features:
|
|||||||
- Demand-monomorphized Brolang and C-ABI functions
|
- Demand-monomorphized Brolang and C-ABI functions
|
||||||
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by comptime argument
|
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by comptime argument
|
||||||
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
|
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
|
||||||
|
- Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`)
|
||||||
- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
||||||
- Native function pointer values and types (`*func(...) R`, `*func(...) R ! E`, `?*func(...) R`)
|
- Native function pointer values and types (`*func(...) R`, `*func(...) R ! E`, `?*func(...) R`)
|
||||||
|
- Typed allocation/reallocation through `std/mem` and generic dynamic arrays through `std/arraylist`
|
||||||
- Bodyless concrete C function declarations with exact external symbol names
|
- Bodyless concrete C function declarations with exact external symbol names
|
||||||
- Bodyless manual and imported C variadic declarations with default argument promotions
|
- Bodyless manual and imported C variadic declarations with default argument promotions
|
||||||
- Ordered linking of additional C sources, objects, archives, and libraries
|
- Ordered linking of additional C sources, objects, archives, and libraries
|
||||||
|
|||||||
@@ -743,9 +743,23 @@
|
|||||||
- final open-constant defaults feed one last inference fixpoint before stale
|
- final open-constant defaults feed one last inference fixpoint before stale
|
||||||
specializations are pruned
|
specializations are pruned
|
||||||
|
|
||||||
30. basic `std/arraylist` implementation using the new `std/mem` typed allocation
|
30. Zig-style type factories and basic `std/arraylist` (implemented; v1)
|
||||||
|
- comptime-only functions may return `type`; anonymous `struct { ... }` expressions and
|
||||||
|
factory calls such as `ArrayList(i32)` resolve to cached nominal concrete types
|
||||||
|
- factory parameters use the existing explicit `$T type` / integer comptime parameters;
|
||||||
|
normal comptime control flow and helper factory calls are supported
|
||||||
|
- type-factory calls work in signatures, nested types, struct literals, and type builtins;
|
||||||
|
runtime materialization and recursive specializations are diagnosed
|
||||||
|
- `std/mem` adds typed `empty` and failure-preserving `realloc`, including overflow,
|
||||||
|
zero-count, zero-sized-type, and alignment handling
|
||||||
|
- `std/arraylist.ArrayList(T)` exposes `items`, `capacity`, and `allocator`, with fallible
|
||||||
|
reserve/append, roughly 1.5x growth from 8, clear-without-free, and reusable deinit
|
||||||
|
- deferred: recursive factories, reflection, inferred type arguments, type-producing
|
||||||
|
unions/enums, pop/insert/remove/shrink/clone container operations
|
||||||
|
|
||||||
31. disallow arbitrary integer division
|
31. threading generic/polymorphic type information everywhere (init, deinit, etc.) might be annoying and verbose. consider whether generic structs could fit nicely to avoid this.
|
||||||
|
|
||||||
|
32. disallow arbitrary integer division
|
||||||
- take inspiration from zig
|
- take inspiration from zig
|
||||||
- see also below for a word on unchecked casts
|
- see also below for a word on unchecked casts
|
||||||
- the user should be explicit about what they mean with integer division (e.g. `div`, `rem`, `trunc`)
|
- the user should be explicit about what they mean with integer division (e.g. `div`, `rem`, `trunc`)
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Try,
|
Try,
|
||||||
Catch,
|
Catch,
|
||||||
Function_Literal,
|
Function_Literal,
|
||||||
|
Anonymous_Struct_Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr :: struct {
|
Expr :: struct {
|
||||||
@@ -294,6 +295,7 @@ Module :: struct {
|
|||||||
unsupported: [dynamic]Unsupported,
|
unsupported: [dynamic]Unsupported,
|
||||||
c_trampolines: [dynamic]Trampoline,
|
c_trampolines: [dynamic]Trampoline,
|
||||||
strings: [dynamic]string,
|
strings: [dynamic]string,
|
||||||
|
type_fields: [dynamic]types.Field,
|
||||||
type_store: types.Store,
|
type_store: types.Store,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -312,6 +314,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
|||||||
module.unsupported.allocator = allocator
|
module.unsupported.allocator = allocator
|
||||||
module.c_trampolines.allocator = allocator
|
module.c_trampolines.allocator = allocator
|
||||||
module.strings.allocator = allocator
|
module.strings.allocator = allocator
|
||||||
|
module.type_fields.allocator = allocator
|
||||||
return module
|
return module
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,5 +365,6 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(module.unsupported)
|
delete(module.unsupported)
|
||||||
delete(module.c_trampolines)
|
delete(module.c_trampolines)
|
||||||
delete(module.strings)
|
delete(module.strings)
|
||||||
|
delete(module.type_fields)
|
||||||
types.destroy_store(&module.type_store)
|
types.destroy_store(&module.type_store)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,19 @@ Import_Index_Entry :: struct {
|
|||||||
id: ast.Import_Id,
|
id: ast.Import_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type_Factory_Entry :: struct {
|
||||||
|
template: ast.Function_Id,
|
||||||
|
values: []Comptime_Value,
|
||||||
|
result: types.Type,
|
||||||
|
resolving: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
Generated_Type_Entry :: struct {
|
||||||
|
expr: ast.Expr_Id,
|
||||||
|
values: []Comptime_Value,
|
||||||
|
result: types.Type,
|
||||||
|
}
|
||||||
|
|
||||||
Checker :: struct {
|
Checker :: struct {
|
||||||
ast_module: ^ast.Module,
|
ast_module: ^ast.Module,
|
||||||
diagnostics: ^source.Diagnostics,
|
diagnostics: ^source.Diagnostics,
|
||||||
@@ -169,6 +182,8 @@ Checker :: struct {
|
|||||||
current_result: types.Type,
|
current_result: types.Type,
|
||||||
current_build_ctx: ^Build_Ctx,
|
current_build_ctx: ^Build_Ctx,
|
||||||
current_comptime_values: []Comptime_Value,
|
current_comptime_values: []Comptime_Value,
|
||||||
|
type_factories: [dynamic]Type_Factory_Entry,
|
||||||
|
generated_types: [dynamic]Generated_Type_Entry,
|
||||||
target: target.Target,
|
target: target.Target,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -340,6 +355,8 @@ write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: ty
|
|||||||
write_type_label(checker, builder, item.child)
|
write_type_label(checker, builder, item.child)
|
||||||
strings.write_string(builder, " ! ")
|
strings.write_string(builder, " ! ")
|
||||||
write_type_label(checker, builder, item.extra)
|
write_type_label(checker, builder, item.extra)
|
||||||
|
case .Type_Call:
|
||||||
|
strings.write_string(builder, "<type factory call>")
|
||||||
case .Struct:
|
case .Struct:
|
||||||
strings.write_string(builder, "struct")
|
strings.write_string(builder, "struct")
|
||||||
case .Union:
|
case .Union:
|
||||||
@@ -597,6 +614,8 @@ type_from_syntax :: proc(
|
|||||||
if params_changed || result != item.child {
|
if params_changed || result != item.child {
|
||||||
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
|
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
|
||||||
}
|
}
|
||||||
|
case .Type_Call:
|
||||||
|
return resolve_type_factory_call(checker, ast.Expr_Id(item.count_expr), pkg, file)
|
||||||
}
|
}
|
||||||
if changed {
|
if changed {
|
||||||
return types.intern(store, item)
|
return types.intern(store, item)
|
||||||
@@ -1130,10 +1149,133 @@ resolve_type_argument :: proc(
|
|||||||
value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
||||||
value = types.resolve_alias(value, &checker.module.types)
|
value = types.resolve_alias(value, &checker.module.types)
|
||||||
return value, types.is_valid(value)
|
return value, types.is_valid(value)
|
||||||
|
case .Call:
|
||||||
|
value := resolve_type_factory_call(checker, expr_id, pkg, file)
|
||||||
|
return value, types.is_valid(value)
|
||||||
}
|
}
|
||||||
return types.INVALID, false
|
return types.INVALID, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone_comptime_values :: proc(values: []Comptime_Value, allocator: mem.Allocator) -> []Comptime_Value {
|
||||||
|
result := make([]Comptime_Value, len(values), allocator)
|
||||||
|
copy(result, values)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> types.Type {
|
||||||
|
for entry in checker.generated_types {
|
||||||
|
if entry.expr == expr_id && comptime_values_equal(entry.values, checker.current_comptime_values) {
|
||||||
|
return entry.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
expr := checker.ast_module.exprs[expr_id]
|
||||||
|
field_start := int(u32(expr.integer>>32))
|
||||||
|
field_count := int(u32(expr.integer))
|
||||||
|
if field_start < 0 || field_count < 0 || field_start+field_count > len(checker.ast_module.type_fields) {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
template_fields := checker.ast_module.type_fields[field_start:field_start+field_count]
|
||||||
|
fields := make([]types.Field, len(template_fields), checker.allocator)
|
||||||
|
defer delete(fields, checker.allocator)
|
||||||
|
for field, index in template_fields {
|
||||||
|
resolved := type_from_syntax(checker, field.type, pkg, file)
|
||||||
|
if !is_runtime_type(checker, resolved) || types.is_void(resolved) {
|
||||||
|
source.addf(checker.diagnostics, expr.span, "anonymous struct field '%s' requires a concrete runtime type, got %s", symbol_text(checker, symbol.Id(field.name)), type_label(checker, resolved))
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
fields[index] = types.Field{name=field.name, type=resolved}
|
||||||
|
}
|
||||||
|
result := types.struct_generated(&checker.module.types, fields)
|
||||||
|
append(&checker.generated_types, Generated_Type_Entry{
|
||||||
|
expr=expr_id,
|
||||||
|
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
||||||
|
result=result,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg: ast.Package_Id, file: ast.File_Id) -> types.Type {
|
||||||
|
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
expr := checker.ast_module.exprs[expr_id]
|
||||||
|
if expr.kind != .Call || expr.left != ast.INVALID_EXPR {
|
||||||
|
source.add(checker.diagnostics, expr.span, "type position requires a direct type-factory call")
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||||
|
if !available {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
||||||
|
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
|
||||||
|
source.addf(checker.diagnostics, expr.span, "unknown type factory '%s'", symbol_text(checker, expr.name))
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
function := checker.ast_module.functions[template]
|
||||||
|
if !is_type_metatype_syntax(checker, function.result) || types.is_valid(function.error) {
|
||||||
|
source.addf(checker.diagnostics, expr.span, "function '%s' does not return a type", symbol_text(checker, expr.name))
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
for param in function.params {
|
||||||
|
if !param.comptime_value {
|
||||||
|
source.addf(checker.diagnostics, param.span, "type-factory parameter '%s' must be comptime", symbol_text(checker, param.name))
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valid_call_arity(function, len(expr.args)) {
|
||||||
|
source.addf(checker.diagnostics, expr.span, "type factory '%s' expects %d arguments, got %d", symbol_text(checker, expr.name), len(function.params), len(expr.args))
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
values, ok := collect_comptime_values(checker, function, expr.args, pkg, file, true, checker.current_comptime_values)
|
||||||
|
defer delete(values, checker.allocator)
|
||||||
|
if !ok {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
// A generic function's declaration is validated before it has a specialization.
|
||||||
|
// Leave calls containing its unresolved type parameters pending until then.
|
||||||
|
for value in values {
|
||||||
|
if value.kind != .Type {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if item, item_ok := types.node(&checker.module.types, value.type); item_ok && item.kind == .Named && !item.declared {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &entry in checker.type_factories {
|
||||||
|
if entry.template != template || !comptime_values_equal(entry.values, values) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if entry.resolving {
|
||||||
|
source.addf(checker.diagnostics, expr.span, "recursive type-factory specialization of '%s'", symbol_text(checker, expr.name))
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
return entry.result
|
||||||
|
}
|
||||||
|
entry_index := len(checker.type_factories)
|
||||||
|
append(&checker.type_factories, Type_Factory_Entry{
|
||||||
|
template=template,
|
||||||
|
values=clone_comptime_values(values, checker.allocator),
|
||||||
|
result=types.INVALID,
|
||||||
|
resolving=true,
|
||||||
|
})
|
||||||
|
state := ct_state_make(checker, pkg, file)
|
||||||
|
value, flow, eval_ok := ct_eval_call_expr(&state, expr, function.result, 0)
|
||||||
|
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)
|
||||||
|
} else if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||||
|
source.addf(checker.diagnostics, expr.span, "type factory '%s' did not return a type", symbol_text(checker, expr.name))
|
||||||
|
}
|
||||||
|
ct_state_destroy(&state)
|
||||||
|
checker.type_factories[entry_index].result = result
|
||||||
|
checker.type_factories[entry_index].resolving = false
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
collect_comptime_values :: proc(
|
collect_comptime_values :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
function: ast.Function,
|
function: ast.Function,
|
||||||
@@ -1362,7 +1504,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
}
|
}
|
||||||
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
append(&stack, expr.left, expr.right)
|
append(&stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Type, .Name:
|
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Type, .Name, .Anonymous_Struct_Type:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2173,6 +2315,9 @@ infer_expr :: proc(
|
|||||||
case .Type:
|
case .Type:
|
||||||
last = types.INVALID
|
last = types.INVALID
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
|
case .Anonymous_Struct_Type:
|
||||||
|
last = types.INVALID
|
||||||
|
_ = pop(&stack)
|
||||||
case .Integer:
|
case .Integer:
|
||||||
last = types.I64
|
last = types.I64
|
||||||
if expr.integer <= 0x7fff_ffff_ffff_ffff {
|
if expr.integer <= 0x7fff_ffff_ffff_ffff {
|
||||||
@@ -4789,7 +4934,10 @@ build_compound_expr :: proc(
|
|||||||
})
|
})
|
||||||
case .Struct_Literal:
|
case .Struct_Literal:
|
||||||
struct_type := types.INVALID
|
struct_type := types.INVALID
|
||||||
if symbol.is_valid(expr.name) {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
|
struct_type, _ = resolve_type_argument(checker, expr.left, pkg, file)
|
||||||
|
struct_type = types.resolve_alias(struct_type, store)
|
||||||
|
} else if symbol.is_valid(expr.name) {
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||||
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) if available else types.INVALID
|
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) if available else types.INVALID
|
||||||
struct_type = types.resolve_alias(struct_type, store)
|
struct_type = types.resolve_alias(struct_type, store)
|
||||||
@@ -4977,7 +5125,7 @@ build_expr :: proc(
|
|||||||
template := ast.Function_Id(u32(expr.integer))
|
template := ast.Function_Id(u32(expr.integer))
|
||||||
last = build_function_value(checker, template, expr.span, frame.expected)
|
last = build_function_value(checker, template, expr.span, frame.expected)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .Type:
|
case .Type, .Anonymous_Struct_Type:
|
||||||
id := source.add(checker.diagnostics, expr.span, "type is not a runtime value")
|
id := source.add(checker.diagnostics, expr.span, "type is not a runtime value")
|
||||||
last = invalid_hir_expr(checker, expr.span, id)
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
@@ -5294,6 +5442,12 @@ build_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
function := checker.ast_module.functions[template]
|
function := checker.ast_module.functions[template]
|
||||||
|
if is_type_metatype_syntax(checker, function.result) {
|
||||||
|
id := source.addf(checker.diagnostics, expr.span, "type factory '%s' is only valid in type position", symbol_text(checker, expr.name))
|
||||||
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !valid_call_arity(function, len(expr.args)) {
|
if !valid_call_arity(function, len(expr.args)) {
|
||||||
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
|
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
|
||||||
"function '%s' expects %d arguments, got %d"
|
"function '%s' expects %d arguments, got %d"
|
||||||
@@ -8821,6 +8975,8 @@ check :: proc(
|
|||||||
checker.build_stack.allocator = allocator
|
checker.build_stack.allocator = allocator
|
||||||
checker.cycle_stack.allocator = allocator
|
checker.cycle_stack.allocator = allocator
|
||||||
checker.anon_globals.allocator = allocator
|
checker.anon_globals.allocator = allocator
|
||||||
|
checker.type_factories.allocator = allocator
|
||||||
|
checker.generated_types.allocator = allocator
|
||||||
build_symbol_indexes(&checker)
|
build_symbol_indexes(&checker)
|
||||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
@@ -8864,6 +9020,14 @@ check :: proc(
|
|||||||
delete(checker.infer_stack)
|
delete(checker.infer_stack)
|
||||||
delete(checker.build_stack)
|
delete(checker.build_stack)
|
||||||
delete(checker.cycle_stack)
|
delete(checker.cycle_stack)
|
||||||
|
for entry in checker.type_factories {
|
||||||
|
delete(entry.values, allocator)
|
||||||
|
}
|
||||||
|
for entry in checker.generated_types {
|
||||||
|
delete(entry.values, allocator)
|
||||||
|
}
|
||||||
|
delete(checker.type_factories)
|
||||||
|
delete(checker.generated_types)
|
||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
for function, index in ast_module.functions {
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ Ct_Value_Kind :: enum u8 {
|
|||||||
Pointer,
|
Pointer,
|
||||||
Slice,
|
Slice,
|
||||||
Function,
|
Function,
|
||||||
|
Type,
|
||||||
None,
|
None,
|
||||||
Optional_Some,
|
Optional_Some,
|
||||||
Fallible,
|
Fallible,
|
||||||
@@ -333,6 +334,9 @@ ct_state_make :: proc(
|
|||||||
if value.kind == .Integer {
|
if value.kind == .Integer {
|
||||||
id := ct_add_value(&state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
|
id := ct_add_value(&state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
|
||||||
ct_bind_value(&state, value.name, value.type, id, false)
|
ct_bind_value(&state, value.name, value.type, id, false)
|
||||||
|
} else if value.kind == .Type {
|
||||||
|
id := ct_add_value(&state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)})
|
||||||
|
ct_bind_value(&state, value.name, types.INVALID, id, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
@@ -532,6 +536,9 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
|
|||||||
return id, true
|
return id, true
|
||||||
}
|
}
|
||||||
value := state.values[id]
|
value := state.values[id]
|
||||||
|
if value.kind == .Type && is_type_metatype_syntax(state.checker, expected) {
|
||||||
|
return id, true
|
||||||
|
}
|
||||||
if types.equal(value.type, expected) {
|
if types.equal(value.type, expected) {
|
||||||
return id, true
|
return id, true
|
||||||
}
|
}
|
||||||
@@ -914,7 +921,7 @@ ct_eval_expr :: proc(
|
|||||||
id := ct_add_value(state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
|
id := ct_add_value(state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
|
||||||
return id, ct_flow(.Normal), true
|
return id, ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "type parameter '%s' is not a runtime value", symbol_text(checker, expr.name))
|
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)}), ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
} else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT {
|
} else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT {
|
||||||
if index, ok := ct_find_binding_index(state, expr.qualifier); ok {
|
if index, ok := ct_find_binding_index(state, expr.qualifier); ok {
|
||||||
@@ -971,6 +978,12 @@ ct_eval_expr :: proc(
|
|||||||
return ct_eval_array_expr(state, expr, expected, depth+1)
|
return ct_eval_array_expr(state, expr, expected, depth+1)
|
||||||
case .Struct_Literal:
|
case .Struct_Literal:
|
||||||
return ct_eval_struct_expr(state, expr, expected, depth+1)
|
return ct_eval_struct_expr(state, expr, expected, depth+1)
|
||||||
|
case .Type:
|
||||||
|
resolved := type_from_syntax(checker, expr.type, state.pkg, state.file)
|
||||||
|
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved)
|
||||||
|
case .Anonymous_Struct_Type:
|
||||||
|
resolved := resolve_generated_struct_type(checker, expr_id, state.pkg, state.file)
|
||||||
|
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)}), ct_flow(.Normal), types.is_valid(resolved)
|
||||||
case .Enum_Literal:
|
case .Enum_Literal:
|
||||||
return ct_eval_enum_literal(state, expr, expected, depth+1)
|
return ct_eval_enum_literal(state, expr, expected, depth+1)
|
||||||
case .None:
|
case .None:
|
||||||
@@ -1150,7 +1163,7 @@ ct_eval_expr :: proc(
|
|||||||
return value, ct_flow(.Normal), true
|
return value, ct_flow(.Normal), true
|
||||||
case .Slice:
|
case .Slice:
|
||||||
return ct_eval_slice_expr(state, expr, depth+1)
|
return ct_eval_slice_expr(state, expr, depth+1)
|
||||||
case .Type, .Undefined, .Keyed:
|
case .Undefined, .Keyed:
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
|
||||||
}
|
}
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
|
||||||
@@ -1216,7 +1229,10 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
|
|||||||
checker := state.checker
|
checker := state.checker
|
||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
struct_type := types.INVALID
|
struct_type := types.INVALID
|
||||||
if symbol.is_valid(expr.name) {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
|
struct_type, _ = resolve_type_argument(checker, expr.left, state.pkg, state.file)
|
||||||
|
struct_type = types.resolve_alias(struct_type, store)
|
||||||
|
} else if symbol.is_valid(expr.name) {
|
||||||
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
|
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
|
||||||
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, state.file))) if available else types.INVALID
|
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, state.file))) if available else types.INVALID
|
||||||
struct_type = types.resolve_alias(struct_type, store)
|
struct_type = types.resolve_alias(struct_type, store)
|
||||||
|
|||||||
@@ -1402,6 +1402,9 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
|||||||
for &statement in module.statements {
|
for &statement in module.statements {
|
||||||
statement.type = canonical_type(module, statement.type, mapping, visiting)
|
statement.type = canonical_type(module, statement.type, mapping, visiting)
|
||||||
}
|
}
|
||||||
|
for &field in module.type_fields {
|
||||||
|
field.type = canonical_type(module, field.type, mapping, visiting)
|
||||||
|
}
|
||||||
for index := 0; index < original_count; index += 1 {
|
for index := 0; index < original_count; index += 1 {
|
||||||
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
|
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,11 +396,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
success_lbl := fresh_label(state)
|
success_lbl := fresh_label(state)
|
||||||
error_lbl := fresh_label(state)
|
error_lbl := fresh_label(state)
|
||||||
merge_lbl := fresh_label(state)
|
merge_lbl := fresh_label(state)
|
||||||
slot := append_instruction(state, ir.Instruction{
|
slot := ir.INVALID_INSTRUCTION
|
||||||
|
if !types.is_void(success) {
|
||||||
|
slot = append_instruction(state, ir.Instruction{
|
||||||
op=.Alloca, span=expr.span, type=success,
|
op=.Alloca, span=expr.span, type=success,
|
||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Cond_Br, span=expr.span, type=types.VOID,
|
op=.Cond_Br, span=expr.span, type=types.VOID,
|
||||||
integer=success_lbl, target=ir.Ref(u32(error_lbl)), a=ok,
|
integer=success_lbl, target=ir.Ref(u32(error_lbl)), a=ok,
|
||||||
@@ -468,10 +471,12 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
}
|
}
|
||||||
if expr.right != hir.INVALID_EXPR {
|
if expr.right != hir.INVALID_EXPR {
|
||||||
fallback := lower_nested_expr(state, expr.right)
|
fallback := lower_nested_expr(state, expr.right)
|
||||||
|
if !types.is_void(success) {
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Store, span=expr.span, type=success,
|
op=.Store, span=expr.span, type=success,
|
||||||
target=ir.INVALID_REF, a=slot, b=fallback, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=ir.INVALID_REF, a=slot, b=fallback, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
|
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
|
||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
@@ -505,11 +510,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
append_instruction(state, ir.Instruction{
|
merge := append_instruction(state, ir.Instruction{
|
||||||
op=.Label, span=expr.span, type=types.VOID, integer=merge_lbl,
|
op=.Label, span=expr.span, type=types.VOID, integer=merge_lbl,
|
||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
if types.is_void(success) {
|
||||||
|
return merge
|
||||||
|
}
|
||||||
return append_instruction(state, ir.Instruction{
|
return append_instruction(state, ir.Instruction{
|
||||||
op=.Load, span=expr.span, type=success,
|
op=.Load, span=expr.span, type=success,
|
||||||
target=ir.INVALID_REF, a=slot, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=slot, b=ir.INVALID_INSTRUCTION,
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
}
|
}
|
||||||
name = advance(parser)
|
name = advance(parser)
|
||||||
}
|
}
|
||||||
return types.named(
|
named := types.named(
|
||||||
&parser.module.type_store,
|
&parser.module.type_store,
|
||||||
u32(parser.pkg),
|
u32(parser.pkg),
|
||||||
u32(name.symbol),
|
u32(name.symbol),
|
||||||
@@ -386,6 +386,14 @@ 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.is_valid(qualifier) && file_hidden_name(parser, name),
|
||||||
)
|
)
|
||||||
|
if current(parser).kind == .Left_Paren {
|
||||||
|
call := parse_call(parser, qualifier, first, name, 0)
|
||||||
|
return types.intern(&parser.module.type_store, types.Node{
|
||||||
|
kind=.Type_Call,
|
||||||
|
count_expr=u32(call),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return named
|
||||||
}
|
}
|
||||||
source.add(parser.diagnostics, tok.span, "expected a type")
|
source.add(parser.diagnostics, tok.span, "expected a type")
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
@@ -589,6 +597,29 @@ parse_struct_literal :: proc(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_anonymous_struct_type_expr :: proc(parser: ^Parser) -> ast.Expr_Id {
|
||||||
|
start := advance(parser)
|
||||||
|
fields: [dynamic]types.Field
|
||||||
|
fields.allocator = parser.module.allocator
|
||||||
|
if !parse_record_body(parser, &fields, "expected '{' after anonymous struct type") {
|
||||||
|
delete(fields)
|
||||||
|
return invalid_expr(parser, start.span, "invalid anonymous struct type")
|
||||||
|
}
|
||||||
|
end := previous(parser)
|
||||||
|
field_start := u32(len(parser.module.type_fields))
|
||||||
|
field_count := u32(len(fields))
|
||||||
|
append(&parser.module.type_fields, ..fields[:])
|
||||||
|
delete(fields)
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Anonymous_Struct_Type,
|
||||||
|
span=span_from(start.span, end.span),
|
||||||
|
integer=u64(field_start)<<32 | u64(field_count),
|
||||||
|
left=ast.INVALID_EXPR,
|
||||||
|
right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
|
parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
|
||||||
value: u64
|
value: u64
|
||||||
for byte in transmute([]byte)text {
|
for byte in transmute([]byte)text {
|
||||||
@@ -755,6 +786,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
})
|
})
|
||||||
case .Keyword_Func:
|
case .Keyword_Func:
|
||||||
return parse_function_literal(parser)
|
return parse_function_literal(parser)
|
||||||
|
case .Keyword_Struct:
|
||||||
|
return parse_anonymous_struct_type_expr(parser)
|
||||||
case .Left_Bracket:
|
case .Left_Bracket:
|
||||||
if starts_declared_type(parser) {
|
if starts_declared_type(parser) {
|
||||||
start := tok
|
start := tok
|
||||||
@@ -842,7 +875,20 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
name = advance(parser)
|
name = advance(parser)
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Left_Paren {
|
if current(parser).kind == .Left_Paren {
|
||||||
return parse_call(parser, qualifier, first, name, nesting)
|
call := parse_call(parser, qualifier, first, name, nesting)
|
||||||
|
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
|
||||||
|
left_brace := advance(parser)
|
||||||
|
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Struct_Literal,
|
||||||
|
span=span_from(parser.module.exprs[call].span, right_brace.span),
|
||||||
|
args=args,
|
||||||
|
left=call,
|
||||||
|
right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return call
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
|
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
|
||||||
return parse_struct_literal(parser, qualifier, first, name, nesting)
|
return parse_struct_literal(parser, qualifier, first, name, nesting)
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ Kind :: enum u8 {
|
|||||||
Struct,
|
Struct,
|
||||||
Union,
|
Union,
|
||||||
Fallible,
|
Fallible,
|
||||||
|
Type_Call,
|
||||||
}
|
}
|
||||||
|
|
||||||
Node :: struct {
|
Node :: struct {
|
||||||
@@ -359,6 +360,21 @@ struct_anonymous :: proc(store: ^Store, fields: []Field) -> 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) -> Type {
|
||||||
|
start := u32(len(store.fields))
|
||||||
|
append(&store.fields, ..fields)
|
||||||
|
id := DYNAMIC_START+Type(len(store.nodes))
|
||||||
|
append(&store.nodes, Node{
|
||||||
|
kind=.Struct,
|
||||||
|
field_start=start,
|
||||||
|
field_count=u32(len(fields)),
|
||||||
|
declared=true,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
|
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
|
||||||
for variant in store.variants {
|
for variant in store.variants {
|
||||||
if variant.name == name && variant.payload == payload {
|
if variant.name == name && variant.payload == payload {
|
||||||
|
|||||||
@@ -6990,6 +6990,71 @@ comptime_type_params_compile_and_run :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
type_factories_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-type-factory"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/type_factory", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
arraylist_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-arraylist"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/arraylist", output, nil, target.DEFAULT, cimport.Options{}, ".")
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
type_factory_rejects_runtime_parameters_and_recursion :: proc(t: ^testing.T) {
|
||||||
|
texts := []string{
|
||||||
|
`Bad func($T type, n usize) type {
|
||||||
|
return struct { value [n]T }
|
||||||
|
}
|
||||||
|
main func() void { value Bad(i32, 4) = undefined; _ = &value }
|
||||||
|
`,
|
||||||
|
`Loop func($T type) type {
|
||||||
|
return struct { next @Loop(T) }
|
||||||
|
}
|
||||||
|
main func() void { value Loop(i32) = undefined; _ = &value }
|
||||||
|
`,
|
||||||
|
`Box func($T type) type {
|
||||||
|
return struct { value T }
|
||||||
|
}
|
||||||
|
main func() void { _ = Box(i32) }
|
||||||
|
`,
|
||||||
|
`Bad func($T type) type {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
main func() void { value Bad(i32) = undefined; _ = &value }
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
wanted := []string{"must be comptime", "recursive type-factory specialization", "only valid in type position", "cannot implicitly convert"}
|
||||||
|
for text, index in texts {
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, wanted[index])
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
hir.destroy_module(&hir_module)
|
||||||
|
ast.destroy_module(&ast_module)
|
||||||
|
delete(stream.items)
|
||||||
|
symbol.destroy_table(&symbols)
|
||||||
|
source.destroy_diagnostics(&diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
comptime_eval_compile_and_run :: proc(t: ^testing.T) {
|
comptime_eval_compile_and_run :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-comptime-eval"
|
output := "/tmp/brolang-test-comptime-eval"
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
arraylist :: import "@std/arraylist"
|
||||||
|
mem :: import "@std/mem"
|
||||||
|
|
||||||
|
_fail_alloc func(_ ?*mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
_fail_realloc func(_ ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
_fail_free func(_ ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {}
|
||||||
|
|
||||||
|
_fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||||
|
alloc = _fail_alloc,
|
||||||
|
realloc = _fail_realloc,
|
||||||
|
free = _fail_free,
|
||||||
|
}
|
||||||
|
|
||||||
|
_fail_allocator mem.Allocator :: mem.Allocator {
|
||||||
|
context = none,
|
||||||
|
vtable = &_fail_vtable,
|
||||||
|
}
|
||||||
|
|
||||||
|
_noop func() void {}
|
||||||
|
|
||||||
|
run func() i32 ! mem.AllocError {
|
||||||
|
values arraylist.ArrayList(i32) = arraylist.init(i32, mem.c_allocator)
|
||||||
|
defer arraylist.deinit(i32, &values)
|
||||||
|
if (values.items.len != 0 or values.capacity != 0) return 1
|
||||||
|
|
||||||
|
i usize = 0
|
||||||
|
while i < 20 : i += 1 {
|
||||||
|
arraylist.append(i32, &values, i32(i)) catch |_| {
|
||||||
|
return .out_of_memory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (values.items.len != 20 or values.capacity < 20) return 2
|
||||||
|
if (values.items[0] != 0 or values.items[19] != 19) return 3
|
||||||
|
values.items[3] = 33
|
||||||
|
if (values.items[3] != 33) return 4
|
||||||
|
|
||||||
|
arraylist.reserve(i32, &values, 50) catch |_| {
|
||||||
|
return .out_of_memory
|
||||||
|
}
|
||||||
|
if (values.capacity < 50 or values.items.len != 20 or values.items[19] != 19) return 5
|
||||||
|
capacity usize :: values.capacity
|
||||||
|
arraylist.clear(i32, &values)
|
||||||
|
if (values.items.len != 0 or values.capacity != capacity) return 6
|
||||||
|
arraylist.append(i32, &values, 7) catch |_| {
|
||||||
|
return .out_of_memory
|
||||||
|
}
|
||||||
|
if (values.items.len != 1 or values.items[0] != 7 or values.capacity != capacity) return 7
|
||||||
|
|
||||||
|
empty_values arraylist.ArrayList([0]u8) = arraylist.init([0]u8, mem.c_allocator)
|
||||||
|
defer arraylist.deinit([0]u8, &empty_values)
|
||||||
|
zero [0]u8 :: []
|
||||||
|
arraylist.append([0]u8, &empty_values, zero) catch |_| {
|
||||||
|
return .out_of_memory
|
||||||
|
}
|
||||||
|
if (empty_values.items.len != 1) return 8
|
||||||
|
|
||||||
|
failed arraylist.ArrayList(i32) = arraylist.init(i32, _fail_allocator)
|
||||||
|
failed_as_expected bool = false
|
||||||
|
arraylist.append(i32, &failed, 1) catch |_| {
|
||||||
|
failed_as_expected = true
|
||||||
|
yield _noop()
|
||||||
|
}
|
||||||
|
if (failed_as_expected == false or failed.items.len != 0 or failed.capacity != 0) return 9
|
||||||
|
arraylist.deinit(i32, &failed)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
return run() catch 100
|
||||||
|
}
|
||||||
@@ -92,6 +92,14 @@ typed_allocator_test func() i32 {
|
|||||||
typed[0] = 10
|
typed[0] = 10
|
||||||
typed[3] = 20
|
typed[3] = 20
|
||||||
if (typed[0] + typed[3] != 30) return 39
|
if (typed[0] + typed[3] != 30) return 39
|
||||||
|
typed = mem.realloc(i32, mem.c_allocator, typed, 8) catch |_| {
|
||||||
|
return 41
|
||||||
|
}
|
||||||
|
if (typed.len != 8 or typed[0] != 10 or typed[3] != 20) return 42
|
||||||
|
typed = mem.realloc(i32, mem.c_allocator, typed, 2) catch |_| {
|
||||||
|
return 43
|
||||||
|
}
|
||||||
|
if (typed.len != 2 or typed[0] != 10) return 44
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
Box func($T type) type {
|
||||||
|
return struct {
|
||||||
|
value T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer func($T type, $N usize) type {
|
||||||
|
if N == 0 {
|
||||||
|
return struct {
|
||||||
|
values [0]T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return struct {
|
||||||
|
values [N]T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxAlias func($T type) type {
|
||||||
|
return Box(T)
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalAlias func($T type) type {
|
||||||
|
chosen :: T
|
||||||
|
return chosen
|
||||||
|
}
|
||||||
|
|
||||||
|
make_box func($T type, value T) Box(T) {
|
||||||
|
return Box(T) { value = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
box Box(i32) :: make_box(i32, 42)
|
||||||
|
if (box.value != 42) return 1
|
||||||
|
aliased BoxAlias(i32) :: box
|
||||||
|
if (aliased.value != 42) return 3
|
||||||
|
local_alias LocalAlias(i32) :: 42
|
||||||
|
if (local_alias != 42) return 6
|
||||||
|
pointer @Box(i32) :: &box
|
||||||
|
if (pointer.value != 42) return 4
|
||||||
|
buffer Buffer(u8, 4) :: Buffer(u8, 4) { values = [1, 2, 3, 4] }
|
||||||
|
if (buffer.values.len != 4) return 2
|
||||||
|
if (size_of(Buffer(u8, 4)) != 4) return 5
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
mem :: import "@std/mem"
|
||||||
|
|
||||||
|
ArrayList func($T type) type {
|
||||||
|
return struct {
|
||||||
|
items []mut T
|
||||||
|
capacity usize
|
||||||
|
allocator mem.Allocator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init func($T type, allocator mem.Allocator) ArrayList(T) {
|
||||||
|
return ArrayList(T) {
|
||||||
|
items = mem.empty(T),
|
||||||
|
capacity = 0,
|
||||||
|
allocator = allocator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit func($T type, list @mut ArrayList(T)) void {
|
||||||
|
allocation []mut T :: list.items.ptr[..list.capacity]
|
||||||
|
mem.free(T, list.allocator, allocation)
|
||||||
|
list.items = mem.empty(T)
|
||||||
|
list.capacity = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem.AllocError {
|
||||||
|
if minimum_capacity <= list.capacity {
|
||||||
|
return _
|
||||||
|
}
|
||||||
|
|
||||||
|
new_capacity usize = 8
|
||||||
|
if list.capacity >= 8 {
|
||||||
|
half usize :: list.capacity / 2
|
||||||
|
if list.capacity > max_value(usize) - half {
|
||||||
|
new_capacity = minimum_capacity
|
||||||
|
} else {
|
||||||
|
new_capacity = list.capacity + half
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if new_capacity < minimum_capacity {
|
||||||
|
new_capacity = minimum_capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
length usize :: list.items.len
|
||||||
|
allocation []mut T :: list.items.ptr[..list.capacity]
|
||||||
|
grown []mut T :: mem.realloc(T, list.allocator, allocation, new_capacity) catch |_| {
|
||||||
|
return .out_of_memory
|
||||||
|
}
|
||||||
|
list.items = grown.ptr[..length]
|
||||||
|
list.capacity = new_capacity
|
||||||
|
return _
|
||||||
|
}
|
||||||
|
|
||||||
|
append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
|
||||||
|
length usize :: list.items.len
|
||||||
|
if length == max_value(usize) {
|
||||||
|
return .out_of_memory
|
||||||
|
}
|
||||||
|
try reserve(T, list, length + 1)
|
||||||
|
list.items = list.items.ptr[..length + 1]
|
||||||
|
list.items[length] = value
|
||||||
|
return _
|
||||||
|
}
|
||||||
|
|
||||||
|
clear func($T type, list @mut ArrayList(T)) void {
|
||||||
|
list.items = list.items.ptr[..0]
|
||||||
|
}
|
||||||
@@ -34,6 +34,10 @@ _empty_slice func($T type, count usize) []mut T {
|
|||||||
return pointer[..count]
|
return pointer[..count]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
empty func($T type) []mut T {
|
||||||
|
return _empty_slice(T, 0)
|
||||||
|
}
|
||||||
|
|
||||||
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return _empty_slice(T, 0)
|
return _empty_slice(T, 0)
|
||||||
@@ -55,6 +59,43 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
|||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError {
|
||||||
|
if new_count == memory.len {
|
||||||
|
return memory
|
||||||
|
}
|
||||||
|
if new_count == 0 {
|
||||||
|
free(T, allocator, memory)
|
||||||
|
return _empty_slice(T, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
element_size usize :: size_of(T)
|
||||||
|
if element_size == 0 {
|
||||||
|
return _empty_slice(T, new_count)
|
||||||
|
}
|
||||||
|
if new_count > max_value(usize) / element_size {
|
||||||
|
return .out_of_memory
|
||||||
|
}
|
||||||
|
|
||||||
|
old_memory ?*mut u8 = none
|
||||||
|
old_size usize = 0
|
||||||
|
if memory.len != 0 {
|
||||||
|
old_memory = ptr_cast(u8, memory.ptr)
|
||||||
|
old_size = memory.len * element_size
|
||||||
|
}
|
||||||
|
resized ?*mut u8 = raw_realloc(
|
||||||
|
allocator,
|
||||||
|
old_memory,
|
||||||
|
old_size,
|
||||||
|
new_count * element_size,
|
||||||
|
align_of(T),
|
||||||
|
)
|
||||||
|
if resized |bytes| {
|
||||||
|
pointer *mut T :: ptr_cast(T, bytes)
|
||||||
|
return pointer[..new_count]
|
||||||
|
}
|
||||||
|
return .out_of_memory
|
||||||
|
}
|
||||||
|
|
||||||
free func($T type, allocator Allocator, memory []mut T) void {
|
free func($T type, allocator Allocator, memory []mut T) void {
|
||||||
if memory.len != 0 and size_of(T) != 0 {
|
if memory.len != 0 and size_of(T) != 0 {
|
||||||
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T))
|
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T))
|
||||||
|
|||||||
Reference in New Issue
Block a user