diff --git a/LANGUAGE.md b/LANGUAGE.md index 82706bc..d7639fe 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -57,6 +57,7 @@ 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 - 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 @@ -94,7 +95,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 diff --git a/README.md b/README.md index 3c9d90a..340a95e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/TODO.md b/TODO.md index d4e9b29..9653ca4 100644 --- a/TODO.md +++ b/TODO.md @@ -754,10 +754,41 @@ 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 + - 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 diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index f6cfb4b..cb3435f 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -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,396 @@ 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 !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 +1509,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 +1678,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 } @@ -1631,7 +2061,10 @@ validate_declarations :: proc(checker: ^Checker) { locals: [dynamic]symbol.Id locals.allocator = checker.allocator for param in function.params { - param_type := type_from_syntax(checker, param.type, function.pkg, function.file) + 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 +2511,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 +2529,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 +2548,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 +2662,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 +2713,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 +2724,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 +2771,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 +3000,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 +3072,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(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 +3138,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 +3294,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 +3348,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 +3398,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 +4000,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 +4487,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 +5204,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 +5238,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 +5940,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 +5967,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 +6033,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 +6053,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(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 +6148,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 +9522,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 +9572,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 { diff --git a/compiler_tests.odin b/compiler_tests.odin index 252ddcf..e31c747 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -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,64 @@ 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_type_params_specialize_by_type_and_omit_runtime_args :: proc(t: ^testing.T) { text := `Point :: struct { diff --git a/examples/programs/arraylist/main.bro b/examples/programs/arraylist/main.bro index 395808b..38887b7 100644 --- a/examples/programs/arraylist/main.bro +++ b/examples/programs/arraylist/main.bro @@ -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 } diff --git a/examples/programs/comptime_type_params/main.bro b/examples/programs/comptime_type_params/main.bro index 5b87012..7cb431a 100644 --- a/examples/programs/comptime_type_params/main.bro +++ b/examples/programs/comptime_type_params/main.bro @@ -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 } diff --git a/examples/programs/mem_allocator/task_list.bro b/examples/programs/mem_allocator/task_list.bro index 896ea58..622e493 100644 --- a/examples/programs/mem_allocator/task_list.bro +++ b/examples/programs/mem_allocator/task_list.bro @@ -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) } } diff --git a/examples/programs/mem_allocator/typed_alloc.bro b/examples/programs/mem_allocator/typed_alloc.bro index cc9cbe4..2005272 100644 --- a/examples/programs/mem_allocator/typed_alloc.bro +++ b/examples/programs/mem_allocator/typed_alloc.bro @@ -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 diff --git a/std/arraylist/arraylist.bro b/std/arraylist/arraylist.bro index ccbd245..9ba3513 100644 --- a/std/arraylist/arraylist.bro +++ b/std/arraylist/arraylist.bro @@ -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 _ diff --git a/std/mem/mem.bro b/std/mem/mem.bro index 1d6c22f..2cc32c3 100644 --- a/std/mem/mem.bro +++ b/std/mem/mem.bro @@ -64,7 +64,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) }