From c914083102de6c582b26c027e1f2456323b6fc77 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sun, 12 Jul 2026 19:48:36 +0200 Subject: [PATCH] enforce leading comptime params --- LANGUAGE.md | 1 + TODO.md | 1 + compiler/checker/checker.odin | 14 ++++++++++++++ compiler_tests.odin | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/LANGUAGE.md b/LANGUAGE.md index d7639fe..adcc71c 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -58,6 +58,7 @@ roadmap and milestone history. - 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 diff --git a/TODO.md b/TODO.md index 9653ca4..e4362e3 100644 --- a/TODO.md +++ b/TODO.md @@ -787,6 +787,7 @@ 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 diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index cb3435f..915b457 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -2060,7 +2060,21 @@ 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 { + 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) diff --git a/compiler_tests.odin b/compiler_tests.odin index e31c747..5e687f0 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -2650,6 +2650,42 @@ main func() void { 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 {