comptime value-params
This commit is contained in:
+2
-1
@@ -52,6 +52,7 @@ roadmap and milestone history.
|
|||||||
### functions, C interop, and linking
|
### functions, C interop, and linking
|
||||||
|
|
||||||
- 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
|
||||||
- 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
|
||||||
@@ -79,7 +80,7 @@ roadmap and milestone history.
|
|||||||
|
|
||||||
## PLANNED / DEFERRED
|
## PLANNED / DEFERRED
|
||||||
|
|
||||||
- comptime polymorphism
|
- comptime type parameters and 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,6 +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
|
||||||
- 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
|
||||||
|
|||||||
@@ -630,11 +630,23 @@
|
|||||||
- imports beginning with `@` resolve from the compiler process cwd / project root
|
- imports beginning with `@` resolve from the compiler process cwd / project root
|
||||||
- `heap :: import "@std/mem/heap"` works from any package depth without `../../../` path math
|
- `heap :: import "@std/mem/heap"` works from any package depth without `../../../` path math
|
||||||
|
|
||||||
27. comptime polymorphism (zig inspired)
|
27. comptime integer value parameters (implemented; v1)
|
||||||
|
- `$N` marks an integer comptime parameter in a normal `func` signature:
|
||||||
|
`make_array func($N usize) [N]u8`
|
||||||
|
- callers pass a compile-time integer expression; the value specializes the function
|
||||||
|
and is omitted from the runtime ABI
|
||||||
|
- inside the specialization, `N` is visible as an immutable compile-time integer in
|
||||||
|
array counts, types, and body expressions
|
||||||
|
- v1 intentionally supports integer values only; no comptime branch pruning or
|
||||||
|
user-function execution
|
||||||
|
|
||||||
28. comptime execution
|
27.5 comptime type parameters (deferred)
|
||||||
|
- planned shape: `max func($T type, a, b T) T`
|
||||||
|
|
||||||
29. brolang build system (requires comptime execution)
|
27.6 comptime-evaluable constants/functions (deferred)
|
||||||
|
- planned shape: `$x :: 32` and `$sum func(a, b int) int { ... }`
|
||||||
|
|
||||||
|
28. brolang build system (requires comptime execution)
|
||||||
|
|
||||||
## A word on multi-unwrap
|
## A word on multi-unwrap
|
||||||
|
|
||||||
@@ -1221,6 +1233,15 @@ data :: read_file(path) catch |e| match e {
|
|||||||
| `yield :label value` | Provide value from labeled block |
|
| `yield :label value` | Provide value from labeled block |
|
||||||
| `return` / `return value` | Exit the current function; in fallible functions, return value dispatches by type |
|
| `return` / `return value` | Exit the current function; in fallible functions, return value dispatches by type |
|
||||||
|
|
||||||
|
## A word on comptime
|
||||||
|
|
||||||
|
```
|
||||||
|
make_array func($N usize) [N]u8 { ... } # implemented: integer comptime value params
|
||||||
|
max func($T type, a, b T) T { ... } # deferred: comptime type params
|
||||||
|
$x :: 32 # deferred: comptime evaluable constant
|
||||||
|
$sum func(a, b int) int { ... } # deferred: comptime evaluable function
|
||||||
|
```
|
||||||
|
|
||||||
## A word on memory allocation
|
## A word on memory allocation
|
||||||
|
|
||||||
(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY)
|
(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY)
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ Param :: struct {
|
|||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
type: Type_Syntax,
|
type: Type_Syntax,
|
||||||
|
comptime_value: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt_Kind :: enum u8 {
|
Stmt_Kind :: enum u8 {
|
||||||
|
|||||||
+349
-41
@@ -28,10 +28,17 @@ spec_index :: proc(id: Spec_Id, count: int) -> (int, bool) {
|
|||||||
Spec :: struct {
|
Spec :: struct {
|
||||||
template: ast.Function_Id,
|
template: ast.Function_Id,
|
||||||
args: []types.Type,
|
args: []types.Type,
|
||||||
|
comptime_values: []Comptime_Value,
|
||||||
result: types.Type,
|
result: types.Type,
|
||||||
hir_id: hir.Function_Id,
|
hir_id: hir.Function_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Comptime_Value :: struct {
|
||||||
|
name: symbol.Id,
|
||||||
|
type: types.Type,
|
||||||
|
value: i128,
|
||||||
|
}
|
||||||
|
|
||||||
Infer_Local :: struct {
|
Infer_Local :: struct {
|
||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
type: types.Type,
|
type: types.Type,
|
||||||
@@ -169,6 +176,7 @@ Checker :: struct {
|
|||||||
sink_symbol: symbol.Id,
|
sink_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,
|
||||||
target: target.Target,
|
target: target.Target,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -187,6 +195,61 @@ type_label :: proc(checker: ^Checker, value: types.Type) -> string {
|
|||||||
return types.name(value)
|
return types.name(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
find_comptime_value :: proc(values: []Comptime_Value, name: symbol.Id) -> (Comptime_Value, bool) {
|
||||||
|
for index := len(values) - 1; index >= 0; index -= 1 {
|
||||||
|
if values[index].name == name {
|
||||||
|
return values[index], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_Value, bool) {
|
||||||
|
return find_comptime_value(checker.current_comptime_values, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
function_has_comptime_params :: proc(function: ast.Function) -> bool {
|
||||||
|
for param in function.params {
|
||||||
|
if param.comptime_value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_param_count :: proc(function: ast.Function) -> int {
|
||||||
|
count := 0
|
||||||
|
for param in function.params {
|
||||||
|
if !param.comptime_value {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
comptime_param_count :: proc(function: ast.Function) -> int {
|
||||||
|
count := 0
|
||||||
|
for param in function.params {
|
||||||
|
if param.comptime_value {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
|
||||||
|
if len(left) != len(right) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for value, index in left {
|
||||||
|
other := right[index]
|
||||||
|
if value.name != other.name || !types.equal(value.type, other.type) || value.value != other.value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
Constant_Frame :: struct {
|
Constant_Frame :: struct {
|
||||||
expr: ast.Expr_Id,
|
expr: ast.Expr_Id,
|
||||||
stage: u8,
|
stage: u8,
|
||||||
@@ -311,6 +374,11 @@ eval_integer_constant_in_context :: proc(
|
|||||||
case .Integer:
|
case .Integer:
|
||||||
return Constant{kind = .Value, value = i128(expr.integer)}
|
return Constant{kind = .Value, value = i128(expr.integer)}
|
||||||
case .Name:
|
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}
|
||||||
|
}
|
||||||
|
}
|
||||||
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}
|
||||||
@@ -767,7 +835,8 @@ function_signatures_equal :: proc(left, right: ast.Function) -> bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for param, index in left.params {
|
for param, index in left.params {
|
||||||
if param.type != right.params[index].type {
|
if param.type != right.params[index].type ||
|
||||||
|
param.comptime_value != right.params[index].comptime_value {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -792,6 +861,79 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
|
|||||||
return declared
|
return declared
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collect_comptime_values :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
function: ast.Function,
|
||||||
|
args: []ast.Expr_Id,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
diagnose := false,
|
||||||
|
) -> ([]Comptime_Value, bool) {
|
||||||
|
if !function_has_comptime_params(function) {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
values: [dynamic]Comptime_Value
|
||||||
|
values.allocator = checker.allocator
|
||||||
|
ok := true
|
||||||
|
for param, index in function.params {
|
||||||
|
if !param.comptime_value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
span := param.span
|
||||||
|
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
|
||||||
|
}
|
||||||
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
|
if !types.is_concrete_integer(declared) {
|
||||||
|
if diagnose {
|
||||||
|
source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
param.span,
|
||||||
|
"comptime parameter '%s' requires a concrete integer type",
|
||||||
|
symbol_text(checker, param.name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
constant := Constant{kind = .Not_Constant}
|
||||||
|
if index < len(args) {
|
||||||
|
constant = eval_integer_constant_in_context(checker, args[index], pkg, file)
|
||||||
|
}
|
||||||
|
if constant.kind != .Value {
|
||||||
|
if diagnose {
|
||||||
|
source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
span,
|
||||||
|
"argument for comptime parameter '%s' must be a compile-time integer expression",
|
||||||
|
symbol_text(checker, param.name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !fits_integer_type(constant.value, declared, checker.target) {
|
||||||
|
if diagnose {
|
||||||
|
source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
span,
|
||||||
|
"integer constant %d does not fit in %s",
|
||||||
|
constant.value,
|
||||||
|
types.name(declared),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
append(&values, Comptime_Value{name=param.name, type=declared, value=constant.value})
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
delete(values)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return values[:], true
|
||||||
|
}
|
||||||
|
|
||||||
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
|
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
|
||||||
if index < 0 || index >= int(function_item.field_count) {
|
if index < 0 || index >= int(function_item.field_count) {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
@@ -818,6 +960,9 @@ function_value_signature :: proc(
|
|||||||
if !function.c_abi || types.is_valid(function.error) {
|
if !function.c_abi || types.is_valid(function.error) {
|
||||||
return nil, types.INVALID, false
|
return nil, types.INVALID, false
|
||||||
}
|
}
|
||||||
|
if function_has_comptime_params(function) {
|
||||||
|
return nil, types.INVALID, false
|
||||||
|
}
|
||||||
result = type_from_syntax(checker, function.result, function.pkg, function.file)
|
result = type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
if !types.is_void(result) && !is_runtime_type(checker, result) {
|
if !types.is_void(result) && !is_runtime_type(checker, result) {
|
||||||
return nil, types.INVALID, false
|
return nil, types.INVALID, false
|
||||||
@@ -1027,14 +1172,34 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
if len(function.unsupported_reason) > 0 {
|
if len(function.unsupported_reason) > 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
has_comptime := function_has_comptime_params(function)
|
||||||
locals: [dynamic]symbol.Id
|
locals: [dynamic]symbol.Id
|
||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(checker, param.type, function.pkg, function.file));
|
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
|
if param.comptime_value {
|
||||||
|
if function.c_abi {
|
||||||
|
checker.template_diagnostics[function_id] = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
param.span,
|
||||||
|
"comptime parameters require 'func', not 'c_func'",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !types.is_concrete_integer(param_type) {
|
||||||
|
checker.template_diagnostics[function_id] = source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
param.span,
|
||||||
|
"comptime parameter '%s' requires a concrete integer type",
|
||||||
|
symbol_text(checker, param.name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if !has_comptime {
|
||||||
|
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
|
||||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
checker.template_diagnostics[function_id] = diagnostic
|
checker.template_diagnostics[function_id] = diagnostic
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if param.type == types.VOID {
|
if param.type == types.VOID {
|
||||||
source.add(
|
source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -1051,7 +1216,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
append(&locals, param.name)
|
append(&locals, param.name)
|
||||||
if types.contains_c_struct_by_value(type_from_syntax(checker, param.type, function.pkg, function.file), &checker.module.types) {
|
if !has_comptime && types.contains_c_struct_by_value(param_type, &checker.module.types) {
|
||||||
checker.template_diagnostics[function_id] = source.addf(
|
checker.template_diagnostics[function_id] = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
param.span,
|
param.span,
|
||||||
@@ -1060,11 +1225,13 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, type_from_syntax(checker, function.result, function.pkg, function.file));
|
if !has_comptime {
|
||||||
|
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
|
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
|
||||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
checker.template_diagnostics[function_id] = diagnostic
|
checker.template_diagnostics[function_id] = diagnostic
|
||||||
}
|
}
|
||||||
if types.contains_c_struct_by_value(type_from_syntax(checker, function.result, function.pkg, function.file), &checker.module.types) {
|
if types.contains_c_struct_by_value(result_type, &checker.module.types) {
|
||||||
checker.template_diagnostics[function_id] = source.addf(
|
checker.template_diagnostics[function_id] = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
function.span,
|
function.span,
|
||||||
@@ -1072,6 +1239,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
symbol_text(checker, function.name),
|
symbol_text(checker, function.name),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if types.is_valid(function.error) {
|
if types.is_valid(function.error) {
|
||||||
error_type := type_from_syntax(checker, function.error, function.pkg, function.file)
|
error_type := type_from_syntax(checker, function.error, function.pkg, function.file)
|
||||||
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
||||||
@@ -1298,23 +1466,38 @@ find_infer_local_index :: proc(locals: []Infer_Local, name: symbol.Id) -> (int,
|
|||||||
return -1, false
|
return -1, false
|
||||||
}
|
}
|
||||||
|
|
||||||
find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
|
find_spec :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
template: ast.Function_Id,
|
||||||
|
actual_args: []types.Type,
|
||||||
|
comptime_values: []Comptime_Value = nil,
|
||||||
|
) -> Spec_Id {
|
||||||
function := checker.ast_module.functions[template]
|
function := checker.ast_module.functions[template]
|
||||||
for spec, index in checker.specs {
|
for spec, index in checker.specs {
|
||||||
if spec.template != template || len(spec.args) != len(function.params) {
|
if spec.template != template || len(spec.args) != runtime_param_count(function) ||
|
||||||
|
!comptime_values_equal(spec.comptime_values, comptime_values) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matches := true
|
matches := true
|
||||||
|
runtime_index := 0
|
||||||
|
previous_comptime := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = comptime_values
|
||||||
for param, param_index in function.params {
|
for param, param_index in function.params {
|
||||||
|
if param.comptime_value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
actual := types.INVALID
|
actual := types.INVALID
|
||||||
if param_index < len(actual_args) {
|
if param_index < len(actual_args) {
|
||||||
actual = actual_args[param_index]
|
actual = actual_args[param_index]
|
||||||
}
|
}
|
||||||
if !types.equal(spec.args[param_index], specialized_param_type(checker, param.type, actual, function.pkg, function.file)) {
|
if runtime_index >= len(spec.args) ||
|
||||||
|
!types.equal(spec.args[runtime_index], specialized_param_type(checker, param.type, actual, function.pkg, function.file)) {
|
||||||
matches = false
|
matches = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
runtime_index += 1
|
||||||
}
|
}
|
||||||
|
checker.current_comptime_values = previous_comptime
|
||||||
if matches {
|
if matches {
|
||||||
return spec_id(index)
|
return spec_id(index)
|
||||||
}
|
}
|
||||||
@@ -1340,8 +1523,19 @@ specialized_param_type :: proc(
|
|||||||
return declared
|
return declared
|
||||||
}
|
}
|
||||||
|
|
||||||
can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: []types.Type) -> bool {
|
can_specialize :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
function: ast.Function,
|
||||||
|
actual_args: []types.Type,
|
||||||
|
comptime_values: []Comptime_Value = nil,
|
||||||
|
) -> bool {
|
||||||
|
previous_comptime := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = comptime_values
|
||||||
|
defer checker.current_comptime_values = previous_comptime
|
||||||
for param, index in function.params {
|
for param, index in function.params {
|
||||||
|
if param.comptime_value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
actual := types.INVALID
|
actual := types.INVALID
|
||||||
if index < len(actual_args) {
|
if index < len(actual_args) {
|
||||||
actual = actual_args[index]
|
actual = actual_args[index]
|
||||||
@@ -1353,20 +1547,34 @@ can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: [
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
|
ensure_spec :: proc(
|
||||||
if existing := find_spec(checker, template, actual_args); existing != INVALID_SPEC {
|
checker: ^Checker,
|
||||||
|
template: ast.Function_Id,
|
||||||
|
actual_args: []types.Type,
|
||||||
|
comptime_values: []Comptime_Value = nil,
|
||||||
|
) -> Spec_Id {
|
||||||
|
if existing := find_spec(checker, template, actual_args, comptime_values); existing != INVALID_SPEC {
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
function := checker.ast_module.functions[template]
|
function := checker.ast_module.functions[template]
|
||||||
|
previous_comptime := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = comptime_values
|
||||||
|
defer checker.current_comptime_values = previous_comptime
|
||||||
signature: [dynamic]types.Type
|
signature: [dynamic]types.Type
|
||||||
signature.allocator = checker.allocator
|
signature.allocator = checker.allocator
|
||||||
for param, index in function.params {
|
for param, index in function.params {
|
||||||
|
if param.comptime_value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
actual := types.INVALID
|
actual := types.INVALID
|
||||||
if index < len(actual_args) {
|
if index < len(actual_args) {
|
||||||
actual = actual_args[index]
|
actual = actual_args[index]
|
||||||
}
|
}
|
||||||
append(&signature, specialized_param_type(checker, param.type, actual, function.pkg, function.file))
|
append(&signature, specialized_param_type(checker, param.type, actual, function.pkg, function.file))
|
||||||
}
|
}
|
||||||
|
comptime_signature: [dynamic]Comptime_Value
|
||||||
|
comptime_signature.allocator = checker.allocator
|
||||||
|
append(&comptime_signature, ..comptime_values)
|
||||||
result := function_channel_type(checker, function)
|
result := function_channel_type(checker, function)
|
||||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT &&
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT &&
|
||||||
!types.is_valid(function.error) {
|
!types.is_valid(function.error) {
|
||||||
@@ -1375,7 +1583,13 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [
|
|||||||
index := spec_id(len(checker.specs))
|
index := spec_id(len(checker.specs))
|
||||||
append(
|
append(
|
||||||
&checker.specs,
|
&checker.specs,
|
||||||
Spec{template = template, args = signature[:], result = result, hir_id = hir.INVALID_FUNCTION},
|
Spec{
|
||||||
|
template = template,
|
||||||
|
args = signature[:],
|
||||||
|
comptime_values = comptime_signature[:],
|
||||||
|
result = result,
|
||||||
|
hir_id = hir.INVALID_FUNCTION,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
@@ -1662,6 +1876,13 @@ 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 !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)
|
||||||
@@ -1859,13 +2080,16 @@ infer_expr :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file)
|
||||||
|
defer delete(comptime_values, checker.allocator)
|
||||||
if valid_call_arity(function, len(expr.args)) &&
|
if valid_call_arity(function, len(expr.args)) &&
|
||||||
can_specialize(checker, function, stack[frame_index].args) {
|
comptime_ok &&
|
||||||
|
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
|
||||||
spec := INVALID_SPEC
|
spec := INVALID_SPEC
|
||||||
if demanded == nil {
|
if demanded == nil {
|
||||||
spec = ensure_spec(checker, frame.template, stack[frame_index].args)
|
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
||||||
} else {
|
} else {
|
||||||
spec = find_spec(checker, frame.template, stack[frame_index].args)
|
spec = find_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
||||||
mark_spec_demanded(checker, spec, demanded)
|
mark_spec_demanded(checker, spec, demanded)
|
||||||
}
|
}
|
||||||
if spec != INVALID_SPEC {
|
if spec != INVALID_SPEC {
|
||||||
@@ -2252,6 +2476,9 @@ infer_spec_locals_and_result :: proc(
|
|||||||
) -> ([]types.Type, types.Type) {
|
) -> ([]types.Type, types.Type) {
|
||||||
spec := checker.specs[id]
|
spec := checker.specs[id]
|
||||||
function := checker.ast_module.functions[spec.template]
|
function := checker.ast_module.functions[spec.template]
|
||||||
|
previous_comptime := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = spec.comptime_values
|
||||||
|
defer checker.current_comptime_values = previous_comptime
|
||||||
declared := type_from_syntax(checker, function.result, function.pkg, function.file)
|
declared := type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
||||||
declared = types.I32
|
declared = types.I32
|
||||||
@@ -2262,12 +2489,17 @@ infer_spec_locals_and_result :: proc(
|
|||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
defer delete(locals)
|
defer delete(locals)
|
||||||
local_types := make([]types.Type, len(checker.ast_module.statements), checker.allocator)
|
local_types := make([]types.Type, len(checker.ast_module.statements), checker.allocator)
|
||||||
|
runtime_index := 0
|
||||||
for param, index in function.params {
|
for param, index in function.params {
|
||||||
|
if param.comptime_value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
param_type := types.INVALID
|
param_type := types.INVALID
|
||||||
if index < len(spec.args) {
|
if runtime_index < len(spec.args) {
|
||||||
param_type = spec.args[index]
|
param_type = spec.args[runtime_index]
|
||||||
}
|
}
|
||||||
append(&locals, Infer_Local{name=param.name, type=param_type, declared=param_type, statement=ast.INVALID_STMT})
|
append(&locals, Infer_Local{name=param.name, type=param_type, declared=param_type, statement=ast.INVALID_STMT})
|
||||||
|
runtime_index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
result := types.INVALID
|
result := types.INVALID
|
||||||
@@ -2716,6 +2948,7 @@ prune_specs :: proc(checker: ^Checker) {
|
|||||||
for spec in checker.specs {
|
for spec in checker.specs {
|
||||||
if spec.hir_id == hir.INVALID_FUNCTION {
|
if spec.hir_id == hir.INVALID_FUNCTION {
|
||||||
delete(spec.args, checker.allocator)
|
delete(spec.args, checker.allocator)
|
||||||
|
delete(spec.comptime_values, checker.allocator)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
checker.specs[retained] = spec
|
checker.specs[retained] = spec
|
||||||
@@ -4090,6 +4323,22 @@ 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
|
||||||
|
}
|
||||||
|
last = build_constant_expr(
|
||||||
|
checker,
|
||||||
|
expr,
|
||||||
|
Constant{kind=.Value, value=value.value},
|
||||||
|
expected_type,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if last == hir.INVALID_EXPR {
|
if last == hir.INVALID_EXPR {
|
||||||
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 {
|
||||||
last = enum_member_hir(checker, enum_type, expr.name, expr.span)
|
last = enum_member_hir(checker, enum_type, expr.name, expr.span)
|
||||||
@@ -4340,14 +4589,22 @@ build_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function := checker.ast_module.functions[frame.template]
|
||||||
|
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file, diagnose=true)
|
||||||
|
defer delete(comptime_values, checker.allocator)
|
||||||
arg_violation := source.INVALID_DIAGNOSTIC
|
arg_violation := source.INVALID_DIAGNOSTIC
|
||||||
{
|
if comptime_ok {
|
||||||
params := checker.ast_module.functions[frame.template].params
|
previous_comptime := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = comptime_values
|
||||||
|
params := function.params
|
||||||
for index in 0..<len(params) {
|
for index in 0..<len(params) {
|
||||||
if index >= len(stack[frame_index].arg_types) {
|
if index >= len(stack[frame_index].arg_types) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
declared := type_from_syntax(checker, params[index].type, checker.ast_module.functions[frame.template].pkg, checker.ast_module.functions[frame.template].file)
|
if params[index].comptime_value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
declared := type_from_syntax(checker, params[index].type, function.pkg, function.file)
|
||||||
actual := stack[frame_index].arg_types[index]
|
actual := stack[frame_index].arg_types[index]
|
||||||
if types.is_constraint(declared) && types.is_valid(actual) &&
|
if types.is_constraint(declared) && types.is_valid(actual) &&
|
||||||
!types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
|
!types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
|
||||||
@@ -4360,14 +4617,22 @@ build_expr :: proc(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
checker.current_comptime_values = previous_comptime
|
||||||
|
}
|
||||||
|
spec := INVALID_SPEC
|
||||||
|
if comptime_ok {
|
||||||
|
spec = find_spec(checker, frame.template, stack[frame_index].arg_types, comptime_values)
|
||||||
}
|
}
|
||||||
spec := find_spec(checker, frame.template, stack[frame_index].arg_types)
|
|
||||||
delete(stack[frame_index].arg_types, checker.allocator)
|
delete(stack[frame_index].arg_types, checker.allocator)
|
||||||
stack[frame_index].arg_types = nil
|
stack[frame_index].arg_types = nil
|
||||||
if arg_violation != source.INVALID_DIAGNOSTIC {
|
if !comptime_ok || arg_violation != source.INVALID_DIAGNOSTIC {
|
||||||
delete(stack[frame_index].built_args, checker.allocator)
|
delete(stack[frame_index].built_args, checker.allocator)
|
||||||
stack[frame_index].built_args = nil
|
stack[frame_index].built_args = nil
|
||||||
last = invalid_hir_expr(checker, expr.span, arg_violation)
|
diagnostic := arg_violation
|
||||||
|
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||||
|
diagnostic = source.add(checker.diagnostics, expr.span, "invalid comptime argument")
|
||||||
|
}
|
||||||
|
last = invalid_hir_expr(checker, expr.span, diagnostic)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -4384,23 +4649,35 @@ build_expr :: proc(
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fixed_count := len(checker.ast_module.functions[frame.template].params)
|
source_args := stack[frame_index].built_args
|
||||||
for index in 0..<fixed_count {
|
runtime_count := runtime_param_count(function)
|
||||||
stack[frame_index].built_args[index] = coerce_expr(
|
runtime_arg_count := runtime_count + max(0, len(source_args)-len(function.params))
|
||||||
checker,
|
runtime_args := make([]hir.Expr_Id, runtime_arg_count, checker.allocator)
|
||||||
stack[frame_index].built_args[index],
|
runtime_index := 0
|
||||||
checker.specs[spec].args[index],
|
for param, source_index in function.params {
|
||||||
checker.module.exprs[stack[frame_index].built_args[index]].span,
|
if param.comptime_value {
|
||||||
)
|
continue
|
||||||
}
|
}
|
||||||
for index in fixed_count..<len(stack[frame_index].built_args) {
|
arg := source_args[source_index]
|
||||||
arg := stack[frame_index].built_args[index]
|
runtime_args[runtime_index] = coerce_expr(
|
||||||
stack[frame_index].built_args[index] = promote_c_vararg_expr(
|
checker,
|
||||||
|
arg,
|
||||||
|
checker.specs[spec].args[runtime_index],
|
||||||
|
checker.module.exprs[arg].span,
|
||||||
|
)
|
||||||
|
runtime_index += 1
|
||||||
|
}
|
||||||
|
for source_index in len(function.params)..<len(source_args) {
|
||||||
|
arg := source_args[source_index]
|
||||||
|
runtime_args[runtime_index] = promote_c_vararg_expr(
|
||||||
checker,
|
checker,
|
||||||
arg,
|
arg,
|
||||||
checker.module.exprs[arg].span,
|
checker.module.exprs[arg].span,
|
||||||
)
|
)
|
||||||
|
runtime_index += 1
|
||||||
}
|
}
|
||||||
|
delete(stack[frame_index].built_args, checker.allocator)
|
||||||
|
stack[frame_index].built_args = nil
|
||||||
function_id := checker.specs[spec].hir_id
|
function_id := checker.specs[spec].hir_id
|
||||||
assert(function_id != hir.INVALID_FUNCTION)
|
assert(function_id != hir.INVALID_FUNCTION)
|
||||||
add_unique_function(calls, function_id)
|
add_unique_function(calls, function_id)
|
||||||
@@ -4412,15 +4689,13 @@ build_expr :: proc(
|
|||||||
"could not resolve result type for specialization of '%s'",
|
"could not resolve result type for specialization of '%s'",
|
||||||
symbol_text(checker, expr.name),
|
symbol_text(checker, expr.name),
|
||||||
)
|
)
|
||||||
delete(stack[frame_index].built_args, checker.allocator)
|
delete(runtime_args, checker.allocator)
|
||||||
stack[frame_index].built_args = nil
|
|
||||||
last = invalid_hir_expr(checker, expr.span, id)
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
} else {
|
} else {
|
||||||
last = add_hir_expr(checker, hir.Expr{
|
last = add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Call, span=expr.span, type=result, target=hir.function_ref(function_id),
|
kind=.Call, span=expr.span, type=result, target=hir.function_ref(function_id),
|
||||||
left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, args=stack[frame_index].built_args, diagnostic = source.INVALID_DIAGNOSTIC,
|
left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, args=runtime_args, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
stack[frame_index].built_args = nil
|
|
||||||
}
|
}
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
}
|
}
|
||||||
@@ -4568,6 +4843,15 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
|||||||
strings.write_string(&builder, types.name(arg))
|
strings.write_string(&builder, types.name(arg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4860,6 +5144,21 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if _, is_comptime := current_comptime_value(checker, statement.name); is_comptime {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
statement.span,
|
||||||
|
"cannot assign comptime parameter '%s'",
|
||||||
|
symbol_text(checker, statement.name),
|
||||||
|
)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
local, found := find_build_local(ctx.locals^[:], statement.name)
|
local, found := find_build_local(ctx.locals^[:], statement.name)
|
||||||
if !found {
|
if !found {
|
||||||
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
|
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
|
||||||
@@ -6901,6 +7200,9 @@ all_paths_exit :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|||||||
build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||||
spec := checker.specs[id]
|
spec := checker.specs[id]
|
||||||
function := checker.ast_module.functions[spec.template]
|
function := checker.ast_module.functions[spec.template]
|
||||||
|
previous_comptime := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = spec.comptime_values
|
||||||
|
defer checker.current_comptime_values = previous_comptime
|
||||||
signature_diagnostic := source.INVALID_DIAGNOSTIC
|
signature_diagnostic := source.INVALID_DIAGNOSTIC
|
||||||
if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) {
|
if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) {
|
||||||
checker.specs[id].result = types.I64
|
checker.specs[id].result = types.I64
|
||||||
@@ -6945,15 +7247,20 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
delete(demanded)
|
delete(demanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
for param, index in function.params {
|
runtime_index := 0
|
||||||
|
for param in function.params {
|
||||||
|
if param.comptime_value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
local_id := hir.local_id(len(hir_locals))
|
local_id := hir.local_id(len(hir_locals))
|
||||||
param_type := types.INVALID
|
param_type := types.INVALID
|
||||||
if index < len(spec.args) {
|
if runtime_index < len(spec.args) {
|
||||||
param_type = spec.args[index]
|
param_type = spec.args[runtime_index]
|
||||||
}
|
}
|
||||||
append(&hir_locals, hir.Local{name = param.name, type = param_type, parameter = true})
|
append(&hir_locals, hir.Local{name = param.name, type = param_type, parameter = true})
|
||||||
append(&locals, Build_Local{name = param.name, type = param_type, id = local_id})
|
append(&locals, Build_Local{name = param.name, type = param_type, id = local_id})
|
||||||
append(¶ms, local_id)
|
append(¶ms, local_id)
|
||||||
|
runtime_index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC ||
|
problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC ||
|
||||||
@@ -7526,6 +7833,7 @@ check :: proc(
|
|||||||
defer {
|
defer {
|
||||||
for spec in checker.specs {
|
for spec in checker.specs {
|
||||||
delete(spec.args, allocator)
|
delete(spec.args, allocator)
|
||||||
|
delete(spec.comptime_values, allocator)
|
||||||
}
|
}
|
||||||
delete(checker.specs)
|
delete(checker.specs)
|
||||||
delete(checker.function_index, allocator)
|
delete(checker.function_index, allocator)
|
||||||
|
|||||||
@@ -201,6 +201,9 @@ lex :: proc(
|
|||||||
case '@':
|
case '@':
|
||||||
append_token(&stream, source_file, .At, cursor, cursor+1)
|
append_token(&stream, source_file, .At, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
case '$':
|
||||||
|
append_token(&stream, source_file, .Dollar, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
case '*':
|
case '*':
|
||||||
start := cursor
|
start := cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
|||||||
@@ -1577,6 +1577,7 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
_, comptime_value := allow(parser, .Dollar)
|
||||||
names: [dynamic]token.Token
|
names: [dynamic]token.Token
|
||||||
names.allocator = parser.module.allocator
|
names.allocator = parser.module.allocator
|
||||||
for {
|
for {
|
||||||
@@ -1596,7 +1597,12 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
|
|||||||
}
|
}
|
||||||
type_syntax := parse_type(parser)
|
type_syntax := parse_type(parser)
|
||||||
for name in names {
|
for name in names {
|
||||||
append(¶ms, ast.Param{name=name.symbol, span=name.span, type=type_syntax})
|
append(¶ms, ast.Param{
|
||||||
|
name=name.symbol,
|
||||||
|
span=name.span,
|
||||||
|
type=type_syntax,
|
||||||
|
comptime_value=comptime_value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
delete(names)
|
delete(names)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ Kind :: enum u8 {
|
|||||||
Range_Inclusive,
|
Range_Inclusive,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
At,
|
At,
|
||||||
|
Dollar,
|
||||||
Star,
|
Star,
|
||||||
Ampersand,
|
Ampersand,
|
||||||
Caret,
|
Caret,
|
||||||
|
|||||||
@@ -175,6 +175,35 @@ main func() void {}
|
|||||||
testing.expect_value(t, len(module.functions[0].params), 2)
|
testing.expect_value(t, len(module.functions[0].params), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_accepts_comptime_value_params :: proc(t: ^testing.T) {
|
||||||
|
text := `make func($N usize, value i32) i32 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
found_dollar := false
|
||||||
|
for tok in stream.items {
|
||||||
|
found_dollar = found_dollar || tok.kind == token.Kind.Dollar
|
||||||
|
}
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found_dollar)
|
||||||
|
testing.expect_value(t, len(module.functions[0].params), 2)
|
||||||
|
testing.expect(t, module.functions[0].params[0].comptime_value)
|
||||||
|
testing.expect(t, !module.functions[0].params[1].comptime_value)
|
||||||
|
}
|
||||||
|
|
||||||
@(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 {}
|
||||||
@@ -1945,6 +1974,116 @@ main func() i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
comptime_value_params_specialize_by_value_and_omit_runtime_args :: proc(t: ^testing.T) {
|
||||||
|
text := `make_array func($N usize) [N]u8 {
|
||||||
|
data [N]u8 = undefined
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
main func() void {
|
||||||
|
four [4]u8 :: make_array(4)
|
||||||
|
eight [8]u8 :: make_array(8)
|
||||||
|
again [4]u8 :: make_array(4)
|
||||||
|
_ = four
|
||||||
|
_ = eight
|
||||||
|
_ = again
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
make_specs := 0
|
||||||
|
for function in hir_module.functions {
|
||||||
|
if symbol.resolve(&symbols, function.name) == "make_array" {
|
||||||
|
make_specs += 1
|
||||||
|
testing.expect_value(t, len(function.params), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, make_specs, 2)
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "@bro__p0__make_array__cv4"))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "@bro__p0__make_array__cv8"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
comptime_value_params_diagnose_invalid_uses :: proc(t: ^testing.T) {
|
||||||
|
text := `make func($N usize) i32 {
|
||||||
|
return N
|
||||||
|
}
|
||||||
|
tiny func($N u8) i32 {
|
||||||
|
return N
|
||||||
|
}
|
||||||
|
bad_type func($T bool) void {}
|
||||||
|
bad_use func($N usize) void {
|
||||||
|
N = 1
|
||||||
|
_ = &N
|
||||||
|
}
|
||||||
|
main func() void {
|
||||||
|
x usize = 4
|
||||||
|
_ = make(x)
|
||||||
|
_ = make()
|
||||||
|
_ = make(1, 2)
|
||||||
|
_ = make(-1)
|
||||||
|
_ = tiny(300)
|
||||||
|
bad_use(4)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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_runtime_arg := false
|
||||||
|
found_missing := false
|
||||||
|
found_extra := false
|
||||||
|
found_negative := false
|
||||||
|
found_range := false
|
||||||
|
found_bad_type := false
|
||||||
|
found_assignment := false
|
||||||
|
found_address := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
message := diagnostic.message
|
||||||
|
found_runtime_arg = found_runtime_arg || strings.contains(message, "must be a compile-time integer expression")
|
||||||
|
found_missing = found_missing || strings.contains(message, "expects 1 arguments, got 0")
|
||||||
|
found_extra = found_extra || strings.contains(message, "expects 1 arguments, got 2")
|
||||||
|
found_negative = found_negative || strings.contains(message, "integer constant -1 does not fit in usize")
|
||||||
|
found_range = found_range || strings.contains(message, "integer constant 300 does not fit in u8")
|
||||||
|
found_bad_type = found_bad_type || strings.contains(message, "requires a concrete integer type")
|
||||||
|
found_assignment = found_assignment || strings.contains(message, "cannot assign comptime parameter 'N'")
|
||||||
|
found_address = found_address || strings.contains(message, "'&' requires an addressable location")
|
||||||
|
}
|
||||||
|
|
||||||
|
testing.expect(t, found_runtime_arg)
|
||||||
|
testing.expect(t, found_missing)
|
||||||
|
testing.expect(t, found_extra)
|
||||||
|
testing.expect(t, found_negative)
|
||||||
|
testing.expect(t, found_range)
|
||||||
|
testing.expect(t, found_bad_type)
|
||||||
|
testing.expect(t, found_assignment)
|
||||||
|
testing.expect(t, found_address)
|
||||||
|
}
|
||||||
|
|
||||||
@(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 {}
|
||||||
@@ -5305,6 +5444,16 @@ cross_package_generic_specializes_from_folded_argument :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 128)
|
testing.expect_value(t, state.exit_code, 128)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
comptime_value_params_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-comptime-value-params"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/comptime_value_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,32 @@
|
|||||||
|
make_array func($N usize) [N]u8 {
|
||||||
|
data [N]u8 = undefined
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
value func($N usize) usize {
|
||||||
|
return N
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
four [4]u8 :: make_array(4)
|
||||||
|
if four.len != 4 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if value(4) != 4 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
eight [8]u8 :: make_array(8)
|
||||||
|
if eight.len != 8 {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
if value(8) != 8 {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
again [4]u8 :: make_array(4)
|
||||||
|
if again.len != 4 {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user