comptime type params

This commit is contained in:
2026-07-02 20:30:02 +02:00
parent a98b26446d
commit b94687c30a
8 changed files with 393 additions and 31 deletions
+2 -1
View File
@@ -53,6 +53,7 @@ roadmap and milestone history.
- demand-monomorphized Brolang and C-ABI functions - 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 - 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 - bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions - concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
- Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns - 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 ## PLANNED / DEFERRED
- comptime type parameters and comptime-evaluable functions - comptime-evaluable functions
- tuples and native Brolang variadic functions - tuples and native Brolang variadic functions
- exporting Brolang functions to C and broader target-specific C ABI lowering - exporting Brolang functions to C and broader target-specific C ABI lowering
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments - non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
+1 -1
View File
@@ -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 - 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 - Qualified imported globals and functions with package-aware symbol mangling
- Demand-monomorphized Brolang and C-ABI functions - 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 concrete C function declarations with exact external symbol names
- Bodyless manual and imported C variadic declarations with default argument promotions - Bodyless manual and imported C variadic declarations with default argument promotions
- Ordered linking of additional C sources, objects, archives, and libraries - Ordered linking of additional C sources, objects, archives, and libraries
+10 -3
View File
@@ -640,10 +640,17 @@
- v1 intentionally supports integer values only; no comptime branch pruning or - v1 intentionally supports integer values only; no comptime branch pruning or
user-function execution user-function execution
27.5 comptime type parameters (deferred) 27.5 comptime type parameters (implemented; v1)
- planned shape: `max func($T type, a, b T) T` - `$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 { ... }` - planned shape: `$x :: 32` and `$sum func(a, b int) int { ... }`
28. brolang build system (requires comptime execution) 28. brolang build system (requires comptime execution)
+1
View File
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
Array, Array,
None, None,
Undefined, Undefined,
Type,
Name, Name,
Enum_Literal, Enum_Literal,
Address, Address,
+139 -9
View File
@@ -33,10 +33,16 @@ Spec :: struct {
hir_id: hir.Function_Id, hir_id: hir.Function_Id,
} }
Comptime_Value_Kind :: enum u8 {
Integer,
Type,
}
Comptime_Value :: struct { Comptime_Value :: struct {
name: symbol.Id, name: symbol.Id,
type: types.Type, type: types.Type,
value: i128, value: i128,
kind: Comptime_Value_Kind,
} }
Infer_Local :: struct { Infer_Local :: struct {
@@ -174,6 +180,7 @@ Checker :: struct {
cycle_stack: [dynamic]Cycle_Frame, cycle_stack: [dynamic]Cycle_Frame,
main_symbol: symbol.Id, main_symbol: symbol.Id,
sink_symbol: symbol.Id, sink_symbol: symbol.Id,
type_symbol: symbol.Id,
current_result: types.Type, current_result: types.Type,
current_build_ctx: ^Build_Ctx, current_build_ctx: ^Build_Ctx,
current_comptime_values: []Comptime_Value, current_comptime_values: []Comptime_Value,
@@ -208,6 +215,22 @@ current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_
return find_comptime_value(checker.current_comptime_values, name) 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 { function_has_comptime_params :: proc(function: ast.Function) -> bool {
for param in function.params { for param in function.params {
if param.comptime_value { if param.comptime_value {
@@ -243,7 +266,8 @@ comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
} }
for value, index in left { for value, index in left {
other := right[index] 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 return false
} }
} }
@@ -376,9 +400,11 @@ eval_integer_constant_in_context :: proc(
case .Name: case .Name:
if !symbol.is_valid(expr.qualifier) { if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok { if value, ok := current_comptime_value(checker, expr.name); ok {
if value.kind == .Integer {
return Constant{kind = .Value, value = value.value} return Constant{kind = .Value, value = value.value}
} }
} }
}
target_pkg, available := expr_package(checker, expr, pkg, file, false) target_pkg, available := expr_package(checker, expr, pkg, file, false)
if !available { if !available {
return Constant{kind = .Not_Constant} return Constant{kind = .Not_Constant}
@@ -469,6 +495,11 @@ type_from_syntax :: proc(
if !ok { if !ok {
return value 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 store := &checker.module.types
changed := false changed := false
#partial switch item.kind { #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) { if index < 0 || index >= len(function.params) {
return types.INVALID 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) 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 // 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. // 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 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( collect_comptime_values :: proc(
checker: ^Checker, checker: ^Checker,
function: ast.Function, 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) { 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 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) declared := type_from_syntax(checker, param.type, function.pkg, function.file)
if !types.is_concrete_integer(declared) { if !types.is_concrete_integer(declared) {
if diagnose { 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) mark_block_imports_used(checker, expr.body, file)
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range: case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right) append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .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'", "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.template_diagnostics[function_id] = source.addf(
checker.diagnostics, checker.diagnostics,
param.span, param.span,
@@ -1855,6 +1940,9 @@ infer_expr :: proc(
case .Invalid: case .Invalid:
last = types.INVALID last = types.INVALID
_ = pop(&stack) _ = pop(&stack)
case .Type:
last = types.INVALID
_ = pop(&stack)
case .Integer: case .Integer:
last = types.I64 last = types.I64
if expr.integer <= 0x7fff_ffff_ffff_ffff { if expr.integer <= 0x7fff_ffff_ffff_ffff {
@@ -1898,10 +1986,12 @@ infer_expr :: proc(
if !types.is_valid(last) { if !types.is_valid(last) {
if !symbol.is_valid(expr.qualifier) { if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok { if value, ok := current_comptime_value(checker, expr.name); ok {
if value.kind == .Integer {
last = value.type last = value.type
} }
} }
} }
}
if !types.is_valid(last) { if !types.is_valid(last) {
if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, pkg, file); enum_ok { if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, pkg, file); enum_ok {
_, member_ok := find_enum_member(checker, enum_type, expr.name) _, member_ok := find_enum_member(checker, enum_type, expr.name)
@@ -3363,6 +3453,14 @@ Build_Expr_Frame :: struct {
template: ast.Function_Id, 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 { 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) { if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
return false return false
@@ -4308,6 +4406,10 @@ build_expr :: proc(
checker, expr, locals, global_reads, calls, frame.expected, pkg, file, checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
) )
_ = pop(&stack) _ = 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: case .Invalid, .Integer:
last = invalid_hir_expr(checker, expr.span, expr.diagnostic) last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
_ = pop(&stack) _ = pop(&stack)
@@ -4363,6 +4465,7 @@ build_expr :: proc(
if last == hir.INVALID_EXPR { if last == hir.INVALID_EXPR {
if !symbol.is_valid(expr.qualifier) { if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok { if value, ok := current_comptime_value(checker, expr.name); ok {
if value.kind == .Integer {
expected_type := value.type expected_type := value.type
if types.is_concrete_integer(frame.expected) || types.is_float(frame.expected, checker.target) { if types.is_concrete_integer(frame.expected) || types.is_float(frame.expected, checker.target) {
expected_type = frame.expected expected_type = frame.expected
@@ -4373,6 +4476,15 @@ build_expr :: proc(
Constant{kind=.Value, value=value.value}, Constant{kind=.Value, value=value.value},
expected_type, 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)
}
} }
} }
} }
@@ -4561,13 +4673,21 @@ build_expr :: proc(
stack[frame_index].template = template stack[frame_index].template = template
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator) 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(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 stack[frame_index].stage = 3
if len(expr.args) > 0 { if stack[frame_index].arg_index < len(expr.args) {
arg_expected := call_arg_expected(checker, function, 0) arg_expected := call_arg_expected(checker, function, stack[frame_index].arg_index)
if !is_runtime_type(checker, arg_expected) { if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID 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 continue
@@ -4615,9 +4735,9 @@ build_expr :: proc(
if frame.arg_index < len(expr.args) { if frame.arg_index < len(expr.args) {
stack[frame_index].built_args[frame.arg_index] = last 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_types[frame.arg_index] = checker.module.exprs[last].type
stack[frame_index].arg_index += 1 next := next_built_template_arg(checker, checker.ast_module.functions[frame.template], frame.arg_index+1)
if frame.arg_index+1 < len(expr.args) { stack[frame_index].arg_index = next
next := frame.arg_index+1 if next < len(expr.args) {
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next) arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next)
if !is_runtime_type(checker, arg_expected) { if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID arg_expected = types.INVALID
@@ -4881,6 +5001,14 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
} }
} }
for value in spec.comptime_values { for value in spec.comptime_values {
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 {
strings.write_string(&builder, "__cv") strings.write_string(&builder, "__cv")
if value.value < 0 { if value.value < 0 {
strings.write_string(&builder, "n") strings.write_string(&builder, "n")
@@ -4889,6 +5017,7 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
fmt.sbprintf(&builder, "%d", value.value) fmt.sbprintf(&builder, "%d", value.value)
} }
} }
}
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator) return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
} }
@@ -7836,6 +7965,7 @@ check :: proc(
module = hir.init_module(selected, allocator), module = hir.init_module(selected, allocator),
main_symbol = symbol.intern(symbols, "main"), main_symbol = symbol.intern(symbols, "main"),
sink_symbol = symbol.intern(symbols, "_"), sink_symbol = symbol.intern(symbols, "_"),
type_symbol = symbol.intern(symbols, "type"),
target = selected, target = selected,
allocator = allocator, allocator = allocator,
} }
+19 -1
View File
@@ -591,6 +591,17 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
tok := current(parser) tok := current(parser)
#partial switch tok.kind { #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, case .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64, .Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64, .Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
@@ -601,7 +612,14 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
start := tok start := tok
target := parse_type_atom(parser) target := parse_type_atom(parser)
if _, ok := allow(parser, .Left_Paren); !ok { 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 parser.delimiter_depth += 1
skip_newlines(parser) skip_newlines(parser)
+159
View File
@@ -204,6 +204,37 @@ main func() void {}
testing.expect(t, !module.functions[0].params[1].comptime_value) 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) @(test)
parser_accepts_sentinel_many_item_pointer_types :: proc(t: ^testing.T) { parser_accepts_sentinel_many_item_pointer_types :: proc(t: ^testing.T) {
text := `zero func(value [*;0]u8) void {} text := `zero func(value [*;0]u8) void {}
@@ -2086,6 +2117,124 @@ main func() void {
testing.expect(t, found_address) 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) @(test)
unused_function_signatures_are_validated_eagerly :: proc(t: ^testing.T) { unused_function_signatures_are_validated_eagerly :: proc(t: ^testing.T) {
text := `broken func(value, value i8, nope void) void {} 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) 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) @(test)
lazy_function_body_marks_import_as_used_without_resolving_it :: proc(t: ^testing.T) { lazy_function_body_marks_import_as_used_without_resolving_it :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-lazy-import" output := "/tmp/brolang-test-package-lazy-import"
@@ -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
}