From b94687c30a902cae8dff4e9387a09056c97e323e Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Thu, 2 Jul 2026 20:30:02 +0200 Subject: [PATCH] comptime type params --- LANGUAGE.md | 3 +- README.md | 2 +- TODO.md | 13 +- compiler/ast/ast.odin | 1 + compiler/checker/checker.odin | 180 +++++++++++++++--- compiler/parser/parser.odin | 20 +- compiler_tests.odin | 159 ++++++++++++++++ .../programs/comptime_type_params/main.bro | 46 +++++ 8 files changed, 393 insertions(+), 31 deletions(-) create mode 100644 examples/programs/comptime_type_params/main.bro diff --git a/LANGUAGE.md b/LANGUAGE.md index 3438e5b..6f8e8ec 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -53,6 +53,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 - bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names - concrete-only C signatures, C variadic declarations/calls, and C default argument promotions - Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns @@ -80,7 +81,7 @@ roadmap and milestone history. ## PLANNED / DEFERRED -- comptime type parameters and comptime-evaluable functions +- comptime-evaluable functions - tuples and native Brolang variadic functions - 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 diff --git a/README.md b/README.md index 2980c2f..0932411 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,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 comptime value parameters (`func($N usize) [N]u8`) specialized by value +- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by comptime argument - Bodyless concrete C function declarations with exact external symbol names - Bodyless manual and imported C variadic declarations with default argument promotions - Ordered linking of additional C sources, objects, archives, and libraries diff --git a/TODO.md b/TODO.md index d981dcf..2b44e42 100644 --- a/TODO.md +++ b/TODO.md @@ -640,10 +640,17 @@ - v1 intentionally supports integer values only; no comptime branch pruning or user-function execution -27.5 comptime type parameters (deferred) - - planned shape: `max func($T type, a, b T) T` +27.5 comptime type parameters (implemented; v1) + - `$T type` marks an explicit comptime type parameter in a normal `func` signature: + `max func($T type, a, b T) T` + - callers pass the type explicitly as an ordinary comptime argument (`max(i32, a, b)`); + the type argument specializes the function and is omitted from the runtime ABI + - inside the specialization, `T` is visible in parameter, result, local, array, pointer, + slice, and fallible type syntax + - v1 intentionally keeps `type` contextual to comptime parameter declarations; no + inferred type parameters, first-class type values, or comptime execution -27.6 comptime-evaluable constants/functions (deferred) +27.6 comptime-evaluable constants/functions - planned shape: `$x :: 32` and `$sum func(a, b int) int { ... }` 28. brolang build system (requires comptime execution) diff --git a/compiler/ast/ast.odin b/compiler/ast/ast.odin index e7c3078..385e6df 100644 --- a/compiler/ast/ast.odin +++ b/compiler/ast/ast.odin @@ -72,6 +72,7 @@ Expr_Kind :: enum u8 { Array, None, Undefined, + Type, Name, Enum_Literal, Address, diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 59130b6..a2d78cb 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -33,10 +33,16 @@ Spec :: struct { hir_id: hir.Function_Id, } +Comptime_Value_Kind :: enum u8 { + Integer, + Type, +} + Comptime_Value :: struct { name: symbol.Id, type: types.Type, value: i128, + kind: Comptime_Value_Kind, } Infer_Local :: struct { @@ -174,6 +180,7 @@ Checker :: struct { cycle_stack: [dynamic]Cycle_Frame, main_symbol: symbol.Id, sink_symbol: symbol.Id, + type_symbol: symbol.Id, current_result: types.Type, current_build_ctx: ^Build_Ctx, current_comptime_values: []Comptime_Value, @@ -208,6 +215,22 @@ current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_ return find_comptime_value(checker.current_comptime_values, name) } +current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type, bool) { + if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type { + return value.type, true + } + return types.INVALID, false +} + +is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool { + item, ok := types.node(&checker.module.types, value) + return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0 +} + +is_comptime_type_param :: proc(checker: ^Checker, param: ast.Param) -> bool { + return param.comptime_value && is_type_metatype_syntax(checker, param.type) +} + function_has_comptime_params :: proc(function: ast.Function) -> bool { for param in function.params { if param.comptime_value { @@ -243,7 +266,8 @@ comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool { } for value, index in left { other := right[index] - if value.name != other.name || !types.equal(value.type, other.type) || value.value != other.value { + if value.name != other.name || value.kind != other.kind || !types.equal(value.type, other.type) || + (value.kind == .Integer && value.value != other.value) { return false } } @@ -376,7 +400,9 @@ eval_integer_constant_in_context :: proc( case .Name: if !symbol.is_valid(expr.qualifier) { if value, ok := current_comptime_value(checker, expr.name); ok { - return Constant{kind = .Value, value = value.value} + if value.kind == .Integer { + return Constant{kind = .Value, value = value.value} + } } } target_pkg, available := expr_package(checker, expr, pkg, file, false) @@ -469,6 +495,11 @@ type_from_syntax :: proc( if !ok { return value } + if item.qualifier == 0 && item.name != 0 { + if actual, ok := current_comptime_type(checker, symbol.Id(item.name)); ok { + return actual + } + } store := &checker.module.types changed := false #partial switch item.kind { @@ -870,6 +901,9 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int) if index < 0 || index >= len(function.params) { return types.INVALID } + if is_comptime_type_param(checker, function.params[index]) { + 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. @@ -880,6 +914,37 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int) return declared } +resolve_type_argument :: proc( + checker: ^Checker, + expr_id: ast.Expr_Id, + pkg: ast.Package_Id, + file: ast.File_Id, +) -> (types.Type, bool) { + if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { + return types.INVALID, false + } + expr := checker.ast_module.exprs[expr_id] + #partial switch expr.kind { + case .Type: + resolved := type_from_syntax(checker, expr.type, pkg, file) + return resolved, types.is_valid(resolved) + case .Name: + if !symbol.is_valid(expr.qualifier) { + if actual, ok := current_comptime_type(checker, expr.name); ok { + return actual, true + } + } + target_pkg, available := expr_package(checker, expr, pkg, file) + if !available { + return types.INVALID, false + } + value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name)) + value = types.resolve_alias(value, &checker.module.types) + return value, types.is_valid(value) + } + return types.INVALID, false +} + collect_comptime_values :: proc( checker: ^Checker, function: ast.Function, @@ -902,6 +967,26 @@ collect_comptime_values :: proc( if index < len(args) && args[index] != ast.INVALID_EXPR && int(args[index]) < len(checker.ast_module.exprs) { span = checker.ast_module.exprs[args[index]].span } + if is_comptime_type_param(checker, param) { + actual, actual_ok := types.INVALID, false + if index < len(args) { + actual, actual_ok = resolve_type_argument(checker, args[index], pkg, file) + } + if !actual_ok { + if diagnose { + source.addf( + checker.diagnostics, + span, + "argument for comptime type parameter '%s' must be a type", + symbol_text(checker, param.name), + ) + } + ok = false + continue + } + append(&values, Comptime_Value{name=param.name, type=actual, kind=.Type}) + continue + } declared := type_from_syntax(checker, param.type, function.pkg, function.file) if !types.is_concrete_integer(declared) { if diagnose { @@ -1068,7 +1153,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as mark_block_imports_used(checker, expr.body, file) case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range: append(&stack, expr.left, expr.right) - case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name: + case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Type, .Name: } } } @@ -1204,7 +1289,7 @@ validate_declarations :: proc(checker: ^Checker) { "comptime parameters require 'func', not 'c_func'", ) } - if !types.is_concrete_integer(param_type) { + if !is_type_metatype_syntax(checker, param.type) && !types.is_concrete_integer(param_type) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, param.span, @@ -1855,6 +1940,9 @@ infer_expr :: proc( case .Invalid: last = types.INVALID _ = pop(&stack) + case .Type: + last = types.INVALID + _ = pop(&stack) case .Integer: last = types.I64 if expr.integer <= 0x7fff_ffff_ffff_ffff { @@ -1898,7 +1986,9 @@ infer_expr :: proc( if !types.is_valid(last) { if !symbol.is_valid(expr.qualifier) { if value, ok := current_comptime_value(checker, expr.name); ok { - last = value.type + if value.kind == .Integer { + last = value.type + } } } } @@ -3363,6 +3453,14 @@ Build_Expr_Frame :: struct { 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 @@ -4308,6 +4406,10 @@ build_expr :: proc( checker, expr, locals, global_reads, calls, frame.expected, pkg, file, ) _ = pop(&stack) + case .Type: + id := source.add(checker.diagnostics, expr.span, "type is not a runtime value") + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) case .Invalid, .Integer: last = invalid_hir_expr(checker, expr.span, expr.diagnostic) _ = pop(&stack) @@ -4363,16 +4465,26 @@ build_expr :: proc( if last == hir.INVALID_EXPR { if !symbol.is_valid(expr.qualifier) { if value, ok := current_comptime_value(checker, expr.name); ok { - expected_type := value.type - if types.is_concrete_integer(frame.expected) || types.is_float(frame.expected, checker.target) { - expected_type = frame.expected + if value.kind == .Integer { + expected_type := value.type + if types.is_concrete_integer(frame.expected) || types.is_float(frame.expected, checker.target) { + expected_type = frame.expected + } + last = build_constant_expr( + checker, + expr, + Constant{kind=.Value, value=value.value}, + expected_type, + ) + } else { + id := source.addf( + checker.diagnostics, + expr.span, + "type parameter '%s' is not a runtime value", + symbol_text(checker, expr.name), + ) + last = invalid_hir_expr(checker, expr.span, id) } - last = build_constant_expr( - checker, - expr, - Constant{kind=.Value, value=value.value}, - expected_type, - ) } } } @@ -4561,13 +4673,21 @@ build_expr :: proc( stack[frame_index].template = template 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) + 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].stage = 3 - if len(expr.args) > 0 { - arg_expected := call_arg_expected(checker, function, 0) + if stack[frame_index].arg_index < len(expr.args) { + arg_expected := call_arg_expected(checker, function, stack[frame_index].arg_index) if !is_runtime_type(checker, arg_expected) { arg_expected = types.INVALID } - append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION}) + append(&stack, Build_Expr_Frame{ + expr=expr.args[stack[frame_index].arg_index], + expected=arg_expected, + template=ast.INVALID_FUNCTION, + }) } } continue @@ -4615,9 +4735,9 @@ build_expr :: proc( 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 - stack[frame_index].arg_index += 1 - if frame.arg_index+1 < len(expr.args) { - next := frame.arg_index+1 + next := next_built_template_arg(checker, checker.ast_module.functions[frame.template], frame.arg_index+1) + stack[frame_index].arg_index = next + if next < len(expr.args) { arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next) if !is_runtime_type(checker, arg_expected) { arg_expected = types.INVALID @@ -4881,12 +5001,21 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string { } } for value in spec.comptime_values { - strings.write_string(&builder, "__cv") - if value.value < 0 { - strings.write_string(&builder, "n") - fmt.sbprintf(&builder, "%d", -value.value) + if value.kind == .Type { + strings.write_string(&builder, "__ct") + if value.type >= types.DYNAMIC_START { + fmt.sbprintf(&builder, "t%d", value.type) + } else { + strings.write_string(&builder, types.name(value.type)) + } } else { - fmt.sbprintf(&builder, "%d", value.value) + strings.write_string(&builder, "__cv") + if value.value < 0 { + strings.write_string(&builder, "n") + fmt.sbprintf(&builder, "%d", -value.value) + } else { + fmt.sbprintf(&builder, "%d", value.value) + } } } return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator) @@ -7836,6 +7965,7 @@ check :: proc( module = hir.init_module(selected, allocator), main_symbol = symbol.intern(symbols, "main"), sink_symbol = symbol.intern(symbols, "_"), + type_symbol = symbol.intern(symbols, "type"), target = selected, allocator = allocator, } diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index f101e07..60bb1ff 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -591,6 +591,17 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) { parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { tok := current(parser) #partial switch tok.kind { + case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Bool: + start := tok + target := parse_type_atom(parser) + return add_expr(parser, ast.Expr{ + kind=.Type, + span=start.span, + type=target, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) case .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64, .Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64, .Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64, @@ -601,7 +612,14 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { start := tok target := parse_type_atom(parser) if _, ok := allow(parser, .Left_Paren); !ok { - return invalid_expr(parser, current(parser).span, "expected '(' after scalar cast type") + return add_expr(parser, ast.Expr{ + kind=.Type, + span=start.span, + type=target, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) } parser.delimiter_depth += 1 skip_newlines(parser) diff --git a/compiler_tests.odin b/compiler_tests.odin index 6884381..3d8e3cb 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -204,6 +204,37 @@ main func() void {} testing.expect(t, !module.functions[0].params[1].comptime_value) } +@(test) +parser_accepts_comptime_type_params_and_builtin_type_args :: proc(t: ^testing.T) { + text := `id func($T type, value T) T { + return value +} +main func() void { + _ = id(i32, 42) +} +` + 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) + module := parser.parse(&stream, &source_file, &diagnostics) + defer ast.destroy_module(&module) + + call := module.exprs[module.statements[module.functions[1].body[0]].expr] + type_item, type_ok := types.node(&module.type_store, module.functions[0].params[0].type) + + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect(t, module.functions[0].params[0].comptime_value) + testing.expect_value(t, symbol.resolve(&symbols, module.functions[0].params[0].name), "T") + testing.expect(t, type_ok) + testing.expect_value(t, symbol.resolve(&symbols, symbol.Id(type_item.name)), "type") + testing.expect_value(t, call.kind, ast.Expr_Kind.Call) + testing.expect_value(t, module.exprs[call.args[0]].kind, ast.Expr_Kind.Type) +} + @(test) parser_accepts_sentinel_many_item_pointer_types :: proc(t: ^testing.T) { text := `zero func(value [*;0]u8) void {} @@ -2086,6 +2117,124 @@ main func() void { testing.expect(t, found_address) } +@(test) +comptime_type_params_specialize_by_type_and_omit_runtime_args :: proc(t: ^testing.T) { + text := `Point :: struct { + x i32 +} +id func($T type, value T) T { + return value +} +zero func($T type) T { + value T = undefined + return value +} +buffer func($T type, $N usize, value T) [N]T { + data [N]T = undefined + return data +} +main func() void { + a i32 :: 42 + b u8 :: 7 + p Point :: Point { x = 9 } + _ = id(i32, a) + _ = id(u8, b) + _ = id(Point, p) + _ = zero(i32) + _ = buffer(u8, 4, b) +} +` + 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) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols) + defer delete(llvm_text) + + id_specs := 0 + zero_specs := 0 + buffer_specs := 0 + for function in hir_module.functions { + name := symbol.resolve(&symbols, function.name) + if name == "id" { + id_specs += 1 + testing.expect_value(t, len(function.params), 1) + } else if name == "zero" { + zero_specs += 1 + testing.expect_value(t, len(function.params), 0) + } else if name == "buffer" { + buffer_specs += 1 + testing.expect_value(t, len(function.params), 1) + } + } + + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect_value(t, id_specs, 3) + testing.expect_value(t, zero_specs, 1) + testing.expect_value(t, buffer_specs, 1) + testing.expect(t, strings.contains(llvm_text, "@bro__p0__id__i32__cti32")) + testing.expect(t, strings.contains(llvm_text, "@bro__p0__zero__cti32")) + testing.expect(t, strings.contains(llvm_text, "@bro__p0__buffer__u8__ctu8__cv4")) +} + +@(test) +comptime_type_params_diagnose_invalid_uses :: proc(t: ^testing.T) { + text := `id func($T type, value T) T { + return value +} +bad_c c_func($T type) void +bad_value func($T type) void { + _ = T +} +bad_assign func($T type) void { + T = 1 +} +main func() void { + x i32 = 1 + _ = id(x, x) + bad_value(i32) + bad_assign(i32) +} +` + 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_arg := false + found_c_func := false + found_value := false + found_assign := false + for diagnostic in diagnostics.items { + message := diagnostic.message + found_arg = found_arg || strings.contains(message, "argument for comptime type parameter 'T' must be a type") + found_c_func = found_c_func || strings.contains(message, "comptime parameters require 'func', not 'c_func'") + found_value = found_value || strings.contains(message, "type parameter 'T' is not a runtime value") + found_assign = found_assign || strings.contains(message, "cannot assign comptime parameter 'T'") + } + + testing.expect(t, found_arg) + testing.expect(t, found_c_func) + testing.expect(t, found_value) + testing.expect(t, found_assign) +} + @(test) unused_function_signatures_are_validated_eagerly :: proc(t: ^testing.T) { text := `broken func(value, value i8, nope void) void {} @@ -5456,6 +5605,16 @@ comptime_value_params_compile_and_run :: proc(t: ^testing.T) { testing.expect_value(t, state.exit_code, 0) } +@(test) +comptime_type_params_compile_and_run :: proc(t: ^testing.T) { + output := "/tmp/brolang-test-comptime-type-params" + defer _ = os.remove(output) + status := compiler_core.compile_package("examples/programs/comptime_type_params", output) + testing.expect_value(t, status, 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 0) +} + @(test) lazy_function_body_marks_import_as_used_without_resolving_it :: proc(t: ^testing.T) { output := "/tmp/brolang-test-package-lazy-import" diff --git a/examples/programs/comptime_type_params/main.bro b/examples/programs/comptime_type_params/main.bro new file mode 100644 index 0000000..5b87012 --- /dev/null +++ b/examples/programs/comptime_type_params/main.bro @@ -0,0 +1,46 @@ +Point :: struct { + x i32 +} + +max func($T type, a, b T) T { + if a > b { + return a + } + return b +} + +id func($T type, value T) T { + return value +} + +buffer func($T type, $N usize, value T) [N]T { + data [N]T = undefined + _ = value + return data +} + +main func() i32 { + a i32 :: 42 + b i32 :: 27 + if max(i32, a, b) != 42 { + return 1 + } + + small_a u8 :: 3 + small_b u8 :: 9 + if max(u8, small_a, small_b) != 9 { + return 2 + } + + p Point :: Point { x = 11 } + q Point :: id(Point, p) + if q.x != 11 { + return 3 + } + + bytes [_]u8 :: buffer(u8, 4, small_a) + if bytes.len != 4 { + return 4 + } + return 0 +}