Compare commits

...

3 Commits

Author SHA1 Message Date
hl-valdemar 0a424ec6d2 mem.eql (stdlib) 2026-07-13 10:22:40 +02:00
hl-valdemar c914083102 enforce leading comptime params 2026-07-12 20:31:41 +02:00
hl-valdemar 2dea7711f2 inferred leading comptime params 2026-07-12 19:00:06 +02:00
12 changed files with 872 additions and 86 deletions
+4 -2
View File
@@ -57,6 +57,8 @@ roadmap and milestone history.
- demand-monomorphized Brolang and C-ABI functions
- integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value and omitted from the runtime ABI
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
- leading comptime type/integer parameters may be omitted when uniquely recoverable from runtime argument types or the immediate expected result; explicit calls remain valid
- comptime parameters must form one leading prefix before all runtime parameters
- 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 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
@@ -75,7 +77,7 @@ roadmap and milestone history.
### standard packages
- `std/mem` 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
### compiler behavior
@@ -94,7 +96,7 @@ roadmap and milestone history.
- 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
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
- recursive type factories, type reflection, inferred type arguments, and type-producing unions/enums
- recursive type factories, type reflection, and type-producing unions/enums
- 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
- backed/C enum composition and must-consume fallible linting
+1 -1
View File
@@ -177,7 +177,7 @@ Current prototype features:
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
- Qualified imported globals and functions with package-aware symbol mangling
- 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 explicit or uniquely inferred leading comptime arguments
- 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
+35 -3
View File
@@ -754,15 +754,47 @@
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
- deferred: recursive factories, reflection, type-producing
unions/enums, pop/insert/remove/shrink/clone container operations
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.
31. generic container ergonomics (spike completed; no language change)
- type factories already provide the important half of generic structs: `ArrayList(T)` is a
cached, concrete nominal type whose layout contains `T`; no type information is carried at
runtime
- the verbosity comes from free functions repeating explicit comptime type arguments
(`deinit(i32, &values)`, `append(i32, &values, value)`), not from a missing generic data model
- do not add functions inside structs, implicit `Self`, associated lookup, or per-value runtime
type metadata for this; those features would add a second namespace/member model without
improving layout or specialization
- the smallest fitting feature is call-local inference of omitted comptime type arguments:
```
values ArrayList(i32) = arraylist.init(mem.c_allocator)
defer arraylist.deinit(&values)
try arraylist.append(&values, 42)
```
`T` comes from the expected result for `init` and from the concrete receiver argument for the
other calls
- keep functions package-scoped and keep the explicit form valid; this preserves simple name
resolution and gives ambiguous calls an escape hatch
31.5. inferred leading comptime parameters (implemented)
- a native call may omit its complete leading `$T type` / integer comptime prefix when every
value is uniquely recoverable from runtime argument types and/or the immediate expected result
- inference structurally matches direct type parameters, pointers/slices/arrays/optionals/
fallibles/functions, direct array counts, and canonical generated type-factory provenance;
forwarding/non-invertible factories keep the explicit spelling
- concrete evidence is exact; contextual numeric constants are weak evidence and are rebuilt with
the resolved parameter type before ordinary coercion
- calls are all-explicit or all-inferred (no partial prefix omission); unconstrained/conflicting
values diagnose with the explicit call as the escape hatch
- comptime parameters must form one leading prefix before every runtime parameter
- the existing specialization/HIR/LLVM ABI is unchanged; `std/mem` and `std/arraylist` now use the
inferred form where their arguments or result provide enough information
32. disallow arbitrary integer division
- take inspiration from zig
- 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`)
## A word on unchecked casts
+629 -51
View File
@@ -143,6 +143,12 @@ Generated_Type_Entry :: struct {
result: types.Type,
}
Type_Factory_Origin :: struct {
result: types.Type,
template: ast.Function_Id,
values: []Comptime_Value,
}
Checker :: struct {
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
@@ -184,6 +190,7 @@ Checker :: struct {
current_comptime_values: []Comptime_Value,
type_factories: [dynamic]Type_Factory_Entry,
generated_types: [dynamic]Generated_Type_Entry,
type_factory_origins: [dynamic]Type_Factory_Origin,
target: target.Target,
allocator: mem.Allocator,
}
@@ -1105,6 +1112,410 @@ valid_call_arity :: proc(function: ast.Function, count: int) -> bool {
return count >= len(function.params) if function.variadic else count == len(function.params)
}
Call_Argument_Mode :: enum u8 {
Invalid,
Explicit,
Inferred,
}
comptime_prefix_count :: proc(function: ast.Function) -> (int, bool) {
count := 0
for param in function.params {
if param.comptime_value {
count += 1
continue
}
break
}
for param in function.params[count:] {
if param.comptime_value {
return count, false
}
}
return count, count > 0
}
call_argument_mode :: proc(function: ast.Function, count: int) -> (Call_Argument_Mode, int) {
if valid_call_arity(function, count) {
return .Explicit, 0
}
prefix, inferable := comptime_prefix_count(function)
if inferable && !function.c_abi && !function.variadic && count == len(function.params)-prefix {
return .Inferred, prefix
}
return .Invalid, prefix
}
call_param_index :: proc(mode: Call_Argument_Mode, prefix, source_index: int) -> int {
return source_index+prefix if mode == .Inferred else source_index
}
next_runtime_call_arg :: proc(function: ast.Function, mode: Call_Argument_Mode, prefix, start: int, source_count: int) -> int {
index := start
for index < source_count {
param_index := call_param_index(mode, prefix, index)
if param_index >= len(function.params) || !function.params[param_index].comptime_value {
break
}
index += 1
}
return index
}
comptime_binding_index :: proc(function: ast.Function, prefix: int, name: symbol.Id) -> (int, bool) {
for param, index in function.params[:prefix] {
if param.name == name {
return index, true
}
}
return -1, false
}
bind_inferred_comptime :: proc(
checker: ^Checker,
function: ast.Function,
prefix: int,
values: []Comptime_Value,
bound: []bool,
name: symbol.Id,
candidate: Comptime_Value,
span: source.Span,
diagnose: bool,
) -> bool {
index, ok := comptime_binding_index(function, prefix, name)
if !ok {
return false
}
value := candidate
value.name = name
if !bound[index] {
values[index] = value
bound[index] = true
return true
}
existing := values[index]
matches := existing.kind == value.kind
if matches {
if existing.kind == .Type {
matches = types.equal(types.resolve_alias(existing.type, &checker.module.types), types.resolve_alias(value.type, &checker.module.types))
} else {
matches = existing.value == value.value && types.equal(existing.type, value.type)
}
}
if !matches && diagnose {
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("%d", value.value, allocator=checker.allocator)
source.addf(checker.diagnostics, span, "conflicting inference for comptime parameter '%s': %s and %s", symbol_text(checker, name), left, right)
if existing.kind != .Type {
delete(left, checker.allocator)
}
if value.kind != .Type {
delete(right, checker.allocator)
}
}
return matches
}
type_pattern_mentions_comptime :: proc(
checker: ^Checker,
function: ast.Function,
prefix: int,
pattern: types.Type,
depth := 0,
) -> bool {
if depth > 64 {
return false
}
item, ok := types.node(&checker.module.types, pattern)
if !ok {
return false
}
if item.qualifier == 0 && item.name != 0 {
if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found {
return true
}
}
if item.kind == .Array && item.unresolved_count {
expr_id := ast.Expr_Id(item.count_expr)
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
expr := checker.ast_module.exprs[expr_id]
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
if _, found := comptime_binding_index(function, prefix, expr.name); found {
return true
}
}
}
}
if item.kind == .Type_Call {
expr_id := ast.Expr_Id(item.count_expr)
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
for arg_id in checker.ast_module.exprs[expr_id].args {
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
continue
}
arg := checker.ast_module.exprs[arg_id]
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
if _, found := comptime_binding_index(function, prefix, arg.name); found {
return true
}
}
}
}
}
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1) {
return true
}
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1) {
return true
}
if item.kind == .Function {
for field in types.params_for(&checker.module.types, pattern) {
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1) {
return true
}
}
}
return false
}
match_inferred_type_pattern :: proc(
checker: ^Checker,
function: ast.Function,
prefix: int,
pattern, actual: types.Type,
values: []Comptime_Value,
bound: []bool,
span: source.Span,
diagnose: bool,
depth := 0,
) -> bool {
if depth > 64 || !types.is_valid(actual) {
return false
}
store := &checker.module.types
actual_type := types.resolve_alias(actual, store)
pattern_item, pattern_ok := types.node(store, pattern)
if pattern_ok && pattern_item.qualifier == 0 && pattern_item.name != 0 {
name := symbol.Id(pattern_item.name)
if index, is_binding := comptime_binding_index(function, prefix, name); is_binding &&
is_comptime_type_param(checker, function.params[index]) {
return bind_inferred_comptime(
checker, function, prefix, values, bound, name,
Comptime_Value{type=actual_type, kind=.Type}, span, diagnose,
)
}
}
if !pattern_ok {
return types.equal(pattern, actual_type)
}
if pattern_item.kind == .Type_Call {
expr_id := ast.Expr_Id(pattern_item.count_expr)
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return false
}
expr := checker.ast_module.exprs[expr_id]
target_pkg, available := expr_package(checker, expr, function.pkg, function.file, true)
if !available {
return false
}
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, function.file))
origin: Type_Factory_Origin
found := false
for candidate in checker.type_factory_origins {
if candidate.template == template && types.equal(candidate.result, actual_type) {
origin = candidate
found = true
break
}
}
if !found || len(expr.args) != len(origin.values) {
return false
}
matched := true
for arg_id, index in expr.args {
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
matched = false
continue
}
arg := checker.ast_module.exprs[arg_id]
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
if _, is_binding := comptime_binding_index(function, prefix, arg.name); is_binding {
matched = bind_inferred_comptime(
checker, function, prefix, values, bound, arg.name,
origin.values[index], span, diagnose,
) && matched
}
}
}
return matched
}
actual_item, actual_ok := types.node(store, actual_type)
if pattern_item.kind == .Slice {
actual_pointer, actual_array, array_pointer_ok := types.array_pointer(actual_type, store)
if array_pointer_ok {
mutable := actual_pointer.mutable && actual_array.mutable
if pattern_item.mutable && !mutable ||
pattern_item.has_sentinel && (!actual_array.has_sentinel || pattern_item.sentinel != actual_array.sentinel) {
return false
}
return match_inferred_type_pattern(
checker, function, prefix, pattern_item.child, actual_array.child,
values, bound, span, diagnose, depth+1,
)
}
}
if !actual_ok || pattern_item.kind != actual_item.kind {
resolved := type_from_syntax(checker, pattern, function.pkg, function.file)
return types.is_valid(resolved) && types.equal(types.resolve_alias(resolved, store), actual_type)
}
if pattern_item.many != actual_item.many || pattern_item.has_sentinel != actual_item.has_sentinel ||
pattern_item.has_sentinel && pattern_item.sentinel != actual_item.sentinel {
return false
}
matched := true
if pattern_item.kind == .Array {
if pattern_item.unresolved_count {
count_expr := ast.Expr_Id(pattern_item.count_expr)
if count_expr != ast.INVALID_EXPR && int(count_expr) < len(checker.ast_module.exprs) {
expr := checker.ast_module.exprs[count_expr]
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
if binding_index, is_binding := comptime_binding_index(function, prefix, expr.name); is_binding &&
!is_comptime_type_param(checker, function.params[binding_index]) {
matched = bind_inferred_comptime(
checker, function, prefix, values, bound, expr.name,
Comptime_Value{type=values[binding_index].type, value=i128(actual_item.count), kind=.Integer},
span, diagnose,
) && matched
}
}
}
} else if pattern_item.count != actual_item.count {
matched = false
}
}
if types.is_valid(pattern_item.child) {
matched = match_inferred_type_pattern(
checker, function, prefix, pattern_item.child, actual_item.child,
values, bound, span, diagnose, depth+1,
) && matched
}
if types.is_valid(pattern_item.extra) {
matched = match_inferred_type_pattern(
checker, function, prefix, pattern_item.extra, actual_item.extra,
values, bound, span, diagnose, depth+1,
) && matched
}
if pattern_item.kind == .Function {
pattern_params := types.params_for(store, pattern)
actual_params := types.params_for(store, actual_type)
if len(pattern_params) != len(actual_params) || pattern_item.c_abi != actual_item.c_abi || pattern_item.variadic != actual_item.variadic {
return false
}
for field, index in pattern_params {
matched = match_inferred_type_pattern(
checker, function, prefix, field.type, actual_params[index].type,
values, bound, span, diagnose, depth+1,
) && matched
}
}
return matched
}
infer_call_comptime_values :: proc(
checker: ^Checker,
function: ast.Function,
prefix: int,
args: []ast.Expr_Id,
actual_args: []types.Type,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
diagnose := false,
) -> ([]Comptime_Value, bool) {
values := make([]Comptime_Value, prefix, checker.allocator)
bound := make([]bool, prefix, checker.allocator)
defer delete(bound, checker.allocator)
for param, index in function.params[:prefix] {
values[index].name = param.name
if is_comptime_type_param(checker, param) {
values[index].kind = .Type
} else {
values[index].kind = .Integer
values[index].type = type_from_syntax(checker, param.type, function.pkg, function.file)
}
}
matched := true
if is_runtime_type(checker, expected) {
if types.is_valid(function.error) && types.kind(expected, &checker.module.types) == .Fallible {
matched = match_inferred_type_pattern(
checker, function, prefix, function.result,
types.fallible_success(expected, &checker.module.types), values, bound,
source.Span{}, diagnose,
) && matched
matched = match_inferred_type_pattern(
checker, function, prefix, function.error,
types.fallible_error(expected, &checker.module.types), values, bound,
source.Span{}, diagnose,
) && matched
} else if type_pattern_mentions_comptime(checker, function, prefix, function.result) {
matched = match_inferred_type_pattern(
checker, function, prefix, function.result, expected, values, bound,
source.Span{}, diagnose,
) && matched
}
}
// Concrete arguments bind first. Numeric constants are contextual and therefore
// only contribute their default type after stronger evidence has had a chance.
weak_passes := [2]bool{false, true}
for weak in weak_passes {
for arg_id, source_index in args {
param_index := prefix+source_index
if param_index >= len(function.params) || param_index >= len(actual_args) {
continue
}
is_weak := is_numeric_constant_expr(checker, arg_id)
if is_weak != weak {
continue
}
if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
continue
}
if weak {
if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok &&
item.qualifier == 0 && item.name != 0 {
if binding_index, is_binding := comptime_binding_index(function, prefix, symbol.Id(item.name));
is_binding && bound[binding_index] {
continue
}
}
}
matched = match_inferred_type_pattern(
checker, function, prefix, function.params[param_index].type,
actual_args[param_index], values, bound,
checker.ast_module.exprs[arg_id].span, diagnose,
) && matched
}
}
for param, index in function.params[:prefix] {
if bound[index] {
continue
}
matched = false
if diagnose {
source.addf(
checker.diagnostics, param.span,
"cannot infer comptime parameter '%s'; pass it explicitly",
symbol_text(checker, param.name),
)
}
}
if !matched {
delete(values, checker.allocator)
return nil, false
}
return values, true
}
call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int) -> types.Type {
if index < 0 || index >= len(function.params) {
return types.INVALID
@@ -1112,6 +1523,14 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
if is_comptime_type_param(checker, function.params[index]) {
return types.INVALID
}
for param in function.params {
if !param.comptime_value {
continue
}
if _, ok := current_comptime_value(checker, param.name); !ok {
return types.INVALID
}
}
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
// A `float` param defaults to f64 so an integer-literal argument builds as a
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
@@ -1273,6 +1692,31 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
ct_state_destroy(&state)
checker.type_factories[entry_index].result = result
checker.type_factories[entry_index].resolving = false
if types.is_valid(result) {
generated := false
for entry in checker.generated_types {
if types.equal(entry.result, result) {
generated = true
break
}
}
if generated {
has_origin := false
for origin in checker.type_factory_origins {
if types.equal(origin.result, result) {
has_origin = true
break
}
}
if !has_origin {
append(&checker.type_factory_origins, Type_Factory_Origin{
result=result,
template=template,
values=clone_comptime_values(values, checker.allocator),
})
}
}
}
return result
}
@@ -1630,8 +2074,25 @@ validate_declarations :: proc(checker: ^Checker) {
has_comptime := function_has_comptime_params(function)
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
seen_runtime_param := false
comptime_order_reported := false
for param in function.params {
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
if param.comptime_value {
if seen_runtime_param && !comptime_order_reported {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
param.span,
"comptime parameters must form a leading parameter prefix",
)
comptime_order_reported = true
}
} else {
seen_runtime_param = true
}
param_type := types.INVALID
if param.comptime_value || !has_comptime {
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
}
if param.comptime_value {
if function.c_abi {
checker.template_diagnostics[function_id] = source.add(
@@ -2078,9 +2539,12 @@ mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec
Infer_Frame :: struct {
expr: ast.Expr_Id,
expected: types.Type,
stage: u8,
left: types.Type,
arg_index: int,
arg_mode: Call_Argument_Mode,
prefix: int,
args: []types.Type,
template: ast.Function_Id,
}
@@ -2093,11 +2557,12 @@ infer_nested_expr :: proc(
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
local_types: []types.Type = nil,
expected := types.INVALID,
) -> types.Type {
outer := checker.infer_stack
checker.infer_stack = nil
checker.infer_stack.allocator = checker.allocator
result := infer_expr(checker, expr_id, locals, pkg, file, demanded, local_types)
result := infer_expr(checker, expr_id, locals, pkg, file, demanded, local_types, expected)
delete(checker.infer_stack)
checker.infer_stack = outer
return result
@@ -2111,6 +2576,7 @@ infer_compound_expr :: proc(
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
local_types: []types.Type = nil,
expected := types.INVALID,
) -> types.Type {
store := &checker.module.types
#partial switch expr.kind {
@@ -2224,10 +2690,12 @@ infer_compound_expr :: proc(
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID
case .Try:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
return types.fallible_success(value, store) if types.kind(value, store) == .Fallible else types.INVALID
case .Catch:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
success := types.fallible_success(value, store)
error_type := types.fallible_error(value, store)
if expr.right != ast.INVALID_EXPR {
@@ -2273,6 +2741,7 @@ infer_expr :: proc(
file := ast.File_Id(0),
demanded: ^[dynamic]Spec_Id = nil,
local_types: []types.Type = nil,
expected := types.INVALID,
) -> types.Type {
stack := checker.infer_stack
clear_dynamic_array(&stack)
@@ -2283,7 +2752,7 @@ infer_expr :: proc(
clear_dynamic_array(&stack)
checker.infer_stack = stack
}
append(&stack, Infer_Frame{expr=expr_id, template=ast.INVALID_FUNCTION})
append(&stack, Infer_Frame{expr=expr_id, expected=expected, template=ast.INVALID_FUNCTION})
last := types.INVALID
for len(stack) > 0 {
@@ -2330,7 +2799,7 @@ infer_expr :: proc(
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
.Comptime, .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
_ = pop(&stack)
case .Function_Literal:
template := ast.Function_Id(u32(expr.integer))
@@ -2559,11 +3028,26 @@ infer_expr :: proc(
_ = pop(&stack)
continue
}
function := checker.ast_module.functions[template]
mode, prefix := call_argument_mode(function, len(expr.args))
if mode == .Invalid {
last = types.INVALID
_ = pop(&stack)
continue
}
stack[frame_index].template = template
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].arg_mode = mode
stack[frame_index].prefix = prefix
stack[frame_index].args = make([]types.Type, len(function.params), checker.allocator)
stack[frame_index].arg_index = next_runtime_call_arg(function, mode, prefix, 0, len(expr.args))
stack[frame_index].stage = 3
if len(expr.args) > 0 {
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
if stack[frame_index].arg_index < len(expr.args) {
param_index := call_param_index(mode, prefix, stack[frame_index].arg_index)
arg_expected := call_arg_expected(checker, function, param_index)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Infer_Frame{expr=expr.args[stack[frame_index].arg_index], expected=arg_expected, template=ast.INVALID_FUNCTION})
}
}
continue
@@ -2616,37 +3100,62 @@ infer_expr :: proc(
}
if frame.stage == 3 {
if frame.arg_index < len(expr.args) {
stack[frame_index].args[frame.arg_index] = last
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION})
param_index := call_param_index(frame.arg_mode, frame.prefix, frame.arg_index)
if param_index < len(stack[frame_index].args) {
stack[frame_index].args[param_index] = last
}
next := next_runtime_call_arg(
checker.ast_module.functions[frame.template], frame.arg_mode, frame.prefix,
frame.arg_index+1, len(expr.args),
)
stack[frame_index].arg_index = next
if next < len(expr.args) {
next_param := call_param_index(frame.arg_mode, frame.prefix, next)
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next_param)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Infer_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
continue
}
}
function := checker.ast_module.functions[frame.template]
// A bare-name argument passed to a concrete (non-constraint) parameter pushes
// that parameter type back onto the argument's slot, so an open constant adopts
// it (e.g. `take_u16(a)` resolves `a` to u16). Constraint params have no single
// type to demand; the callee's result flowing back is milestone 14.5.
for arg_index in 0..<len(expr.args) {
record_demand(checker, expr.args[arg_index], call_arg_expected(checker, function, arg_index), locals, local_types, pkg, file)
}
// Deferred defaulting leaves an undemanded open constant typeless; give such an
// argument its default so the call can still monomorphize (the default feeds only
// the spec arg vector, not a demand).
for arg_index in 0..<len(expr.args) {
if !is_runtime_type(checker, stack[frame_index].args[arg_index]) {
fallback := open_const_default_type(checker, expr.args[arg_index], locals, pkg, file)
for source_index in 0..<len(expr.args) {
param_index := call_param_index(frame.arg_mode, frame.prefix, source_index)
if param_index < len(stack[frame_index].args) && !is_runtime_type(checker, stack[frame_index].args[param_index]) {
fallback := open_const_default_type(checker, expr.args[source_index], locals, pkg, file)
if is_runtime_type(checker, fallback) {
stack[frame_index].args[arg_index] = fallback
stack[frame_index].args[param_index] = fallback
}
}
}
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file)
comptime_values: []Comptime_Value
comptime_ok := false
if frame.arg_mode == .Inferred {
comptime_values, comptime_ok = infer_call_comptime_values(
checker, function, frame.prefix, expr.args, stack[frame_index].args,
frame.expected, pkg, file,
)
} else {
comptime_values, comptime_ok = collect_comptime_values(checker, function, expr.args, pkg, file)
}
defer delete(comptime_values, checker.allocator)
if valid_call_arity(function, len(expr.args)) &&
comptime_ok &&
if comptime_ok &&
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = comptime_values
for source_index in 0..<len(expr.args) {
param_index := call_param_index(frame.arg_mode, frame.prefix, source_index)
if param_index >= len(function.params) || function.params[param_index].comptime_value {
continue
}
demand := call_arg_expected(checker, function, param_index)
record_demand(checker, expr.args[source_index], demand, locals, local_types, pkg, file)
}
checker.current_comptime_values = previous_comptime
spec := INVALID_SPEC
if demanded == nil {
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
@@ -2657,7 +3166,10 @@ infer_expr :: proc(
if spec != INVALID_SPEC {
last = checker.specs[spec].result
} else {
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = comptime_values
declared := function_channel_type(checker, function)
checker.current_comptime_values = previous_comptime
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
}
} else {
@@ -2810,7 +3322,8 @@ infer_statements :: proc(
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
value_type := types.INVALID
if !is_undefined_expr(checker, statement.expr) {
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
expected := declared_local if is_runtime_type(checker, declared_local) else types.INVALID
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected)
declared_local = resolve_inferred_array_from_type(checker, declared_local, value_type)
}
if is_runtime_type(checker, declared_local) && !has_inferred_array_count(checker, declared_local) {
@@ -2863,14 +3376,24 @@ infer_statements :: proc(
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
continue
}
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
expected_assignment := types.INVALID
if statement.target != ast.INVALID_EXPR {
expected_assignment = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
} else if statement.name != checker.sink_symbol {
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok {
expected_assignment = locals^[local_index].type
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
expected_assignment = checker.global_types[global]
}
}
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected_assignment)
// Only push the target's type back onto a bare-name RHS (e.g. `x += speed`):
// pushing through an arithmetic RHS would feed the target's (often provisional)
// type onto open-constant operands and poison their family. Operands of an
// arithmetic RHS resolve from their own authoritative uses.
rhs_is_arith := is_arith_kind(checker.ast_module.exprs[statement.expr].kind)
if statement.target != ast.INVALID_EXPR {
target_type := infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
target_type := expected_assignment
target_expr := checker.ast_module.exprs[statement.target]
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
@@ -2903,7 +3426,7 @@ infer_statements :: proc(
}
case .Return:
if statement.expr != ast.INVALID_EXPR {
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, result_hint)
if is_runtime_type(checker, result_hint) {
_ = record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
expr := checker.ast_module.exprs[statement.expr]
@@ -3505,12 +4028,13 @@ infer_all :: proc(checker: ^Checker) {
if global.external {
continue
}
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
declared := resolve_inferred_array(
checker,
type_from_syntax(checker, global.type, global.pkg, global.file),
global.expr,
)
expected := declared if is_runtime_type(checker, declared) else types.INVALID
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file, expected=expected)
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
resolved != declared {
if !types.equal(checker.global_types[index], resolved) {
@@ -3991,19 +4515,13 @@ Build_Expr_Frame :: struct {
stage: u8,
left: hir.Expr_Id,
arg_index: int,
arg_mode: Call_Argument_Mode,
prefix: int,
built_args: []hir.Expr_Id,
arg_types: []types.Type,
template: ast.Function_Id,
}
next_built_template_arg :: proc(checker: ^Checker, function: ast.Function, start: int) -> int {
index := start
for index < len(function.params) && is_comptime_type_param(checker, function.params[index]) {
index += 1
}
return index
}
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
return false
@@ -4714,7 +5232,8 @@ build_compound_expr :: proc(
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Try:
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
channel_type := checker.module.exprs[channel].type
success := types.fallible_success(channel_type, store)
if !types.is_valid(success) {
@@ -4747,7 +5266,8 @@ build_compound_expr :: proc(
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Catch:
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
channel_type := checker.module.exprs[channel].type
success := types.fallible_success(channel_type, store)
if !types.is_valid(success) {
@@ -5448,9 +5968,20 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
if !valid_call_arity(function, len(expr.args)) {
mode, prefix := call_argument_mode(function, len(expr.args))
if mode == .Invalid {
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
"function '%s' expects %d arguments, got %d"
if inferred_prefix, inferable := comptime_prefix_count(function); inferable && !function.c_abi && !function.variadic {
id := source.addf(
checker.diagnostics, expr.span,
"function '%s' expects %d arguments with explicit comptime parameters or %d with inferred comptime parameters, got %d",
symbol_text(checker, expr.name), len(function.params), len(function.params)-inferred_prefix, len(expr.args),
)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
id := source.addf(
checker.diagnostics,
expr.span,
@@ -5464,15 +5995,18 @@ build_expr :: proc(
continue
}
stack[frame_index].template = template
stack[frame_index].arg_mode = mode
stack[frame_index].prefix = prefix
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].arg_types = make([]types.Type, len(function.params), checker.allocator)
for &arg in stack[frame_index].built_args {
arg = hir.INVALID_EXPR
}
stack[frame_index].arg_index = next_built_template_arg(checker, function, 0)
stack[frame_index].arg_index = next_runtime_call_arg(function, mode, prefix, 0, len(expr.args))
stack[frame_index].stage = 3
if stack[frame_index].arg_index < len(expr.args) {
arg_expected := call_arg_expected(checker, function, stack[frame_index].arg_index)
param_index := call_param_index(mode, prefix, stack[frame_index].arg_index)
arg_expected := call_arg_expected(checker, function, param_index)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
@@ -5527,11 +6061,18 @@ build_expr :: proc(
if frame.stage == 3 {
if frame.arg_index < len(expr.args) {
stack[frame_index].built_args[frame.arg_index] = last
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
next := next_built_template_arg(checker, checker.ast_module.functions[frame.template], frame.arg_index+1)
param_index := call_param_index(frame.arg_mode, frame.prefix, frame.arg_index)
if param_index < len(stack[frame_index].arg_types) {
stack[frame_index].arg_types[param_index] = checker.module.exprs[last].type
}
next := next_runtime_call_arg(
checker.ast_module.functions[frame.template], frame.arg_mode, frame.prefix,
frame.arg_index+1, len(expr.args),
)
stack[frame_index].arg_index = next
if next < len(expr.args) {
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next)
next_param := call_param_index(frame.arg_mode, frame.prefix, next)
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next_param)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
@@ -5540,8 +6081,39 @@ build_expr :: proc(
}
}
function := checker.ast_module.functions[frame.template]
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file, diagnose=true)
comptime_values: []Comptime_Value
comptime_ok := false
if frame.arg_mode == .Inferred {
comptime_values, comptime_ok = infer_call_comptime_values(
checker, function, frame.prefix, expr.args, stack[frame_index].arg_types,
frame.expected, pkg, file, diagnose=true,
)
} else {
comptime_values, comptime_ok = collect_comptime_values(checker, function, expr.args, pkg, file, diagnose=true)
}
defer delete(comptime_values, checker.allocator)
if comptime_ok {
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = comptime_values
for source_index in 0..<len(expr.args) {
param_index := call_param_index(frame.arg_mode, frame.prefix, source_index)
if param_index >= len(function.params) || function.params[param_index].comptime_value ||
!is_numeric_constant_expr(checker, expr.args[source_index]) {
continue
}
expected_arg := call_arg_expected(checker, function, param_index)
if !is_runtime_type(checker, expected_arg) {
continue
}
rebuilt := build_nested_expr(
checker, expr.args[source_index], locals, global_reads, calls,
expected_arg, pkg, file,
)
stack[frame_index].built_args[source_index] = rebuilt
stack[frame_index].arg_types[param_index] = checker.module.exprs[rebuilt].type
}
checker.current_comptime_values = previous_comptime
}
arg_violation := source.INVALID_DIAGNOSTIC
if comptime_ok {
previous_comptime := checker.current_comptime_values
@@ -5604,10 +6176,11 @@ build_expr :: proc(
runtime_arg_count := runtime_count + max(0, len(source_args)-len(function.params))
runtime_args := make([]hir.Expr_Id, runtime_arg_count, checker.allocator)
runtime_index := 0
for param, source_index in function.params {
for param, param_index in function.params {
if param.comptime_value {
continue
}
source_index := param_index if frame.arg_mode == .Explicit else param_index-frame.prefix
arg := source_args[source_index]
runtime_args[runtime_index] = coerce_expr(
checker,
@@ -8977,6 +9550,7 @@ check :: proc(
checker.anon_globals.allocator = allocator
checker.type_factories.allocator = allocator
checker.generated_types.allocator = allocator
checker.type_factory_origins.allocator = allocator
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
@@ -9026,8 +9600,12 @@ check :: proc(
for entry in checker.generated_types {
delete(entry.values, allocator)
}
for origin in checker.type_factory_origins {
delete(origin.values, allocator)
}
delete(checker.type_factories)
delete(checker.generated_types)
delete(checker.type_factory_origins)
}
for function, index in ast_module.functions {
+96 -2
View File
@@ -2573,8 +2573,8 @@ main func() void {
for diagnostic in diagnostics.items {
message := diagnostic.message
found_runtime_arg = found_runtime_arg || strings.contains(message, "must be a compile-time integer expression")
found_missing = found_missing || strings.contains(message, "expects 1 arguments, got 0")
found_extra = found_extra || strings.contains(message, "expects 1 arguments, got 2")
found_missing = found_missing || strings.contains(message, "cannot infer comptime parameter 'N'")
found_extra = found_extra || strings.contains(message, "expects 1 arguments with explicit comptime parameters or 0 with inferred comptime parameters, got 2")
found_negative = found_negative || strings.contains(message, "integer constant -1 does not fit in usize")
found_range = found_range || strings.contains(message, "integer constant 300 does not fit in u8")
found_bad_type = found_bad_type || strings.contains(message, "requires a concrete integer type")
@@ -2592,6 +2592,100 @@ main func() void {
testing.expect(t, found_address)
}
@(test)
inferred_comptime_params_diagnose_ambiguous_calls :: proc(t: ^testing.T) {
text := `Ignored func($T type) type {
return i32
}
Box func($T type) type {
return struct { value T }
}
BoxAlias func($T type) type {
return Box(T)
}
conflict func($T type, left, right T) T { return left }
unknown func($T type) T { value T = undefined; return value }
partial func($T type, $N usize, value T) T { return value }
use_ignored func($T type, value Ignored(T)) i32 { return value }
use_alias func($T type, value BoxAlias(T)) T { return value.value }
main func() void {
a i32 :: 1
b u32 :: 2
_ = conflict(a, b)
_ = unknown()
_ = partial(i32, a)
_ = use_ignored(a)
box Box(i32) :: Box(i32) { value = 1 }
_ = use_alias(box)
}
`
source_file := source.Source{path="test.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)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found_conflict := false
found_unknown := false
found_partial := false
unrecoverable := 0
for diagnostic in diagnostics.items {
message := diagnostic.message
found_conflict = found_conflict || strings.contains(message, "conflicting inference for comptime parameter 'T': i32 and u32")
found_unknown = found_unknown || strings.contains(message, "cannot infer comptime parameter 'T'")
found_partial = found_partial || strings.contains(message, "expects 3 arguments with explicit comptime parameters or 1 with inferred comptime parameters, got 2")
if strings.contains(message, "cannot infer comptime parameter 'T'") {
unrecoverable += 1
}
}
testing.expect(t, found_conflict)
testing.expect(t, found_unknown)
testing.expect(t, found_partial)
testing.expect(t, unrecoverable >= 3)
}
@(test)
comptime_params_must_form_leading_prefix :: proc(t: ^testing.T) {
text := `valid func($T type, $N usize, value T) [N]T {
result [N]T = undefined
_ = value
return result
}
runtime_first func(value i32, $T type, $N usize) T { return value }
split func($T type, value T, $N usize) [N]T {
result [N]T = undefined
_ = value
return result
}
main func() void {}
`
source_file := source.Source{path="test.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)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
count := 0
for diagnostic in diagnostics.items {
if strings.contains(diagnostic.message, "comptime parameters must form a leading parameter prefix") {
count += 1
}
}
testing.expect_value(t, count, 2)
}
@(test)
comptime_type_params_specialize_by_type_and_omit_runtime_args :: proc(t: ^testing.T) {
text := `Point :: struct {
+11 -11
View File
@@ -25,13 +25,13 @@ _fail_allocator mem.Allocator :: mem.Allocator {
_noop func() void {}
run func() i32 ! mem.AllocError {
values arraylist.ArrayList(i32) = arraylist.init(i32, mem.c_allocator)
defer arraylist.deinit(i32, &values)
values arraylist.ArrayList(i32) = arraylist.init(mem.c_allocator)
defer arraylist.deinit(&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 |_| {
arraylist.append(&values, i32(i)) catch |_| {
return .out_of_memory
}
}
@@ -40,34 +40,34 @@ run func() i32 ! mem.AllocError {
values.items[3] = 33
if (values.items[3] != 33) return 4
arraylist.reserve(i32, &values, 50) catch |_| {
arraylist.reserve(&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)
arraylist.clear(&values)
if (values.items.len != 0 or values.capacity != capacity) return 6
arraylist.append(i32, &values, 7) catch |_| {
arraylist.append(&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)
empty_values arraylist.ArrayList([0]u8) = arraylist.init(mem.c_allocator)
defer arraylist.deinit(&empty_values)
zero [0]u8 :: []
arraylist.append([0]u8, &empty_values, zero) catch |_| {
arraylist.append(&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 |_| {
arraylist.append(&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)
arraylist.deinit(&failed)
return 0
}
@@ -19,10 +19,37 @@ buffer func($T type, $N usize, value T) [N]T {
return data
}
zero func($T type) T {
value T = undefined
return value
}
array_len func($T type, $N usize, values [N]T) usize {
return values.len
}
Fixed func($T type, $N usize) type {
return struct {
values [N]T
}
}
fixed_len func($T type, $N usize, value @Fixed(T, N)) usize {
return value.values.len
}
take_i32 func(value i32) i32 {
return value
}
return_zero func() i32 {
return zero()
}
main func() i32 {
a i32 :: 42
b i32 :: 27
if max(i32, a, b) != 42 {
if max(a, b) != 42 {
return 1
}
@@ -33,14 +60,32 @@ main func() i32 {
}
p Point :: Point { x = 11 }
q Point :: id(Point, p)
q Point :: id(p)
if q.x != 11 {
return 3
}
bytes [_]u8 :: buffer(u8, 4, small_a)
bytes [4]u8 :: buffer(small_a)
if bytes.len != 4 {
return 4
}
if array_len(bytes) != 4 {
return 5
}
zero_value i32 :: zero()
_ = zero_value
literal :: id(7)
if literal != 7 {
return 6
}
fixed Fixed(u8, 3) :: Fixed(u8, 3) { values = [1, 2, 3] }
if fixed_len(&fixed) != 3 {
return 7
}
assigned i32 = 1
assigned = zero()
_ = assigned
_ = take_i32(zero())
_ = return_zero()
return 0
}
+21
View File
@@ -1,4 +1,25 @@
mem :: import "@std/mem"
eql_test func() i32 {
if (mem.eql("bro", "bro") == false) return 21
if mem.eql("bro", "bra") return 22
if mem.eql("bro", "brolang") return 23
empty []u8 :: ""
if (mem.eql(empty, "") == false) return 24
left [3]i32 :: [1, 2, 3]
same [3]i32 :: [1, 2, 3]
different [3]i32 :: [1, 2, 4]
if (mem.eql(left[..], same[..]) == false) return 25
if mem.eql(left[..], different[..]) return 26
return 0
}
main func() i32 {
eql_result i32 :: eql_test()
if (eql_result != 0) return eql_result
typed_result i32 :: typed_allocator_test()
if (typed_result != 0) return typed_result
@@ -23,7 +23,7 @@ task_list_init func(allocator mem.Allocator) TaskList {
alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
fallback [1]mut i32 = undefined
failed bool = false
values []mut i32 = mem.alloc(i32, allocator, count) catch |_| {
values []mut i32 = mem.alloc(allocator, count) catch |_| {
failed = true
yield (&fallback).ptr[..0]
}
@@ -33,7 +33,7 @@ alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
free_i32s func(allocator mem.Allocator, values ?[]mut i32) void {
if values |slice| {
mem.free(i32, allocator, slice)
mem.free(allocator, slice)
}
}
@@ -49,12 +49,12 @@ typed_allocator_test func() i32 {
i32_fallback [1]mut i32 = undefined
empty_failed bool = false
empty []mut i32 = mem.alloc(i32, first_allocator, 0) catch |_| {
empty []mut i32 = mem.alloc(first_allocator, 0) catch |_| {
empty_failed = true
yield (&i32_fallback).ptr[..0]
}
if (empty_failed or empty.len != 0 or first_calls[0] != 3) return 34
mem.free(i32, first_allocator, empty)
mem.free(first_allocator, empty)
if (first_calls[0] != 3) return 33
zero_sized_fallback [1]mut [0]u8 = undefined
@@ -70,7 +70,7 @@ typed_allocator_test func() i32 {
u64_fallback [1]mut u64 = undefined
overflow_fallback_failed bool = false
overflow_fallback []mut u64 = mem.alloc(u64, first_allocator, 0) catch |_| {
overflow_fallback []mut u64 = mem.alloc(first_allocator, 0) catch |_| {
overflow_fallback_failed = true
yield (&u64_fallback).ptr[..0]
}
@@ -83,20 +83,20 @@ typed_allocator_test func() i32 {
if (overflow_failed == false or first_calls[0] != 3) return 40
typed_failed bool = false
typed []mut i32 = mem.alloc(i32, mem.c_allocator, 4) catch |_| {
typed []mut i32 = mem.alloc(mem.c_allocator, 4) catch |_| {
typed_failed = true
yield (&i32_fallback).ptr[..0]
}
if (typed_failed) return 38
defer mem.free(i32, mem.c_allocator, typed)
defer mem.free(mem.c_allocator, typed)
typed[0] = 10
typed[3] = 20
if (typed[0] + typed[3] != 30) return 39
typed = mem.realloc(i32, mem.c_allocator, typed, 8) catch |_| {
typed = mem.realloc(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 |_| {
typed = mem.realloc(mem.c_allocator, typed, 2) catch |_| {
return 43
}
if (typed.len != 2 or typed[0] != 10) return 44
+3 -3
View File
@@ -18,7 +18,7 @@ init func($T type, allocator mem.Allocator) ArrayList(T) {
deinit func($T type, list @mut ArrayList(T)) void {
allocation []mut T :: list.items.ptr[..list.capacity]
mem.free(T, list.allocator, allocation)
mem.free(list.allocator, allocation)
list.items = mem.empty(T)
list.capacity = 0
}
@@ -43,7 +43,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
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 |_| {
grown []mut T :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| {
return .out_of_memory
}
list.items = grown.ptr[..length]
@@ -56,7 +56,7 @@ append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
if length == max_value(usize) {
return .out_of_memory
}
try reserve(T, list, length + 1)
try reserve(list, length + 1)
list.items = list.items.ptr[..length + 1]
list.items[length] = value
return _
+15 -1
View File
@@ -27,6 +27,20 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
allocator.vtable.free(allocator.context, memory, size, alignment)
}
eql func($T type, left, right []T) bool {
if left.len != right.len {
return false
}
i usize = 0
while i < left.len : i += 1 {
if left[i] != right[i] {
return false
}
}
return true
}
_empty_storage [1]mut u64 = [0]
_empty_slice func($T type, count usize) []mut T {
@@ -64,7 +78,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
return memory
}
if new_count == 0 {
free(T, allocator, memory)
free(allocator, memory)
return _empty_slice(T, 0)
}