bare func declaration identities (comptime)

This commit is contained in:
2026-07-18 14:05:55 +02:00
parent 85693e57e1
commit e889a99e55
8 changed files with 512 additions and 74 deletions
+5 -3
View File
@@ -155,7 +155,7 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
- 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 - explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
- comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and concrete function values; equal structural values and function declarations share specializations, while pointers, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity - comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities; equal structural values and aliases of one function declaration share specializations, while distinct declarations remain distinct and pointers, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity
- comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole - comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }` - forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed - comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
@@ -165,13 +165,15 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
- `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name - `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name
- 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
- native function pointer values and types with `@func(...) R`, fallible `@func(...) R ! E`, optional `?@func(...) R`, comptime specialization by declaration identity, and non-variadic native indirect calls when the target is not statically known - bare `func(...) R` and `c_func(...) R` values are comptime-only declaration identities; arrays, native records, optionals, and tagged unions containing one are also comptime-only and cannot enter runtime storage, ordinary ABI parameters/results, runtime globals, or C-layout records
- native function pointer values and types use `@func(...) R`, fallible `@func(...) R ! E`, and optional `?@func(...) R`; bare native identities implicitly materialize compatible pointers when a runtime pointer context requires one, but pointers never convert back to bare identities
- statically known bare identities and comptime-known pointers lower calls directly; native indirect calls remain available for runtime-selected non-variadic pointers
- 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
- imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, C `void*` as nullable `anyopaque` pointers, and pointers to opaque records - imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, C `void*` as nullable `anyopaque` pointers, and pointers to opaque records
- imported external C object variables, including mutable variables and immutable object globals - imported external C object variables, including mutable variables and immutable object globals
- object-like scalar and plain record/union macro constants - object-like scalar and plain record/union macro constants
- supported static inline C functions through generated external wrappers - supported static inline C functions through generated external wrappers
- C function pointer types, imported nullable callback typedefs, concrete `c_func` callback values, and postfix calls through non-null function pointers - C function pointer types, imported nullable callback typedefs, bare `c_func` identities with one-way pointer materialization, and postfix calls through non-null function pointers
- `brolang translate-c <header.h>... [--output-dir <dir>]` for native `.bro` bindings from supported C declarations, with package-wide declaration deduplication when writing multiple headers - `brolang translate-c <header.h>... [--output-dir <dir>]` for native `.bro` bindings from supported C declarations, with package-wide declaration deduplication when writing multiple headers
- `brolang --translate-c stdio.h` for offline bindings from Zig-bundled standard C headers - `brolang --translate-c stdio.h` for offline bindings from Zig-bundled standard C headers
- ordered linking of additional C sources, objects, archives, library paths, and libraries through compiler CLI options - ordered linking of additional C sources, objects, archives, library paths, and libraries through compiler CLI options
+10 -4
View File
@@ -144,15 +144,20 @@ call_mapper func(mapper native.Imported_Mapper) c_int {
} }
``` ```
Native Brolang function pointer values use `*func(...) R`, with fallible Native Brolang function pointer values use `@func(...) R`, with fallible
channels written on the result: channels written on the result:
```bro ```bro
call func(callback *func(value i32) i32, value i32) i32 { call func(callback @func(value i32) i32, value i32) i32 {
return callback(value) return callback(value)
} }
``` ```
Bare `func(...) R` and `c_func(...) R` values are comptime-only declaration
identities. They implicitly materialize compatible pointers in runtime pointer
contexts; pointers do not convert back to bare identities. Aggregates containing
bare identities are likewise comptime-only.
Bodyless manual and imported C functions may be variadic: Bodyless manual and imported C functions may be variadic:
```bro ```bro
@@ -226,11 +231,12 @@ 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
- Recursively stable comptime values—including booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and concrete function values—may be interleaved with runtime parameters, are erased from the ABI, and specialize from explicit arguments, declaration identity, or exact inference provenance - Recursively stable comptime values—including booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities—may be interleaved with runtime parameters, are erased from the ABI, and specialize from explicit arguments, declaration identity, or exact inference provenance
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`) - Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
- Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`) - Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`)
- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`/`errdefer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values - Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`/`errdefer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
- Native function pointer values and types (`*func(...) R`, `*func(...) R ! E`, `?*func(...) R`) - Comptime-only native and C function identities (`func(...) R`, `c_func(...) R`) with structural comptime-only propagation through aggregates
- Native function pointer values and types (`@func(...) R`, `@func(...) R ! E`, `?@func(...) R`) with implicit bare-to-pointer materialization
- Typed allocation/reallocation through `std/mem` and generic dynamic arrays through `std/arraylist` - Typed allocation/reallocation through `std/mem` and generic dynamic arrays through `std/arraylist`
- 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
+10 -4
View File
@@ -712,8 +712,10 @@
places, slicing, `.len`, `.ptr`, pointer captures, and pointer-param aliasing places, slicing, `.len`, `.ptr`, pointer captures, and pointer-param aliasing
- comptime storage pointers/slices cannot materialize as runtime memory; escaped - comptime storage pointers/slices cannot materialize as runtime memory; escaped
dead storage is rejected dead storage is rejected
- bare concrete non-comptime function names are values; native function pointer - bare concrete non-comptime function names are comptime-only declaration identities;
types use `@func(...) R` and fallible `@func(...) R ! E` native function pointer types use `@func(...) R` and fallible `@func(...) R ! E`
- bare identities implicitly materialize compatible runtime pointers, never the reverse;
aggregates containing bare identities remain comptime-only
- comptime-known native/bodyful `c_func` values can be called; bodyless/imported - comptime-known native/bodyful `c_func` values can be called; bodyless/imported
callbacks remain runtime-only callbacks remain runtime-only
- native function pointers are non-variadic v1; C variadic function pointers stay - native function pointers are non-variadic v1; C variadic function pointers stay
@@ -914,12 +916,16 @@
- `isize` and `usize` remain concrete pointer-sized types - `isize` and `usize` remain concrete pointer-sized types
43. comptime function parameters (implemented) 43. comptime function parameters (implemented)
- concrete native and C function values, function literals, and aggregates containing them - bare native and C function identities, function literals, and comptime-only aggregates
specialize by declaration identity rather than runtime address containing them specialize by declaration identity rather than runtime address
- repeated declarations reuse specializations, distinct declarations specialize separately, and - repeated declarations reuse specializations, distinct declarations specialize separately, and
function-valued parameters remain erased from the runtime ABI function-valued parameters remain erased from the runtime ABI
- statically known callback invocations lower to direct calls; runtime-selected function pointers - statically known callback invocations lower to direct calls; runtime-selected function pointers
remain indirect, and bodyless C declarations remain runtime-only during comptime execution remain indirect, and bodyless C declarations remain runtime-only during comptime execution
- runtime pointer contexts implicitly materialize a bare identity; pointer-to-identity conversion is
rejected, and bare-containing aggregates cannot enter runtime storage or ABI/C layouts
44. struct field defaults on declaration
## A word on unchecked casts ## A word on unchecked casts
+192 -39
View File
@@ -597,7 +597,8 @@ build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span
}) })
} }
if value.kind == .Function { if value.kind == .Function {
return build_function_value(checker, ast.Function_Id(u32(value.index)), span, expected) value_expected := expected if types.is_valid(expected) else value.type
return build_function_value(checker, ast.Function_Id(u32(value.index)), span, value_expected)
} }
if value.kind == .Array || value.kind == .Struct || value.kind == .Range { if value.kind == .Array || value.kind == .Struct || value.kind == .Range {
children := ct_child_slice(&checker.static_state, value) children := ct_child_slice(&checker.static_state, value)
@@ -1174,6 +1175,38 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_runtime_value(value, &checker.module.types) return types.is_runtime_value(value, &checker.module.types)
} }
is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
if depth > 256 || !types.is_valid(value) {
return false
}
if is_runtime_type(checker, value) {
return true
}
item, ok := types.node(&checker.module.types, value)
if !ok {
return false
}
if item.kind == .Function {
return true
}
if item.kind == .Array || item.kind == .Optional || item.kind == .Alias || item.kind == .Distinct {
return is_comptime_value_type(checker, item.child, depth+1)
}
if item.kind == .Struct || item.kind == .Union {
if !item.declared || item.opaque || item.c_layout ||
(item.kind == .Union && !types.is_tagged_union(value, &checker.module.types)) {
return false
}
for field in types.fields_for(&checker.module.types, value) {
if !types.is_void(field.type) && !is_comptime_value_type(checker, field.type, depth+1) {
return false
}
}
return true
}
return false
}
is_undefined_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool { is_undefined_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return false return false
@@ -2006,7 +2039,7 @@ call_mapping_semantically_valid :: proc(
break break
} }
} }
if !is_runtime_type(checker, actual) { if !is_runtime_type(checker, actual) && !can_implicitly_convert_type(checker, actual, declared) {
return false, fmt.aprintf( return false, fmt.aprintf(
"argument %d is not a runtime value", source_index+1, "argument %d is not a runtime value", source_index+1,
allocator=checker.allocator, allocator=checker.allocator,
@@ -2814,7 +2847,7 @@ resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, p
defer delete(fields, checker.allocator) defer delete(fields, checker.allocator)
for field, index in template_fields { for field, index in template_fields {
resolved := type_from_syntax(checker, field.type, pkg, file) resolved := type_from_syntax(checker, field.type, pkg, file)
if !is_runtime_type(checker, resolved) || types.is_void(resolved) { if (!is_runtime_type(checker, resolved) && !is_comptime_value_type(checker, resolved)) || types.is_void(resolved) {
if expr.tuple { if expr.tuple {
source.addf(checker.diagnostics, expr.span, "tuple element %d requires a concrete runtime type, got %s", index, type_label(checker, resolved)) source.addf(checker.diagnostics, expr.span, "tuple element %d requires a concrete runtime type, got %s", index, type_label(checker, resolved))
} else { } else {
@@ -3112,7 +3145,7 @@ function_value_signature :: proc(
return params, result, true return params, result, true
} }
function_pointer_type_for_template :: proc( function_type_for_template :: proc(
checker: ^Checker, checker: ^Checker,
template: ast.Function_Id, template: ast.Function_Id,
demanded: ^[dynamic]Spec_Id = nil, demanded: ^[dynamic]Spec_Id = nil,
@@ -3125,7 +3158,6 @@ function_pointer_type_for_template :: proc(
defer delete(params, checker.allocator) defer delete(params, checker.allocator)
function := checker.ast_module.functions[template] function := checker.ast_module.functions[template]
function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic) function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic)
pointer_type := types.pointer(&checker.module.types, function_type, false, false)
spec := INVALID_SPEC spec := INVALID_SPEC
if demanded == nil { if demanded == nil {
if demand_spec { if demand_spec {
@@ -3137,7 +3169,40 @@ function_pointer_type_for_template :: proc(
spec = find_spec(checker, template, params) spec = find_spec(checker, template, params)
mark_spec_demanded(checker, spec, demanded) mark_spec_demanded(checker, spec, demanded)
} }
return pointer_type, spec, spec != INVALID_SPEC || !demand_spec return function_type, spec, spec != INVALID_SPEC || !demand_spec
}
function_pointer_type_for_template :: proc(
checker: ^Checker,
template: ast.Function_Id,
demanded: ^[dynamic]Spec_Id = nil,
demand_spec := true,
) -> (types.Type, Spec_Id, bool) {
function_type, spec, ok := function_type_for_template(checker, template, demanded, demand_spec)
if !ok {
return types.INVALID, spec, false
}
return types.pointer(&checker.module.types, function_type, false, false), spec, true
}
function_expr_type_for_template :: proc(
checker: ^Checker,
template: ast.Function_Id,
expected: types.Type,
demanded: ^[dynamic]Spec_Id = nil,
) -> (types.Type, bool) {
function_type, _, ok := function_type_for_template(checker, template, demanded)
if !ok {
return types.INVALID, false
}
pointer_expected := expected
if types.is_optional(pointer_expected, &checker.module.types) {
pointer_expected = types.child_type(pointer_expected, &checker.module.types)
}
if types.can_coerce_function_pointer(function_type, pointer_expected, &checker.module.types) {
return pointer_expected, true
}
return function_type, true
} }
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool { contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
@@ -3358,7 +3423,7 @@ validate_declarations :: proc(checker: ^Checker) {
) )
} }
if !is_type_metatype_syntax(checker, param.type) && if !is_type_metatype_syntax(checker, param.type) &&
!is_runtime_type(checker, param_type) && param_type != types.RANGE { !is_comptime_value_type(checker, param_type) && param_type != types.RANGE {
checker.template_diagnostics[function_id] = source.addf( checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics, checker.diagnostics,
param.span, param.span,
@@ -3366,6 +3431,13 @@ validate_declarations :: proc(checker: ^Checker) {
symbol_text(checker, param.name), symbol_text(checker, param.name),
) )
} }
} else if !has_comptime && !signature_poisoned && types.is_comptime_only(param_type, &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
"parameter '%s' has a comptime-only type; prefix it with '$'",
symbol_text(checker, param.name),
)
} else if !has_comptime && !signature_poisoned { } else if !has_comptime && !signature_poisoned {
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type); if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
diagnostic != source.INVALID_DIAGNOSTIC { diagnostic != source.INVALID_DIAGNOSTIC {
@@ -3904,7 +3976,14 @@ validate_type_nodes :: proc(checker: ^Checker) {
// not enter this state and retain the existing validation below. // not enter this state and retain the existing validation below.
} else if comptime_meta { } else if comptime_meta {
// Reflection metadata is compile-time-only and may contain `type`. // Reflection metadata is compile-time-only and may contain `type`.
} else if !types.is_runtime_value(field.type, &checker.module.types) { } else if item.c_layout && !types.is_runtime_value(field.type, &checker.module.types) {
source.add(
checker.diagnostics,
source.Span{},
"c_struct fields must have C-layout-compatible types",
)
} else if !types.is_runtime_value(field.type, &checker.module.types) &&
!is_comptime_value_type(checker, field.type) {
source.add( source.add(
checker.diagnostics, checker.diagnostics,
source.Span{}, source.Span{},
@@ -4505,8 +4584,8 @@ infer_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
case .Function_Literal: case .Function_Literal:
template := ast.Function_Id(u32(expr.integer)) template := ast.Function_Id(u32(expr.integer))
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded) function_type, ok := function_expr_type_for_template(checker, template, frame.expected, demanded)
last = pointer_type if ok else types.INVALID last = function_type if ok else types.INVALID
_ = pop(&stack) _ = pop(&stack)
case .Name: case .Name:
last = types.INVALID last = types.INVALID
@@ -4614,9 +4693,9 @@ infer_expr :: proc(
if template != ast.INVALID_FUNCTION && if template != ast.INVALID_FUNCTION &&
len(checker.ast_module.functions[template].unsupported_reason) == 0 && len(checker.ast_module.functions[template].unsupported_reason) == 0 &&
checker.template_diagnostics[template] == source.INVALID_DIAGNOSTIC { checker.template_diagnostics[template] == source.INVALID_DIAGNOSTIC {
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded) function_type, ok := function_expr_type_for_template(checker, template, frame.expected, demanded)
if ok { if ok {
last = pointer_type last = function_type
} }
} }
} }
@@ -4631,7 +4710,7 @@ infer_expr :: proc(
case .Call: case .Call:
if expr.left != ast.INVALID_EXPR { if expr.left != ast.INVALID_EXPR {
callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types) callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) _, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok { if !ok {
last = types.INVALID last = types.INVALID
_ = pop(&stack) _ = pop(&stack)
@@ -4732,7 +4811,7 @@ infer_expr :: proc(
continue continue
} }
if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file, demanded); handled { if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file, demanded); handled {
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) _, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok { if !ok {
last = types.INVALID last = types.INVALID
_ = pop(&stack) _ = pop(&stack)
@@ -4760,6 +4839,19 @@ infer_expr :: proc(
callee_type := types.INVALID callee_type := types.INVALID
if !symbol.is_valid(expr.qualifier) { if !symbol.is_valid(expr.qualifier) {
callee_type = find_infer_local(locals, expr.name) callee_type = find_infer_local(locals, expr.name)
if !types.is_valid(callee_type) {
if binding, ok := current_static_binding(checker, expr.name); ok {
callee_type = binding.type
if binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
function_value := checker.static_state.values[binding.value]
if function_value.kind == .Function {
_, _, _ = function_pointer_type_for_template(
checker, ast.Function_Id(u32(function_value.index)), demanded,
)
}
}
}
}
if !types.is_valid(callee_type) { if !types.is_valid(callee_type) {
if value, ok := current_comptime_value(checker, expr.name); ok && value.kind == .Static { if value, ok := current_comptime_value(checker, expr.name); ok && value.kind == .Static {
callee_type = value.type callee_type = value.type
@@ -4780,7 +4872,7 @@ infer_expr :: proc(
callee_type = checker.global_types[global] callee_type = checker.global_types[global]
} }
} }
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) _, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok { if !ok {
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type) distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
@@ -6269,6 +6361,7 @@ can_implicitly_convert_type :: proc(checker: ^Checker, actual, expected: types.T
types.can_coerce_c_integer(actual, expected, checker.target) || types.can_coerce_c_integer(actual, expected, checker.target) ||
types.can_coerce_c_scalar(actual, expected, checker.target) || types.can_coerce_c_scalar(actual, expected, checker.target) ||
types.can_weaken_pointer(actual, expected, store) || types.can_weaken_pointer(actual, expected, store) ||
types.can_coerce_function_pointer(actual, expected, store) ||
types.can_weaken_slice(actual, expected, store) || types.can_weaken_slice(actual, expected, store) ||
types.can_decay_slice_c_string(actual, expected, store) || types.can_decay_slice_c_string(actual, expected, store) ||
types.can_decay_array_pointer(actual, expected, store) || types.can_decay_array_pointer(actual, expected, store) ||
@@ -6297,6 +6390,13 @@ coerce_expr :: proc(
if types.equal(actual, expected) { if types.equal(actual, expected) {
return expr_id return expr_id
} }
if types.can_coerce_function_pointer(actual, expected, &checker.module.types) {
expr := checker.module.exprs[expr_id]
if expr.kind == .Function {
expr.type = expected
return add_hir_expr(checker, expr)
}
}
if types.can_weaken_pointer(actual, expected, &checker.module.types) { if types.can_weaken_pointer(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{ return add_hir_expr(checker, hir.Expr{
kind=.Weaken_Pointer, kind=.Weaken_Pointer,
@@ -6355,6 +6455,7 @@ coerce_expr :: proc(
if types.is_optional(expected, &checker.module.types) { if types.is_optional(expected, &checker.module.types) {
child := types.child_type(expected, &checker.module.types) child := types.child_type(expected, &checker.module.types)
if types.equal(actual, child) || if types.equal(actual, child) ||
types.can_coerce_function_pointer(actual, child, &checker.module.types) ||
types.can_widen(actual, child) || types.can_widen(actual, child) ||
types.can_coerce_c_integer(actual, child, checker.target) || types.can_coerce_c_integer(actual, child, checker.target) ||
types.can_coerce_c_scalar(actual, child, checker.target) || types.can_coerce_c_scalar(actual, child, checker.target) ||
@@ -6933,14 +7034,16 @@ build_function_value :: proc(
} }
defer delete(params, checker.allocator) defer delete(params, checker.allocator)
function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic) function_type := types.function(&checker.module.types, params, result, function.c_abi, function.variadic)
pointer_type := types.pointer(&checker.module.types, function_type, false, false) value_type := function_type
expected_pointer := expected expected_pointer := expected
if types.is_optional(expected_pointer, &checker.module.types) { if types.is_optional(expected_pointer, &checker.module.types) {
expected_pointer = types.child_type(expected_pointer, &checker.module.types) expected_pointer = types.child_type(expected_pointer, &checker.module.types)
} }
if _, _, expected_function, ok := types.function_pointer(expected_pointer, &checker.module.types); ok && if _, _, expected_function, ok := types.function_pointer(expected_pointer, &checker.module.types); ok &&
types.equal(expected_function, function_type) { types.equal(expected_function, function_type) {
pointer_type = expected_pointer value_type = expected_pointer
} else if types.equal(expected, function_type) {
value_type = expected
} }
spec := find_spec(checker, template, params) spec := find_spec(checker, template, params)
if spec == INVALID_SPEC { if spec == INVALID_SPEC {
@@ -6950,14 +7053,14 @@ build_function_value :: proc(
"could not resolve callback specialization of '%s'", "could not resolve callback specialization of '%s'",
symbol_text(checker, function.name), symbol_text(checker, function.name),
) )
return invalid_hir_expr(checker, span, id, pointer_type) return invalid_hir_expr(checker, span, id, value_type)
} }
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)
return add_hir_expr(checker, hir.Expr{ return add_hir_expr(checker, hir.Expr{
kind=.Function, kind=.Function,
span=span, span=span,
type=pointer_type, type=value_type,
target=hir.function_ref(function_id), target=hir.function_ref(function_id),
left=hir.INVALID_EXPR, left=hir.INVALID_EXPR,
right=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
@@ -7917,6 +8020,15 @@ build_expr :: proc(
} }
expr := checker.ast_module.exprs[frame.expr] expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 { if frame.stage == 0 {
if expr.kind == .Name || expr.kind == .Field || expr.kind == .Index || expr.kind == .Unwrap {
if specialized, ok := try_build_specialization_expr(
checker, frame.expr, frame.expected, pkg, file,
); ok {
last = specialized
_ = pop(&stack)
continue
}
}
constant := Constant{} constant := Constant{}
_, static_name := current_static_binding(checker, expr.name) _, static_name := current_static_binding(checker, expr.name)
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name { if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
@@ -8257,12 +8369,20 @@ build_expr :: proc(
non_callable_global := false non_callable_global := false
if !symbol.is_valid(expr.qualifier) { if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok { if local, ok := find_build_local(locals, expr.name); ok {
if _, _, _, callable := types.function_pointer(local.type, &checker.module.types); callable { if _, _, _, callable := types.callable_function(local.type, &checker.module.types); callable {
callee = build_local_expr(checker, local, expr.span) callee = build_local_expr(checker, local, expr.span)
} else { } else {
non_callable = true non_callable = true
} }
} }
if callee == hir.INVALID_EXPR && !non_callable {
if binding, ok := current_static_binding(checker, expr.name); ok &&
binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
callee = build_static_value(
checker, checker.static_state.values[binding.value], expr.span, binding.type,
)
}
}
if callee == hir.INVALID_EXPR && !non_callable { if callee == hir.INVALID_EXPR && !non_callable {
if value, ok := current_comptime_value(checker, expr.name); if value, ok := current_comptime_value(checker, expr.name);
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE && ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
@@ -8275,7 +8395,7 @@ build_expr :: proc(
} }
if callee == hir.INVALID_EXPR && !non_callable { if callee == hir.INVALID_EXPR && !non_callable {
if global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)); global != ast.INVALID_GLOBAL { if global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)); global != ast.INVALID_GLOBAL {
if _, _, _, callable := types.function_pointer(checker.global_types[global], &checker.module.types); callable { if _, _, _, callable := types.callable_function(checker.global_types[global], &checker.module.types); callable {
callee = build_global_reference(checker, global, expr.span, global_reads) callee = build_global_reference(checker, global, expr.span, global_reads)
} else { } else {
non_callable = true non_callable = true
@@ -8322,7 +8442,7 @@ build_expr :: proc(
id := source.INVALID_DIAGNOSTIC id := source.INVALID_DIAGNOSTIC
if non_callable { if non_callable {
id = add_call_resolution_diagnostic(checker, expr, target_pkg, file) if non_callable_global else id = add_call_resolution_diagnostic(checker, expr, target_pkg, file) if non_callable_global else
source.add(checker.diagnostics, expr.span, "call target is not a function pointer") source.add(checker.diagnostics, expr.span, "call target is not callable")
} else { } else {
id = add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name) id = add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC { if id == source.INVALID_DIAGNOSTIC {
@@ -8333,10 +8453,10 @@ build_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
_, function_item, function_type, _ := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types) _, function_item, function_type, _ := types.callable_function(checker.module.exprs[callee].type, &checker.module.types)
if !valid_callable_arity(function_item, len(expr.args)) { if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else message := "function expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d" "function expects %d arguments, got %d"
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args)) id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
last = invalid_hir_expr(checker, expr.span, id) last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack) _ = pop(&stack)
@@ -8705,16 +8825,16 @@ build_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types) _, function_item, function_type, ok := types.callable_function(checker.module.exprs[callee].type, &checker.module.types)
if !ok { if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer") id := source.add(checker.diagnostics, expr.span, "call target is not callable")
last = invalid_hir_expr(checker, expr.span, id) last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
if !valid_callable_arity(function_item, len(expr.args)) { if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else message := "function expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d" "function expects %d arguments, got %d"
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args)) id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
last = invalid_hir_expr(checker, expr.span, id) last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack) _ = pop(&stack)
@@ -8739,7 +8859,7 @@ build_expr :: proc(
if frame.arg_index+1 < len(expr.args) { if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1 next := frame.arg_index+1
callee_type := checker.module.exprs[frame.left].type callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, _ := types.function_pointer(callee_type, &checker.module.types) _, function_item, function_type, _ := types.callable_function(callee_type, &checker.module.types)
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, next) arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, next)
if !is_runtime_type(checker, arg_expected) { if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID arg_expected = types.INVALID
@@ -8760,9 +8880,9 @@ build_expr :: proc(
continue continue
} }
callee_type := checker.module.exprs[frame.left].type callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) _, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
if !ok { if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer") id := source.add(checker.diagnostics, expr.span, "call target is not callable")
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, id) last = invalid_hir_expr(checker, expr.span, id)
@@ -8789,7 +8909,7 @@ build_expr :: proc(
} }
result := function_item.child result := function_item.child
if !types.is_valid(result) { if !types.is_valid(result) {
id := source.add(checker.diagnostics, expr.span, "could not resolve function pointer result type") id := source.add(checker.diagnostics, expr.span, "could not resolve callable result type")
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, id) last = invalid_hir_expr(checker, expr.span, id)
@@ -9524,6 +9644,21 @@ build_block :: proc(
value_type = types.INVALID value_type = types.INVALID
} }
} }
if types.is_comptime_only(value_type, &checker.module.types) {
id := source.addf(
checker.diagnostics,
statement.span,
"local '%s' has a comptime-only type and cannot be stored at runtime",
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
}
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found { if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
id := source.addf( id := source.addf(
checker.diagnostics, statement.span, checker.diagnostics, statement.span,
@@ -12145,6 +12280,14 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
signature_diagnostic := source.INVALID_DIAGNOSTIC signature_diagnostic := source.INVALID_DIAGNOSTIC
unresolved_result := !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) unresolved_result := !types.is_void(spec.result) && !is_runtime_type(checker, spec.result)
if unresolved_result { if unresolved_result {
if types.is_comptime_only(spec.result, &checker.module.types) {
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"function '%s' has a comptime-only result and cannot return it through the runtime ABI",
symbol_text(checker, function.name),
)
}
checker.specs[id].result = types.I64 checker.specs[id].result = types.I64
spec.result = types.I64 spec.result = types.I64
} }
@@ -12554,12 +12697,22 @@ build_globals :: proc(checker: ^Checker) {
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type) expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
} }
if diagnostic == source.INVALID_DIAGNOSTIC && !is_runtime_type(checker, global_type) { if diagnostic == source.INVALID_DIAGNOSTIC && !is_runtime_type(checker, global_type) {
diagnostic = source.addf( if types.is_comptime_only(global_type, &checker.module.types) ||
checker.diagnostics, types.is_comptime_only(declared, &checker.module.types) {
global.span, diagnostic = source.addf(
"could not resolve a concrete type for global '%s'", checker.diagnostics,
symbol_text(checker, global.name), global.span,
) "global '%s' has a comptime-only type and cannot be stored at runtime",
symbol_text(checker, global.name),
)
} else {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"could not resolve a concrete type for global '%s'",
symbol_text(checker, global.name),
)
}
global_type = types.I64 global_type = types.I64
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type) expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
} }
+50 -11
View File
@@ -624,9 +624,15 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
return ct_add_value(state, value), true return ct_add_value(state, value), true
} }
if value.kind == .Function { if value.kind == .Function {
_, _, actual_function, actual_ok := types.function_pointer(value.type, store) actual_item, actual_ok := types.node(store, value.type)
_, _, expected_function, expected_ok := types.function_pointer(expected, store) _, _, expected_function, expected_ok := types.function_pointer(expected, store)
if actual_ok && expected_ok && types.equal(actual_function, expected_function) { if actual_ok && actual_item.kind == .Function && expected_ok &&
types.equal(value.type, expected_function) {
value.type = expected
return ct_add_value(state, value), true
}
_, _, actual_function, actual_pointer := types.function_pointer(value.type, store)
if actual_pointer && expected_ok && types.equal(actual_function, expected_function) {
value.type = expected value.type = expected
return ct_add_value(state, value), true return ct_add_value(state, value), true
} }
@@ -928,7 +934,8 @@ ct_materialize_value :: proc(
} }
return invalid_hir_expr(checker, span, state.diagnostic, value.type) return invalid_hir_expr(checker, span, state.diagnostic, value.type)
case .Function: case .Function:
return build_function_value(checker, ast.Function_Id(u32(value.index)), span, expected) value_expected := expected if types.is_valid(expected) else value.type
return build_function_value(checker, ast.Function_Id(u32(value.index)), span, value_expected)
case .None: case .None:
return add_hir_expr(checker, hir.Expr{ return add_hir_expr(checker, hir.Expr{
kind=.None, span=span, type=value.type, kind=.None, span=span, type=value.type,
@@ -1076,16 +1083,20 @@ ct_eval_expr :: proc(
if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) { if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) {
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file)) template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
if template != ast.INVALID_FUNCTION { if template != ast.INVALID_FUNCTION {
pointer_type, _, function_ok := function_pointer_type_for_template( function_type, _, function_ok := function_type_for_template(
checker, checker,
template, template,
state.demanded, state.demanded,
state.demanded != nil, state.demanded != nil,
) )
if function_ok { if function_ok {
return ct_add_value(state, Ct_Value{ id := ct_add_value(state, Ct_Value{
kind=.Function, type=pointer_type, index=u64(template), kind=.Function, type=function_type, index=u64(template),
}), ct_flow(.Normal), true })
if types.is_valid(expected) {
return ct_coerce_expr_value(state, id, expected, expr.span)
}
return id, ct_flow(.Normal), true
} }
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "function '%s' is not comptime-callable as a value", symbol_text(checker, expr.name)) return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "function '%s' is not comptime-callable as a value", symbol_text(checker, expr.name))
} }
@@ -1099,7 +1110,7 @@ ct_eval_expr :: proc(
return ct_eval_expr(state, g.expr, global_expected, depth+1) return ct_eval_expr(state, g.expr, global_expected, depth+1)
case .Function_Literal: case .Function_Literal:
template := ast.Function_Id(u32(expr.integer)) template := ast.Function_Id(u32(expr.integer))
pointer_type, _, ok := function_pointer_type_for_template( function_type, _, ok := function_type_for_template(
checker, checker,
template, template,
state.demanded, state.demanded,
@@ -1110,9 +1121,13 @@ ct_eval_expr :: proc(
state, .Not_Comptime, expr.span, "function literal is not comptime-callable as a value", state, .Not_Comptime, expr.span, "function literal is not comptime-callable as a value",
) )
} }
return ct_add_value(state, Ct_Value{ id := ct_add_value(state, Ct_Value{
kind=.Function, type=pointer_type, index=u64(template), kind=.Function, type=function_type, index=u64(template),
}), ct_flow(.Normal), true })
if types.is_valid(expected) {
return ct_coerce_expr_value(state, id, expected, expr.span)
}
return id, ct_flow(.Normal), true
case .Comptime: case .Comptime:
if expr.left != ast.INVALID_EXPR { if expr.left != ast.INVALID_EXPR {
return ct_eval_expr(state, expr.left, expected, depth+1) return ct_eval_expr(state, expr.left, expected, depth+1)
@@ -4164,3 +4179,27 @@ build_comptime_expr :: proc(
} }
return invalid_hir_expr(checker, expr.span, diagnostic, expected) return invalid_hir_expr(checker, expr.span, diagnostic, expected)
} }
try_build_specialization_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> (hir.Expr_Id, bool) {
if len(checker.current_comptime_values) == 0 && len(checker.static_bindings) == 0 ||
expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return hir.INVALID_EXPR, false
}
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
defer ct_state_destroy(&state)
value, flow, ok := ct_eval_expr(&state, expr_id, expected, 0)
if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) {
return hir.INVALID_EXPR, false
}
static := state.values[value]
if static.kind != .Function {
return hir.INVALID_EXPR, false
}
return ct_materialize_value(&state, value, checker.ast_module.exprs[expr_id].span, expected), true
}
+43 -1
View File
@@ -957,6 +957,33 @@ is_optional_pointer :: proc(value: Type, store: ^Store) -> bool {
return ok && item.kind == .Optional && is_pointer(item.child, store) return ok && item.kind == .Optional && is_pointer(item.child, store)
} }
is_comptime_only :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 {
return false
}
item, ok := node(store, value)
if !ok {
return false
}
if item.kind == .Function {
return true
}
if item.kind == .Pointer || item.kind == .Slice || item.kind == .Range || item.kind == .Fallible {
return false
}
if item.kind == .Array || item.kind == .Optional || item.kind == .Distinct || item.kind == .Enum || item.kind == .Alias {
return is_comptime_only(item.child, store, depth+1)
}
if item.kind == .Struct || item.kind == .Union {
for field in fields_for(store, value) {
if is_comptime_only(field.type, store, depth+1) {
return true
}
}
}
return false
}
is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool { is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 { if depth > 256 {
return false return false
@@ -981,7 +1008,8 @@ is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
} }
if value_kind == .Struct || value_kind == .Union { if value_kind == .Struct || value_kind == .Union {
item, ok := node(store, value) item, ok := node(store, value)
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0) return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0) &&
!is_comptime_only(value, store)
} }
if value_kind == .Fallible { if value_kind == .Fallible {
item, ok := node(store, value) item, ok := node(store, value)
@@ -1265,6 +1293,20 @@ function_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, function_
return pointer_node, function_node, pointer_node.child, true return pointer_node, function_node, pointer_node.child, true
} }
callable_function :: proc(value: Type, store: ^Store) -> (pointer_item, function_item: Node, function_type: Type, ok: bool) {
item, item_ok := node(store, value)
if item_ok && item.kind == .Function {
return {}, item, value, true
}
return function_pointer(value, store)
}
can_coerce_function_pointer :: proc(actual, expected: Type, store: ^Store) -> bool {
actual_item, actual_ok := node(store, actual)
_, _, expected_function, expected_ok := function_pointer(expected, store)
return actual_ok && actual_item.kind == .Function && expected_ok && equal(actual, expected_function)
}
replace_pointer_child :: proc(store: ^Store, value, child: Type) -> (Type, bool) { replace_pointer_child :: proc(store: ^Store, value, child: Type) -> (Type, bool) {
item, ok := node(store, value) item, ok := node(store, value)
if !ok { if !ok {
+162 -8
View File
@@ -2829,17 +2829,21 @@ main func() void {
@(test) @(test)
comptime_function_parameters_specialize_and_lower_directly :: proc(t: ^testing.T) { comptime_function_parameters_specialize_and_lower_directly :: proc(t: ^testing.T) {
text := `Callback_Config :: struct { call @func(value i32) i32 } text := `Callback_Config :: struct { call func(value i32) i32 }
Callback_Choice :: union(enum) {
call func(value i32) i32
empty void
}
increment func(value i32) i32 { return value + 1 } increment func(value i32) i32 { return value + 1 }
decrement func(value i32) i32 { return value - 1 } decrement func(value i32) i32 { return value - 1 }
external c_func(value i32) i32 external c_func(value i32) i32
apply func($callback @func(value i32) i32, value i32) i32 { apply func($callback func(value i32) i32, value i32) i32 {
return callback(value) return callback(value)
} }
apply_c func($callback *c_func(value i32) i32, value i32) i32 { apply_c func($callback c_func(value i32) i32, value i32) i32 {
return callback(value) return callback(value)
} }
@@ -2847,6 +2851,33 @@ apply_config func($config Callback_Config, value i32) i32 {
return config.call(value) return config.call(value)
} }
apply_array func($callbacks [2]func(value i32) i32, value i32) i32 {
return callbacks[0](value) + callbacks[1](value)
}
apply_optional func($callback ?func(value i32) i32, value i32) i32 {
return callback?(value)
}
apply_choice func($choice Callback_Choice, value i32) i32 {
return match choice {
.call |callback|: callback(value)
.empty: value
}
}
apply_pointer func($callback @func(value i32) i32, value i32) i32 {
return callback(value)
}
call_pointer func(callback @func(value i32) i32, value i32) i32 {
return callback(value)
}
materialize func($callback func(value i32) i32, value i32) i32 {
return call_pointer(callback, value)
}
main func() i32 { main func() i32 {
a i32 :: apply(increment, 1) a i32 :: apply(increment, 1)
b i32 :: apply(increment, 2) b i32 :: apply(increment, 2)
@@ -2854,7 +2885,12 @@ main func() i32 {
d i32 :: apply(func(value i32) i32 { return value + 2 }, 4) d i32 :: apply(func(value i32) i32 { return value + 2 }, 4)
e i32 :: apply_c(external, 5) e i32 :: apply_c(external, 5)
f i32 :: apply_config(Callback_Config {call = increment}, 6) f i32 :: apply_config(Callback_Config {call = increment}, 6)
return a + b + c + d + e + f g i32 :: apply_array([increment, decrement], 7)
h i32 :: apply_optional(increment, 8)
i i32 :: apply_choice(Callback_Choice {call = increment}, 9)
j i32 :: apply_pointer(increment, 10)
k i32 :: materialize(increment, 11)
return a + b + c + d + e + f + g + h + i + j + k
} }
` `
stable_names: [dynamic]string stable_names: [dynamic]string
@@ -2875,11 +2911,21 @@ main func() i32 {
apply_count := 0 apply_count := 0
callback_specializations := 0 callback_specializations := 0
found_materialization := false
for function in ir_module.functions { for function in ir_module.functions {
plain_apply := strings.contains(function.link_name, "bro__p0__apply__") plain_apply := strings.contains(function.link_name, "bro__p0__apply__")
callback_specialization := plain_apply || callback_specialization := plain_apply ||
strings.contains(function.link_name, "bro__p0__apply_c__") || strings.contains(function.link_name, "bro__p0__apply_c__") ||
strings.contains(function.link_name, "bro__p0__apply_config__") strings.contains(function.link_name, "bro__p0__apply_config__") ||
strings.contains(function.link_name, "bro__p0__apply_array__") ||
strings.contains(function.link_name, "bro__p0__apply_optional__") ||
strings.contains(function.link_name, "bro__p0__apply_choice__") ||
strings.contains(function.link_name, "bro__p0__apply_pointer__")
if strings.contains(function.link_name, "bro__p0__materialize__") {
for instruction in function.instructions {
found_materialization = found_materialization || instruction.op == .Function_Address
}
}
if !callback_specialization { if !callback_specialization {
continue continue
} }
@@ -2906,7 +2952,8 @@ main func() i32 {
testing.expect_value(t, len(diagnostics.items), 0) testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, apply_count, 3) testing.expect_value(t, apply_count, 3)
testing.expect_value(t, callback_specializations, 5) testing.expect_value(t, callback_specializations, 9)
testing.expect(t, found_materialization)
ir.destroy_module(&ir_module) ir.destroy_module(&ir_module)
hir.destroy_module(&hir_module) hir.destroy_module(&hir_module)
@@ -2917,6 +2964,110 @@ main func() i32 {
} }
} }
@(test)
comptime_only_function_identities_reject_runtime_storage_and_abi_use :: proc(t: ^testing.T) {
text := `Callback :: alias func(value i32) i32
Config :: struct { callback Callback }
Bad_C :: c_struct { callback c_func(value i32) i32 }
increment func(value i32) i32 { return value + 1 }
bad_param func(callback Callback) i32 { return callback(1) }
bad_config func(config Config) i32 { return config.callback(1) }
bad_result func() Callback { return increment }
stored Callback = increment
main func() void {
local Callback = increment
_ = bad_result()
}
`
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_param := false
found_config := false
found_result := false
found_global := false
found_local := false
found_c_field := false
for diagnostic in diagnostics.items {
found_param = found_param || strings.contains(diagnostic.message, "parameter 'callback' has a comptime-only type")
found_config = found_config || strings.contains(diagnostic.message, "parameter 'config' has a comptime-only type")
found_result = found_result || strings.contains(diagnostic.message, "has a comptime-only result")
found_global = found_global || strings.contains(diagnostic.message, "global 'stored' has a comptime-only type")
found_local = found_local || strings.contains(diagnostic.message, "local 'local' has a comptime-only type")
found_c_field = found_c_field || strings.contains(diagnostic.message, "c_struct fields must have C-layout-compatible types")
}
testing.expect(t, found_param)
testing.expect(t, found_config)
testing.expect(t, found_result)
testing.expect(t, found_global)
testing.expect(t, found_local)
testing.expect(t, found_c_field)
}
@(test)
function_pointers_do_not_coerce_back_to_bare_identities :: proc(t: ^testing.T) {
text := `Callback :: alias func(value i32) i32
increment func(value i32) i32 { return value + 1 }
apply func($callback Callback, value i32) i32 { return callback(value) }
pointer @func(value i32) i32 :: increment
main func() i32 { return apply(pointer, 1) }
`
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 := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "comptime argument")
}
testing.expect(t, found)
}
@(test)
bodyless_c_function_identity_is_not_comptime_executable :: proc(t: ^testing.T) {
text := `external c_func(value i32) i32
apply_c func($callback c_func(value i32) i32, value i32) i32 { return callback(value) }
answer :: $apply_c(external, 1)
main func() i32 { return answer }
`
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 := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "runtime-only")
}
testing.expect(t, found)
}
@(test) @(test)
milestone_37_expand_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) { milestone_37_expand_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) {
text := `main func() void { text := `main func() void {
@@ -4732,7 +4883,7 @@ main func() void {
found := false found := false
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "call target is not a function pointer") found = found || strings.contains(diagnostic.message, "call target is not callable")
} }
testing.expect(t, found) testing.expect(t, found)
} }
@@ -8591,10 +8742,12 @@ Point :: alias facade.RenamedPoint
counter :: alias facade.counter counter :: alias facade.counter
answer :: alias facade.answer answer :: alias facade.answer
` `
app_text := `dep :: import "../dep" app_text := `dep :: import "../dep"
facade :: import "../facade" facade :: import "../facade"
top :: import "../top" top :: import "../top"
apply func($callback func() i32) i32 { return callback() }
main func() i32 { main func() i32 {
box top.Box(i32) :: top.Box(i32) { value = 2 } box top.Box(i32) :: top.Box(i32) { value = 2 }
point top.Point :: top.Point { value = 3 } point top.Point :: top.Point { value = 3 }
@@ -8603,6 +8756,7 @@ main func() i32 {
top.counter = 7 top.counter = 7
if box.value != 2 or point.value != 3 { return 1 } if box.value != 2 or point.value != 3 { return 1 }
if scalar != 5 or top.answer() != 40 or facade.local_answer() != 40 or dep.counter != 7 { return 2 } if scalar != 5 or top.answer() != 40 or facade.local_answer() != 40 or dep.counter != 7 { return 2 }
if apply(top.answer) != 40 or apply(facade.answer) != 40 { return 3 }
_ = maybe _ = maybe
return 0 return 0
} }
+40 -4
View File
@@ -10,7 +10,12 @@ Point :: struct {
} }
Callback_Config :: struct { Callback_Config :: struct {
call @func(value i32) i32 call func(value i32) i32
}
Callback_Choice :: union(enum) {
call func(value i32) i32
empty void
} }
Box :: union(enum) { Box :: union(enum) {
@@ -90,11 +95,11 @@ decrement_c c_func(value i32) i32 {
return value - 1 return value - 1
} }
apply_comptime func($callback @func(value i32) i32, value i32) i32 { apply_comptime func($callback func(value i32) i32, value i32) i32 {
return callback(value) return callback(value)
} }
apply_comptime_c func($callback *c_func(value i32) i32, value i32) i32 { apply_comptime_c func($callback c_func(value i32) i32, value i32) i32 {
return callback(value) return callback(value)
} }
@@ -102,10 +107,29 @@ apply_comptime_config func($config Callback_Config, value i32) i32 {
return config.call(value) return config.call(value)
} }
apply_comptime_array func($callbacks [2]func(value i32) i32, value i32) i32 {
return callbacks[0](value) + callbacks[1](value)
}
apply_comptime_optional func($callback ?func(value i32) i32, value i32) i32 {
return callback?(value)
}
apply_comptime_choice func($choice Callback_Choice, value i32) i32 {
return match choice {
.call |callback|: callback(value)
.empty: value
}
}
call_native func(callback @func(value i32) i32, value i32) i32 { call_native func(callback @func(value i32) i32, value i32) i32 {
return callback(value) return callback(value)
} }
materialize_callback func($callback func(value i32) i32, value i32) i32 {
return call_native(callback, value)
}
call_fallible func(callback @func(flag bool) i32 ! Error, flag bool) i32 ! Error { call_fallible func(callback @func(flag bool) i32 ! Error, flag bool) i32 ! Error {
return try callback(flag) return try callback(flag)
} }
@@ -208,6 +232,9 @@ main func() i32 {
comptime_literal i32 :: $apply_comptime(func(value i32) i32 { return value + 2 }, 12) comptime_literal i32 :: $apply_comptime(func(value i32) i32 { return value + 2 }, 12)
comptime_c_callback i32 :: $apply_comptime_c(decrement_c, 15) comptime_c_callback i32 :: $apply_comptime_c(decrement_c, 15)
comptime_config i32 :: $apply_comptime_config(Callback_Config {call = increment}, 15) comptime_config i32 :: $apply_comptime_config(Callback_Config {call = increment}, 15)
comptime_array i32 :: $apply_comptime_array([increment, double], 3)
comptime_optional i32 :: $apply_comptime_optional(increment, 16)
comptime_choice i32 :: $apply_comptime_choice(Callback_Choice {call = double}, 9)
fallible_ok i32 :: $call_fallible(may_fail, false) catch 0 fallible_ok i32 :: $call_fallible(may_fail, false) catch 0
fallible_err i32 :: $call_fallible(may_fail, true) catch |e| { fallible_err i32 :: $call_fallible(may_fail, true) catch |e| {
result i32 :: match e { result i32 :: match e {
@@ -268,7 +295,8 @@ main func() i32 {
return 17 return 17
} }
if comptime_callback != 13 or comptime_literal != 14 or if comptime_callback != 13 or comptime_literal != 14 or
comptime_c_callback != 14 or comptime_config != 16 { comptime_c_callback != 14 or comptime_config != 16 or
comptime_array != 10 or comptime_optional != 17 or comptime_choice != 18 {
return 18 return 18
} }
if apply_comptime(increment, 20) != 21 or apply_comptime(increment, 21) != 22 or if apply_comptime(increment, 20) != 21 or apply_comptime(increment, 21) != 22 or
@@ -282,5 +310,13 @@ main func() i32 {
apply_comptime_config(Callback_Config {call = increment}, 20) != 21 { apply_comptime_config(Callback_Config {call = increment}, 20) != 21 {
return 21 return 21
} }
if apply_comptime_array([increment, double], 4) != 13 or
apply_comptime_optional(increment, 4) != 5 or
apply_comptime_choice(Callback_Choice {call = double}, 4) != 8 {
return 22
}
if materialize_callback(increment, 30) != 31 {
return 23
}
return 0 return 0
} }