comptime type params
This commit is contained in:
+2
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
|
||||
Array,
|
||||
None,
|
||||
Undefined,
|
||||
Type,
|
||||
Name,
|
||||
Enum_Literal,
|
||||
Address,
|
||||
|
||||
+155
-25
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user