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
|
- 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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Array,
|
Array,
|
||||||
None,
|
None,
|
||||||
Undefined,
|
Undefined,
|
||||||
|
Type,
|
||||||
Name,
|
Name,
|
||||||
Enum_Literal,
|
Enum_Literal,
|
||||||
Address,
|
Address,
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user