Compare commits
3 Commits
0706188b98
...
0a424ec6d2
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a424ec6d2 | |||
| c914083102 | |||
| 2dea7711f2 |
+4
-2
@@ -57,6 +57,8 @@ roadmap and milestone history.
|
||||
- demand-monomorphized Brolang and C-ABI functions
|
||||
- integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value and omitted from the runtime ABI
|
||||
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
|
||||
- leading comptime type/integer parameters may be omitted when uniquely recoverable from runtime argument types or the immediate expected result; explicit calls remain valid
|
||||
- comptime parameters must form one leading prefix before all runtime parameters
|
||||
- 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
|
||||
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
|
||||
@@ -75,7 +77,7 @@ roadmap and milestone history.
|
||||
|
||||
### standard packages
|
||||
|
||||
- `std/mem` allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
|
||||
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
|
||||
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
||||
|
||||
### compiler behavior
|
||||
@@ -94,7 +96,7 @@ roadmap and milestone history.
|
||||
- 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
|
||||
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
|
||||
- recursive type factories, type reflection, inferred type arguments, and type-producing unions/enums
|
||||
- recursive type factories, type reflection, and type-producing unions/enums
|
||||
- broader Zig-style pointer/result casts beyond V1 `ptr_cast(T, ptr)`
|
||||
- sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism
|
||||
- backed/C enum composition and must-consume fallible linting
|
||||
|
||||
@@ -177,7 +177,7 @@ Current prototype features:
|
||||
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
|
||||
- Qualified imported globals and functions with package-aware symbol mangling
|
||||
- Demand-monomorphized Brolang and C-ABI functions
|
||||
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by comptime argument
|
||||
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by explicit or uniquely inferred leading comptime arguments
|
||||
- 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)`)
|
||||
- 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
|
||||
|
||||
@@ -754,15 +754,47 @@
|
||||
zero-count, zero-sized-type, and alignment handling
|
||||
- `std/arraylist.ArrayList(T)` exposes `items`, `capacity`, and `allocator`, with fallible
|
||||
reserve/append, roughly 1.5x growth from 8, clear-without-free, and reusable deinit
|
||||
- deferred: recursive factories, reflection, inferred type arguments, type-producing
|
||||
- deferred: recursive factories, reflection, type-producing
|
||||
unions/enums, pop/insert/remove/shrink/clone container operations
|
||||
|
||||
31. threading generic/polymorphic type information everywhere (init, deinit, etc.) might be annoying and verbose. consider whether generic structs could fit nicely to avoid this.
|
||||
31. generic container ergonomics (spike completed; no language change)
|
||||
- type factories already provide the important half of generic structs: `ArrayList(T)` is a
|
||||
cached, concrete nominal type whose layout contains `T`; no type information is carried at
|
||||
runtime
|
||||
- the verbosity comes from free functions repeating explicit comptime type arguments
|
||||
(`deinit(i32, &values)`, `append(i32, &values, value)`), not from a missing generic data model
|
||||
- do not add functions inside structs, implicit `Self`, associated lookup, or per-value runtime
|
||||
type metadata for this; those features would add a second namespace/member model without
|
||||
improving layout or specialization
|
||||
- the smallest fitting feature is call-local inference of omitted comptime type arguments:
|
||||
```
|
||||
values ArrayList(i32) = arraylist.init(mem.c_allocator)
|
||||
defer arraylist.deinit(&values)
|
||||
try arraylist.append(&values, 42)
|
||||
```
|
||||
`T` comes from the expected result for `init` and from the concrete receiver argument for the
|
||||
other calls
|
||||
- keep functions package-scoped and keep the explicit form valid; this preserves simple name
|
||||
resolution and gives ambiguous calls an escape hatch
|
||||
|
||||
31.5. inferred leading comptime parameters (implemented)
|
||||
- a native call may omit its complete leading `$T type` / integer comptime prefix when every
|
||||
value is uniquely recoverable from runtime argument types and/or the immediate expected result
|
||||
- inference structurally matches direct type parameters, pointers/slices/arrays/optionals/
|
||||
fallibles/functions, direct array counts, and canonical generated type-factory provenance;
|
||||
forwarding/non-invertible factories keep the explicit spelling
|
||||
- concrete evidence is exact; contextual numeric constants are weak evidence and are rebuilt with
|
||||
the resolved parameter type before ordinary coercion
|
||||
- calls are all-explicit or all-inferred (no partial prefix omission); unconstrained/conflicting
|
||||
values diagnose with the explicit call as the escape hatch
|
||||
- comptime parameters must form one leading prefix before every runtime parameter
|
||||
- the existing specialization/HIR/LLVM ABI is unchanged; `std/mem` and `std/arraylist` now use the
|
||||
inferred form where their arguments or result provide enough information
|
||||
|
||||
32. disallow arbitrary integer division
|
||||
- take inspiration from zig
|
||||
- see also below for a word on unchecked casts
|
||||
- the user should be explicit about what they mean with integer division (e.g. `div`, `rem`, `trunc`)
|
||||
- the user should be explicit about what they mean with integer division (e.g. `div`, `rem`)
|
||||
|
||||
## A word on unchecked casts
|
||||
|
||||
|
||||
+629
-51
@@ -143,6 +143,12 @@ Generated_Type_Entry :: struct {
|
||||
result: types.Type,
|
||||
}
|
||||
|
||||
Type_Factory_Origin :: struct {
|
||||
result: types.Type,
|
||||
template: ast.Function_Id,
|
||||
values: []Comptime_Value,
|
||||
}
|
||||
|
||||
Checker :: struct {
|
||||
ast_module: ^ast.Module,
|
||||
diagnostics: ^source.Diagnostics,
|
||||
@@ -184,6 +190,7 @@ Checker :: struct {
|
||||
current_comptime_values: []Comptime_Value,
|
||||
type_factories: [dynamic]Type_Factory_Entry,
|
||||
generated_types: [dynamic]Generated_Type_Entry,
|
||||
type_factory_origins: [dynamic]Type_Factory_Origin,
|
||||
target: target.Target,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
@@ -1105,6 +1112,410 @@ valid_call_arity :: proc(function: ast.Function, count: int) -> bool {
|
||||
return count >= len(function.params) if function.variadic else count == len(function.params)
|
||||
}
|
||||
|
||||
Call_Argument_Mode :: enum u8 {
|
||||
Invalid,
|
||||
Explicit,
|
||||
Inferred,
|
||||
}
|
||||
|
||||
comptime_prefix_count :: proc(function: ast.Function) -> (int, bool) {
|
||||
count := 0
|
||||
for param in function.params {
|
||||
if param.comptime_value {
|
||||
count += 1
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
for param in function.params[count:] {
|
||||
if param.comptime_value {
|
||||
return count, false
|
||||
}
|
||||
}
|
||||
return count, count > 0
|
||||
}
|
||||
|
||||
call_argument_mode :: proc(function: ast.Function, count: int) -> (Call_Argument_Mode, int) {
|
||||
if valid_call_arity(function, count) {
|
||||
return .Explicit, 0
|
||||
}
|
||||
prefix, inferable := comptime_prefix_count(function)
|
||||
if inferable && !function.c_abi && !function.variadic && count == len(function.params)-prefix {
|
||||
return .Inferred, prefix
|
||||
}
|
||||
return .Invalid, prefix
|
||||
}
|
||||
|
||||
call_param_index :: proc(mode: Call_Argument_Mode, prefix, source_index: int) -> int {
|
||||
return source_index+prefix if mode == .Inferred else source_index
|
||||
}
|
||||
|
||||
next_runtime_call_arg :: proc(function: ast.Function, mode: Call_Argument_Mode, prefix, start: int, source_count: int) -> int {
|
||||
index := start
|
||||
for index < source_count {
|
||||
param_index := call_param_index(mode, prefix, index)
|
||||
if param_index >= len(function.params) || !function.params[param_index].comptime_value {
|
||||
break
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
comptime_binding_index :: proc(function: ast.Function, prefix: int, name: symbol.Id) -> (int, bool) {
|
||||
for param, index in function.params[:prefix] {
|
||||
if param.name == name {
|
||||
return index, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
bind_inferred_comptime :: proc(
|
||||
checker: ^Checker,
|
||||
function: ast.Function,
|
||||
prefix: int,
|
||||
values: []Comptime_Value,
|
||||
bound: []bool,
|
||||
name: symbol.Id,
|
||||
candidate: Comptime_Value,
|
||||
span: source.Span,
|
||||
diagnose: bool,
|
||||
) -> bool {
|
||||
index, ok := comptime_binding_index(function, prefix, name)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
value := candidate
|
||||
value.name = name
|
||||
if !bound[index] {
|
||||
values[index] = value
|
||||
bound[index] = true
|
||||
return true
|
||||
}
|
||||
existing := values[index]
|
||||
matches := existing.kind == value.kind
|
||||
if matches {
|
||||
if existing.kind == .Type {
|
||||
matches = types.equal(types.resolve_alias(existing.type, &checker.module.types), types.resolve_alias(value.type, &checker.module.types))
|
||||
} else {
|
||||
matches = existing.value == value.value && types.equal(existing.type, value.type)
|
||||
}
|
||||
}
|
||||
if !matches && diagnose {
|
||||
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
|
||||
right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("%d", value.value, allocator=checker.allocator)
|
||||
source.addf(checker.diagnostics, span, "conflicting inference for comptime parameter '%s': %s and %s", symbol_text(checker, name), left, right)
|
||||
if existing.kind != .Type {
|
||||
delete(left, checker.allocator)
|
||||
}
|
||||
if value.kind != .Type {
|
||||
delete(right, checker.allocator)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
type_pattern_mentions_comptime :: proc(
|
||||
checker: ^Checker,
|
||||
function: ast.Function,
|
||||
prefix: int,
|
||||
pattern: types.Type,
|
||||
depth := 0,
|
||||
) -> bool {
|
||||
if depth > 64 {
|
||||
return false
|
||||
}
|
||||
item, ok := types.node(&checker.module.types, pattern)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if item.qualifier == 0 && item.name != 0 {
|
||||
if _, found := comptime_binding_index(function, prefix, symbol.Id(item.name)); found {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if item.kind == .Array && item.unresolved_count {
|
||||
expr_id := ast.Expr_Id(item.count_expr)
|
||||
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
||||
if _, found := comptime_binding_index(function, prefix, expr.name); found {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if item.kind == .Type_Call {
|
||||
expr_id := ast.Expr_Id(item.count_expr)
|
||||
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
|
||||
for arg_id in checker.ast_module.exprs[expr_id].args {
|
||||
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
|
||||
continue
|
||||
}
|
||||
arg := checker.ast_module.exprs[arg_id]
|
||||
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
|
||||
if _, found := comptime_binding_index(function, prefix, arg.name); found {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if types.is_valid(item.child) && type_pattern_mentions_comptime(checker, function, prefix, item.child, depth+1) {
|
||||
return true
|
||||
}
|
||||
if types.is_valid(item.extra) && type_pattern_mentions_comptime(checker, function, prefix, item.extra, depth+1) {
|
||||
return true
|
||||
}
|
||||
if item.kind == .Function {
|
||||
for field in types.params_for(&checker.module.types, pattern) {
|
||||
if type_pattern_mentions_comptime(checker, function, prefix, field.type, depth+1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
match_inferred_type_pattern :: proc(
|
||||
checker: ^Checker,
|
||||
function: ast.Function,
|
||||
prefix: int,
|
||||
pattern, actual: types.Type,
|
||||
values: []Comptime_Value,
|
||||
bound: []bool,
|
||||
span: source.Span,
|
||||
diagnose: bool,
|
||||
depth := 0,
|
||||
) -> bool {
|
||||
if depth > 64 || !types.is_valid(actual) {
|
||||
return false
|
||||
}
|
||||
store := &checker.module.types
|
||||
actual_type := types.resolve_alias(actual, store)
|
||||
pattern_item, pattern_ok := types.node(store, pattern)
|
||||
if pattern_ok && pattern_item.qualifier == 0 && pattern_item.name != 0 {
|
||||
name := symbol.Id(pattern_item.name)
|
||||
if index, is_binding := comptime_binding_index(function, prefix, name); is_binding &&
|
||||
is_comptime_type_param(checker, function.params[index]) {
|
||||
return bind_inferred_comptime(
|
||||
checker, function, prefix, values, bound, name,
|
||||
Comptime_Value{type=actual_type, kind=.Type}, span, diagnose,
|
||||
)
|
||||
}
|
||||
}
|
||||
if !pattern_ok {
|
||||
return types.equal(pattern, actual_type)
|
||||
}
|
||||
if pattern_item.kind == .Type_Call {
|
||||
expr_id := ast.Expr_Id(pattern_item.count_expr)
|
||||
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
return false
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
target_pkg, available := expr_package(checker, expr, function.pkg, function.file, true)
|
||||
if !available {
|
||||
return false
|
||||
}
|
||||
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, function.file))
|
||||
origin: Type_Factory_Origin
|
||||
found := false
|
||||
for candidate in checker.type_factory_origins {
|
||||
if candidate.template == template && types.equal(candidate.result, actual_type) {
|
||||
origin = candidate
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found || len(expr.args) != len(origin.values) {
|
||||
return false
|
||||
}
|
||||
matched := true
|
||||
for arg_id, index in expr.args {
|
||||
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
|
||||
matched = false
|
||||
continue
|
||||
}
|
||||
arg := checker.ast_module.exprs[arg_id]
|
||||
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
|
||||
if _, is_binding := comptime_binding_index(function, prefix, arg.name); is_binding {
|
||||
matched = bind_inferred_comptime(
|
||||
checker, function, prefix, values, bound, arg.name,
|
||||
origin.values[index], span, diagnose,
|
||||
) && matched
|
||||
}
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
actual_item, actual_ok := types.node(store, actual_type)
|
||||
if pattern_item.kind == .Slice {
|
||||
actual_pointer, actual_array, array_pointer_ok := types.array_pointer(actual_type, store)
|
||||
if array_pointer_ok {
|
||||
mutable := actual_pointer.mutable && actual_array.mutable
|
||||
if pattern_item.mutable && !mutable ||
|
||||
pattern_item.has_sentinel && (!actual_array.has_sentinel || pattern_item.sentinel != actual_array.sentinel) {
|
||||
return false
|
||||
}
|
||||
return match_inferred_type_pattern(
|
||||
checker, function, prefix, pattern_item.child, actual_array.child,
|
||||
values, bound, span, diagnose, depth+1,
|
||||
)
|
||||
}
|
||||
}
|
||||
if !actual_ok || pattern_item.kind != actual_item.kind {
|
||||
resolved := type_from_syntax(checker, pattern, function.pkg, function.file)
|
||||
return types.is_valid(resolved) && types.equal(types.resolve_alias(resolved, store), actual_type)
|
||||
}
|
||||
if pattern_item.many != actual_item.many || pattern_item.has_sentinel != actual_item.has_sentinel ||
|
||||
pattern_item.has_sentinel && pattern_item.sentinel != actual_item.sentinel {
|
||||
return false
|
||||
}
|
||||
matched := true
|
||||
if pattern_item.kind == .Array {
|
||||
if pattern_item.unresolved_count {
|
||||
count_expr := ast.Expr_Id(pattern_item.count_expr)
|
||||
if count_expr != ast.INVALID_EXPR && int(count_expr) < len(checker.ast_module.exprs) {
|
||||
expr := checker.ast_module.exprs[count_expr]
|
||||
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
||||
if binding_index, is_binding := comptime_binding_index(function, prefix, expr.name); is_binding &&
|
||||
!is_comptime_type_param(checker, function.params[binding_index]) {
|
||||
matched = bind_inferred_comptime(
|
||||
checker, function, prefix, values, bound, expr.name,
|
||||
Comptime_Value{type=values[binding_index].type, value=i128(actual_item.count), kind=.Integer},
|
||||
span, diagnose,
|
||||
) && matched
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if pattern_item.count != actual_item.count {
|
||||
matched = false
|
||||
}
|
||||
}
|
||||
if types.is_valid(pattern_item.child) {
|
||||
matched = match_inferred_type_pattern(
|
||||
checker, function, prefix, pattern_item.child, actual_item.child,
|
||||
values, bound, span, diagnose, depth+1,
|
||||
) && matched
|
||||
}
|
||||
if types.is_valid(pattern_item.extra) {
|
||||
matched = match_inferred_type_pattern(
|
||||
checker, function, prefix, pattern_item.extra, actual_item.extra,
|
||||
values, bound, span, diagnose, depth+1,
|
||||
) && matched
|
||||
}
|
||||
if pattern_item.kind == .Function {
|
||||
pattern_params := types.params_for(store, pattern)
|
||||
actual_params := types.params_for(store, actual_type)
|
||||
if len(pattern_params) != len(actual_params) || pattern_item.c_abi != actual_item.c_abi || pattern_item.variadic != actual_item.variadic {
|
||||
return false
|
||||
}
|
||||
for field, index in pattern_params {
|
||||
matched = match_inferred_type_pattern(
|
||||
checker, function, prefix, field.type, actual_params[index].type,
|
||||
values, bound, span, diagnose, depth+1,
|
||||
) && matched
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
infer_call_comptime_values :: proc(
|
||||
checker: ^Checker,
|
||||
function: ast.Function,
|
||||
prefix: int,
|
||||
args: []ast.Expr_Id,
|
||||
actual_args: []types.Type,
|
||||
expected: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
diagnose := false,
|
||||
) -> ([]Comptime_Value, bool) {
|
||||
values := make([]Comptime_Value, prefix, checker.allocator)
|
||||
bound := make([]bool, prefix, checker.allocator)
|
||||
defer delete(bound, checker.allocator)
|
||||
for param, index in function.params[:prefix] {
|
||||
values[index].name = param.name
|
||||
if is_comptime_type_param(checker, param) {
|
||||
values[index].kind = .Type
|
||||
} else {
|
||||
values[index].kind = .Integer
|
||||
values[index].type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
}
|
||||
}
|
||||
matched := true
|
||||
if is_runtime_type(checker, expected) {
|
||||
if types.is_valid(function.error) && types.kind(expected, &checker.module.types) == .Fallible {
|
||||
matched = match_inferred_type_pattern(
|
||||
checker, function, prefix, function.result,
|
||||
types.fallible_success(expected, &checker.module.types), values, bound,
|
||||
source.Span{}, diagnose,
|
||||
) && matched
|
||||
matched = match_inferred_type_pattern(
|
||||
checker, function, prefix, function.error,
|
||||
types.fallible_error(expected, &checker.module.types), values, bound,
|
||||
source.Span{}, diagnose,
|
||||
) && matched
|
||||
} else if type_pattern_mentions_comptime(checker, function, prefix, function.result) {
|
||||
matched = match_inferred_type_pattern(
|
||||
checker, function, prefix, function.result, expected, values, bound,
|
||||
source.Span{}, diagnose,
|
||||
) && matched
|
||||
}
|
||||
}
|
||||
// Concrete arguments bind first. Numeric constants are contextual and therefore
|
||||
// only contribute their default type after stronger evidence has had a chance.
|
||||
weak_passes := [2]bool{false, true}
|
||||
for weak in weak_passes {
|
||||
for arg_id, source_index in args {
|
||||
param_index := prefix+source_index
|
||||
if param_index >= len(function.params) || param_index >= len(actual_args) {
|
||||
continue
|
||||
}
|
||||
is_weak := is_numeric_constant_expr(checker, arg_id)
|
||||
if is_weak != weak {
|
||||
continue
|
||||
}
|
||||
if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
|
||||
continue
|
||||
}
|
||||
if weak {
|
||||
if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok &&
|
||||
item.qualifier == 0 && item.name != 0 {
|
||||
if binding_index, is_binding := comptime_binding_index(function, prefix, symbol.Id(item.name));
|
||||
is_binding && bound[binding_index] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
matched = match_inferred_type_pattern(
|
||||
checker, function, prefix, function.params[param_index].type,
|
||||
actual_args[param_index], values, bound,
|
||||
checker.ast_module.exprs[arg_id].span, diagnose,
|
||||
) && matched
|
||||
}
|
||||
}
|
||||
for param, index in function.params[:prefix] {
|
||||
if bound[index] {
|
||||
continue
|
||||
}
|
||||
matched = false
|
||||
if diagnose {
|
||||
source.addf(
|
||||
checker.diagnostics, param.span,
|
||||
"cannot infer comptime parameter '%s'; pass it explicitly",
|
||||
symbol_text(checker, param.name),
|
||||
)
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
delete(values, checker.allocator)
|
||||
return nil, false
|
||||
}
|
||||
return values, true
|
||||
}
|
||||
|
||||
call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int) -> types.Type {
|
||||
if index < 0 || index >= len(function.params) {
|
||||
return types.INVALID
|
||||
@@ -1112,6 +1523,14 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
|
||||
if is_comptime_type_param(checker, function.params[index]) {
|
||||
return types.INVALID
|
||||
}
|
||||
for param in function.params {
|
||||
if !param.comptime_value {
|
||||
continue
|
||||
}
|
||||
if _, ok := current_comptime_value(checker, param.name); !ok {
|
||||
return types.INVALID
|
||||
}
|
||||
}
|
||||
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
|
||||
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
||||
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
||||
@@ -1273,6 +1692,31 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
|
||||
ct_state_destroy(&state)
|
||||
checker.type_factories[entry_index].result = result
|
||||
checker.type_factories[entry_index].resolving = false
|
||||
if types.is_valid(result) {
|
||||
generated := false
|
||||
for entry in checker.generated_types {
|
||||
if types.equal(entry.result, result) {
|
||||
generated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if generated {
|
||||
has_origin := false
|
||||
for origin in checker.type_factory_origins {
|
||||
if types.equal(origin.result, result) {
|
||||
has_origin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !has_origin {
|
||||
append(&checker.type_factory_origins, Type_Factory_Origin{
|
||||
result=result,
|
||||
template=template,
|
||||
values=clone_comptime_values(values, checker.allocator),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1630,8 +2074,25 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
has_comptime := function_has_comptime_params(function)
|
||||
locals: [dynamic]symbol.Id
|
||||
locals.allocator = checker.allocator
|
||||
seen_runtime_param := false
|
||||
comptime_order_reported := false
|
||||
for param in function.params {
|
||||
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
if param.comptime_value {
|
||||
if seen_runtime_param && !comptime_order_reported {
|
||||
checker.template_diagnostics[function_id] = source.add(
|
||||
checker.diagnostics,
|
||||
param.span,
|
||||
"comptime parameters must form a leading parameter prefix",
|
||||
)
|
||||
comptime_order_reported = true
|
||||
}
|
||||
} else {
|
||||
seen_runtime_param = true
|
||||
}
|
||||
param_type := types.INVALID
|
||||
if param.comptime_value || !has_comptime {
|
||||
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(
|
||||
@@ -2078,9 +2539,12 @@ mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec
|
||||
|
||||
Infer_Frame :: struct {
|
||||
expr: ast.Expr_Id,
|
||||
expected: types.Type,
|
||||
stage: u8,
|
||||
left: types.Type,
|
||||
arg_index: int,
|
||||
arg_mode: Call_Argument_Mode,
|
||||
prefix: int,
|
||||
args: []types.Type,
|
||||
template: ast.Function_Id,
|
||||
}
|
||||
@@ -2093,11 +2557,12 @@ infer_nested_expr :: proc(
|
||||
file: ast.File_Id,
|
||||
demanded: ^[dynamic]Spec_Id,
|
||||
local_types: []types.Type = nil,
|
||||
expected := types.INVALID,
|
||||
) -> types.Type {
|
||||
outer := checker.infer_stack
|
||||
checker.infer_stack = nil
|
||||
checker.infer_stack.allocator = checker.allocator
|
||||
result := infer_expr(checker, expr_id, locals, pkg, file, demanded, local_types)
|
||||
result := infer_expr(checker, expr_id, locals, pkg, file, demanded, local_types, expected)
|
||||
delete(checker.infer_stack)
|
||||
checker.infer_stack = outer
|
||||
return result
|
||||
@@ -2111,6 +2576,7 @@ infer_compound_expr :: proc(
|
||||
file: ast.File_Id,
|
||||
demanded: ^[dynamic]Spec_Id,
|
||||
local_types: []types.Type = nil,
|
||||
expected := types.INVALID,
|
||||
) -> types.Type {
|
||||
store := &checker.module.types
|
||||
#partial switch expr.kind {
|
||||
@@ -2224,10 +2690,12 @@ infer_compound_expr :: proc(
|
||||
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||
return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID
|
||||
case .Try:
|
||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
|
||||
return types.fallible_success(value, store) if types.kind(value, store) == .Fallible else types.INVALID
|
||||
case .Catch:
|
||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
|
||||
success := types.fallible_success(value, store)
|
||||
error_type := types.fallible_error(value, store)
|
||||
if expr.right != ast.INVALID_EXPR {
|
||||
@@ -2273,6 +2741,7 @@ infer_expr :: proc(
|
||||
file := ast.File_Id(0),
|
||||
demanded: ^[dynamic]Spec_Id = nil,
|
||||
local_types: []types.Type = nil,
|
||||
expected := types.INVALID,
|
||||
) -> types.Type {
|
||||
stack := checker.infer_stack
|
||||
clear_dynamic_array(&stack)
|
||||
@@ -2283,7 +2752,7 @@ infer_expr :: proc(
|
||||
clear_dynamic_array(&stack)
|
||||
checker.infer_stack = stack
|
||||
}
|
||||
append(&stack, Infer_Frame{expr=expr_id, template=ast.INVALID_FUNCTION})
|
||||
append(&stack, Infer_Frame{expr=expr_id, expected=expected, template=ast.INVALID_FUNCTION})
|
||||
last := types.INVALID
|
||||
|
||||
for len(stack) > 0 {
|
||||
@@ -2330,7 +2799,7 @@ infer_expr :: proc(
|
||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
||||
.Comptime, .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
|
||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
|
||||
_ = pop(&stack)
|
||||
case .Function_Literal:
|
||||
template := ast.Function_Id(u32(expr.integer))
|
||||
@@ -2559,11 +3028,26 @@ infer_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
function := checker.ast_module.functions[template]
|
||||
mode, prefix := call_argument_mode(function, len(expr.args))
|
||||
if mode == .Invalid {
|
||||
last = types.INVALID
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
stack[frame_index].template = template
|
||||
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
|
||||
stack[frame_index].arg_mode = mode
|
||||
stack[frame_index].prefix = prefix
|
||||
stack[frame_index].args = make([]types.Type, len(function.params), checker.allocator)
|
||||
stack[frame_index].arg_index = next_runtime_call_arg(function, mode, prefix, 0, len(expr.args))
|
||||
stack[frame_index].stage = 3
|
||||
if len(expr.args) > 0 {
|
||||
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
||||
if stack[frame_index].arg_index < len(expr.args) {
|
||||
param_index := call_param_index(mode, prefix, stack[frame_index].arg_index)
|
||||
arg_expected := call_arg_expected(checker, function, param_index)
|
||||
if !is_runtime_type(checker, arg_expected) {
|
||||
arg_expected = types.INVALID
|
||||
}
|
||||
append(&stack, Infer_Frame{expr=expr.args[stack[frame_index].arg_index], expected=arg_expected, template=ast.INVALID_FUNCTION})
|
||||
}
|
||||
}
|
||||
continue
|
||||
@@ -2616,37 +3100,62 @@ infer_expr :: proc(
|
||||
}
|
||||
if frame.stage == 3 {
|
||||
if frame.arg_index < len(expr.args) {
|
||||
stack[frame_index].args[frame.arg_index] = last
|
||||
stack[frame_index].arg_index += 1
|
||||
if frame.arg_index+1 < len(expr.args) {
|
||||
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION})
|
||||
param_index := call_param_index(frame.arg_mode, frame.prefix, frame.arg_index)
|
||||
if param_index < len(stack[frame_index].args) {
|
||||
stack[frame_index].args[param_index] = last
|
||||
}
|
||||
next := next_runtime_call_arg(
|
||||
checker.ast_module.functions[frame.template], frame.arg_mode, frame.prefix,
|
||||
frame.arg_index+1, len(expr.args),
|
||||
)
|
||||
stack[frame_index].arg_index = next
|
||||
if next < len(expr.args) {
|
||||
next_param := call_param_index(frame.arg_mode, frame.prefix, next)
|
||||
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next_param)
|
||||
if !is_runtime_type(checker, arg_expected) {
|
||||
arg_expected = types.INVALID
|
||||
}
|
||||
append(&stack, Infer_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
|
||||
continue
|
||||
}
|
||||
}
|
||||
function := checker.ast_module.functions[frame.template]
|
||||
// A bare-name argument passed to a concrete (non-constraint) parameter pushes
|
||||
// that parameter type back onto the argument's slot, so an open constant adopts
|
||||
// it (e.g. `take_u16(a)` resolves `a` to u16). Constraint params have no single
|
||||
// type to demand; the callee's result flowing back is milestone 14.5.
|
||||
for arg_index in 0..<len(expr.args) {
|
||||
record_demand(checker, expr.args[arg_index], call_arg_expected(checker, function, arg_index), locals, local_types, pkg, file)
|
||||
}
|
||||
// Deferred defaulting leaves an undemanded open constant typeless; give such an
|
||||
// argument its default so the call can still monomorphize (the default feeds only
|
||||
// the spec arg vector, not a demand).
|
||||
for arg_index in 0..<len(expr.args) {
|
||||
if !is_runtime_type(checker, stack[frame_index].args[arg_index]) {
|
||||
fallback := open_const_default_type(checker, expr.args[arg_index], locals, pkg, file)
|
||||
for source_index in 0..<len(expr.args) {
|
||||
param_index := call_param_index(frame.arg_mode, frame.prefix, source_index)
|
||||
if param_index < len(stack[frame_index].args) && !is_runtime_type(checker, stack[frame_index].args[param_index]) {
|
||||
fallback := open_const_default_type(checker, expr.args[source_index], locals, pkg, file)
|
||||
if is_runtime_type(checker, fallback) {
|
||||
stack[frame_index].args[arg_index] = fallback
|
||||
stack[frame_index].args[param_index] = fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file)
|
||||
comptime_values: []Comptime_Value
|
||||
comptime_ok := false
|
||||
if frame.arg_mode == .Inferred {
|
||||
comptime_values, comptime_ok = infer_call_comptime_values(
|
||||
checker, function, frame.prefix, expr.args, stack[frame_index].args,
|
||||
frame.expected, pkg, file,
|
||||
)
|
||||
} else {
|
||||
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)) &&
|
||||
comptime_ok &&
|
||||
if comptime_ok &&
|
||||
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
|
||||
previous_comptime := checker.current_comptime_values
|
||||
checker.current_comptime_values = comptime_values
|
||||
for source_index in 0..<len(expr.args) {
|
||||
param_index := call_param_index(frame.arg_mode, frame.prefix, source_index)
|
||||
if param_index >= len(function.params) || function.params[param_index].comptime_value {
|
||||
continue
|
||||
}
|
||||
demand := call_arg_expected(checker, function, param_index)
|
||||
record_demand(checker, expr.args[source_index], demand, locals, local_types, pkg, file)
|
||||
}
|
||||
checker.current_comptime_values = previous_comptime
|
||||
spec := INVALID_SPEC
|
||||
if demanded == nil {
|
||||
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
||||
@@ -2657,7 +3166,10 @@ infer_expr :: proc(
|
||||
if spec != INVALID_SPEC {
|
||||
last = checker.specs[spec].result
|
||||
} else {
|
||||
previous_comptime := checker.current_comptime_values
|
||||
checker.current_comptime_values = comptime_values
|
||||
declared := function_channel_type(checker, function)
|
||||
checker.current_comptime_values = previous_comptime
|
||||
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
||||
}
|
||||
} else {
|
||||
@@ -2810,7 +3322,8 @@ infer_statements :: proc(
|
||||
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
|
||||
value_type := types.INVALID
|
||||
if !is_undefined_expr(checker, statement.expr) {
|
||||
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
expected := declared_local if is_runtime_type(checker, declared_local) else types.INVALID
|
||||
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected)
|
||||
declared_local = resolve_inferred_array_from_type(checker, declared_local, value_type)
|
||||
}
|
||||
if is_runtime_type(checker, declared_local) && !has_inferred_array_count(checker, declared_local) {
|
||||
@@ -2863,14 +3376,24 @@ infer_statements :: proc(
|
||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
continue
|
||||
}
|
||||
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
expected_assignment := types.INVALID
|
||||
if statement.target != ast.INVALID_EXPR {
|
||||
expected_assignment = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
|
||||
} else if statement.name != checker.sink_symbol {
|
||||
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok {
|
||||
expected_assignment = locals^[local_index].type
|
||||
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
|
||||
expected_assignment = checker.global_types[global]
|
||||
}
|
||||
}
|
||||
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected_assignment)
|
||||
// Only push the target's type back onto a bare-name RHS (e.g. `x += speed`):
|
||||
// pushing through an arithmetic RHS would feed the target's (often provisional)
|
||||
// type onto open-constant operands and poison their family. Operands of an
|
||||
// arithmetic RHS resolve from their own authoritative uses.
|
||||
rhs_is_arith := is_arith_kind(checker.ast_module.exprs[statement.expr].kind)
|
||||
if statement.target != ast.INVALID_EXPR {
|
||||
target_type := infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
|
||||
target_type := expected_assignment
|
||||
target_expr := checker.ast_module.exprs[statement.target]
|
||||
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
||||
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
|
||||
@@ -2903,7 +3426,7 @@ infer_statements :: proc(
|
||||
}
|
||||
case .Return:
|
||||
if statement.expr != ast.INVALID_EXPR {
|
||||
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, result_hint)
|
||||
if is_runtime_type(checker, result_hint) {
|
||||
_ = record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
||||
expr := checker.ast_module.exprs[statement.expr]
|
||||
@@ -3505,12 +4028,13 @@ infer_all :: proc(checker: ^Checker) {
|
||||
if global.external {
|
||||
continue
|
||||
}
|
||||
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
|
||||
declared := resolve_inferred_array(
|
||||
checker,
|
||||
type_from_syntax(checker, global.type, global.pkg, global.file),
|
||||
global.expr,
|
||||
)
|
||||
expected := declared if is_runtime_type(checker, declared) else types.INVALID
|
||||
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file, expected=expected)
|
||||
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
|
||||
resolved != declared {
|
||||
if !types.equal(checker.global_types[index], resolved) {
|
||||
@@ -3991,19 +4515,13 @@ Build_Expr_Frame :: struct {
|
||||
stage: u8,
|
||||
left: hir.Expr_Id,
|
||||
arg_index: int,
|
||||
arg_mode: Call_Argument_Mode,
|
||||
prefix: int,
|
||||
built_args: []hir.Expr_Id,
|
||||
arg_types: []types.Type,
|
||||
template: ast.Function_Id,
|
||||
}
|
||||
|
||||
next_built_template_arg :: proc(checker: ^Checker, function: ast.Function, start: int) -> int {
|
||||
index := start
|
||||
for index < len(function.params) && is_comptime_type_param(checker, function.params[index]) {
|
||||
index += 1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
|
||||
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
|
||||
return false
|
||||
@@ -4714,7 +5232,8 @@ build_compound_expr :: proc(
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Try:
|
||||
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
||||
channel_type := checker.module.exprs[channel].type
|
||||
success := types.fallible_success(channel_type, store)
|
||||
if !types.is_valid(success) {
|
||||
@@ -4747,7 +5266,8 @@ build_compound_expr :: proc(
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Catch:
|
||||
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
||||
channel_type := checker.module.exprs[channel].type
|
||||
success := types.fallible_success(channel_type, store)
|
||||
if !types.is_valid(success) {
|
||||
@@ -5448,9 +5968,20 @@ build_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if !valid_call_arity(function, len(expr.args)) {
|
||||
mode, prefix := call_argument_mode(function, len(expr.args))
|
||||
if mode == .Invalid {
|
||||
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
|
||||
"function '%s' expects %d arguments, got %d"
|
||||
if inferred_prefix, inferable := comptime_prefix_count(function); inferable && !function.c_abi && !function.variadic {
|
||||
id := source.addf(
|
||||
checker.diagnostics, expr.span,
|
||||
"function '%s' expects %d arguments with explicit comptime parameters or %d with inferred comptime parameters, got %d",
|
||||
symbol_text(checker, expr.name), len(function.params), len(function.params)-inferred_prefix, len(expr.args),
|
||||
)
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
@@ -5464,15 +5995,18 @@ build_expr :: proc(
|
||||
continue
|
||||
}
|
||||
stack[frame_index].template = template
|
||||
stack[frame_index].arg_mode = mode
|
||||
stack[frame_index].prefix = prefix
|
||||
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
|
||||
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
|
||||
stack[frame_index].arg_types = make([]types.Type, len(function.params), checker.allocator)
|
||||
for &arg in stack[frame_index].built_args {
|
||||
arg = hir.INVALID_EXPR
|
||||
}
|
||||
stack[frame_index].arg_index = next_built_template_arg(checker, function, 0)
|
||||
stack[frame_index].arg_index = next_runtime_call_arg(function, mode, prefix, 0, len(expr.args))
|
||||
stack[frame_index].stage = 3
|
||||
if stack[frame_index].arg_index < len(expr.args) {
|
||||
arg_expected := call_arg_expected(checker, function, stack[frame_index].arg_index)
|
||||
param_index := call_param_index(mode, prefix, stack[frame_index].arg_index)
|
||||
arg_expected := call_arg_expected(checker, function, param_index)
|
||||
if !is_runtime_type(checker, arg_expected) {
|
||||
arg_expected = types.INVALID
|
||||
}
|
||||
@@ -5527,11 +6061,18 @@ build_expr :: proc(
|
||||
if frame.stage == 3 {
|
||||
if frame.arg_index < len(expr.args) {
|
||||
stack[frame_index].built_args[frame.arg_index] = last
|
||||
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
|
||||
next := next_built_template_arg(checker, checker.ast_module.functions[frame.template], frame.arg_index+1)
|
||||
param_index := call_param_index(frame.arg_mode, frame.prefix, frame.arg_index)
|
||||
if param_index < len(stack[frame_index].arg_types) {
|
||||
stack[frame_index].arg_types[param_index] = checker.module.exprs[last].type
|
||||
}
|
||||
next := next_runtime_call_arg(
|
||||
checker.ast_module.functions[frame.template], frame.arg_mode, frame.prefix,
|
||||
frame.arg_index+1, len(expr.args),
|
||||
)
|
||||
stack[frame_index].arg_index = next
|
||||
if next < len(expr.args) {
|
||||
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next)
|
||||
next_param := call_param_index(frame.arg_mode, frame.prefix, next)
|
||||
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next_param)
|
||||
if !is_runtime_type(checker, arg_expected) {
|
||||
arg_expected = types.INVALID
|
||||
}
|
||||
@@ -5540,8 +6081,39 @@ build_expr :: proc(
|
||||
}
|
||||
}
|
||||
function := checker.ast_module.functions[frame.template]
|
||||
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file, diagnose=true)
|
||||
comptime_values: []Comptime_Value
|
||||
comptime_ok := false
|
||||
if frame.arg_mode == .Inferred {
|
||||
comptime_values, comptime_ok = infer_call_comptime_values(
|
||||
checker, function, frame.prefix, expr.args, stack[frame_index].arg_types,
|
||||
frame.expected, pkg, file, diagnose=true,
|
||||
)
|
||||
} else {
|
||||
comptime_values, comptime_ok = collect_comptime_values(checker, function, expr.args, pkg, file, diagnose=true)
|
||||
}
|
||||
defer delete(comptime_values, checker.allocator)
|
||||
if comptime_ok {
|
||||
previous_comptime := checker.current_comptime_values
|
||||
checker.current_comptime_values = comptime_values
|
||||
for source_index in 0..<len(expr.args) {
|
||||
param_index := call_param_index(frame.arg_mode, frame.prefix, source_index)
|
||||
if param_index >= len(function.params) || function.params[param_index].comptime_value ||
|
||||
!is_numeric_constant_expr(checker, expr.args[source_index]) {
|
||||
continue
|
||||
}
|
||||
expected_arg := call_arg_expected(checker, function, param_index)
|
||||
if !is_runtime_type(checker, expected_arg) {
|
||||
continue
|
||||
}
|
||||
rebuilt := build_nested_expr(
|
||||
checker, expr.args[source_index], locals, global_reads, calls,
|
||||
expected_arg, pkg, file,
|
||||
)
|
||||
stack[frame_index].built_args[source_index] = rebuilt
|
||||
stack[frame_index].arg_types[param_index] = checker.module.exprs[rebuilt].type
|
||||
}
|
||||
checker.current_comptime_values = previous_comptime
|
||||
}
|
||||
arg_violation := source.INVALID_DIAGNOSTIC
|
||||
if comptime_ok {
|
||||
previous_comptime := checker.current_comptime_values
|
||||
@@ -5604,10 +6176,11 @@ build_expr :: proc(
|
||||
runtime_arg_count := runtime_count + max(0, len(source_args)-len(function.params))
|
||||
runtime_args := make([]hir.Expr_Id, runtime_arg_count, checker.allocator)
|
||||
runtime_index := 0
|
||||
for param, source_index in function.params {
|
||||
for param, param_index in function.params {
|
||||
if param.comptime_value {
|
||||
continue
|
||||
}
|
||||
source_index := param_index if frame.arg_mode == .Explicit else param_index-frame.prefix
|
||||
arg := source_args[source_index]
|
||||
runtime_args[runtime_index] = coerce_expr(
|
||||
checker,
|
||||
@@ -8977,6 +9550,7 @@ check :: proc(
|
||||
checker.anon_globals.allocator = allocator
|
||||
checker.type_factories.allocator = allocator
|
||||
checker.generated_types.allocator = allocator
|
||||
checker.type_factory_origins.allocator = allocator
|
||||
build_symbol_indexes(&checker)
|
||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
|
||||
@@ -9026,8 +9600,12 @@ check :: proc(
|
||||
for entry in checker.generated_types {
|
||||
delete(entry.values, allocator)
|
||||
}
|
||||
for origin in checker.type_factory_origins {
|
||||
delete(origin.values, allocator)
|
||||
}
|
||||
delete(checker.type_factories)
|
||||
delete(checker.generated_types)
|
||||
delete(checker.type_factory_origins)
|
||||
}
|
||||
|
||||
for function, index in ast_module.functions {
|
||||
|
||||
+96
-2
@@ -2573,8 +2573,8 @@ main func() void {
|
||||
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_missing = found_missing || strings.contains(message, "cannot infer comptime parameter 'N'")
|
||||
found_extra = found_extra || strings.contains(message, "expects 1 arguments with explicit comptime parameters or 0 with inferred comptime parameters, 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")
|
||||
@@ -2592,6 +2592,100 @@ main func() void {
|
||||
testing.expect(t, found_address)
|
||||
}
|
||||
|
||||
@(test)
|
||||
inferred_comptime_params_diagnose_ambiguous_calls :: proc(t: ^testing.T) {
|
||||
text := `Ignored func($T type) type {
|
||||
return i32
|
||||
}
|
||||
Box func($T type) type {
|
||||
return struct { value T }
|
||||
}
|
||||
BoxAlias func($T type) type {
|
||||
return Box(T)
|
||||
}
|
||||
conflict func($T type, left, right T) T { return left }
|
||||
unknown func($T type) T { value T = undefined; return value }
|
||||
partial func($T type, $N usize, value T) T { return value }
|
||||
use_ignored func($T type, value Ignored(T)) i32 { return value }
|
||||
use_alias func($T type, value BoxAlias(T)) T { return value.value }
|
||||
main func() void {
|
||||
a i32 :: 1
|
||||
b u32 :: 2
|
||||
_ = conflict(a, b)
|
||||
_ = unknown()
|
||||
_ = partial(i32, a)
|
||||
_ = use_ignored(a)
|
||||
box Box(i32) :: Box(i32) { value = 1 }
|
||||
_ = use_alias(box)
|
||||
}
|
||||
`
|
||||
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_conflict := false
|
||||
found_unknown := false
|
||||
found_partial := false
|
||||
unrecoverable := 0
|
||||
for diagnostic in diagnostics.items {
|
||||
message := diagnostic.message
|
||||
found_conflict = found_conflict || strings.contains(message, "conflicting inference for comptime parameter 'T': i32 and u32")
|
||||
found_unknown = found_unknown || strings.contains(message, "cannot infer comptime parameter 'T'")
|
||||
found_partial = found_partial || strings.contains(message, "expects 3 arguments with explicit comptime parameters or 1 with inferred comptime parameters, got 2")
|
||||
if strings.contains(message, "cannot infer comptime parameter 'T'") {
|
||||
unrecoverable += 1
|
||||
}
|
||||
}
|
||||
testing.expect(t, found_conflict)
|
||||
testing.expect(t, found_unknown)
|
||||
testing.expect(t, found_partial)
|
||||
testing.expect(t, unrecoverable >= 3)
|
||||
}
|
||||
|
||||
@(test)
|
||||
comptime_params_must_form_leading_prefix :: proc(t: ^testing.T) {
|
||||
text := `valid func($T type, $N usize, value T) [N]T {
|
||||
result [N]T = undefined
|
||||
_ = value
|
||||
return result
|
||||
}
|
||||
runtime_first func(value i32, $T type, $N usize) T { return value }
|
||||
split func($T type, value T, $N usize) [N]T {
|
||||
result [N]T = undefined
|
||||
_ = value
|
||||
return result
|
||||
}
|
||||
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)
|
||||
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)
|
||||
|
||||
count := 0
|
||||
for diagnostic in diagnostics.items {
|
||||
if strings.contains(diagnostic.message, "comptime parameters must form a leading parameter prefix") {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, count, 2)
|
||||
}
|
||||
|
||||
@(test)
|
||||
comptime_type_params_specialize_by_type_and_omit_runtime_args :: proc(t: ^testing.T) {
|
||||
text := `Point :: struct {
|
||||
|
||||
@@ -25,13 +25,13 @@ _fail_allocator mem.Allocator :: mem.Allocator {
|
||||
_noop func() void {}
|
||||
|
||||
run func() i32 ! mem.AllocError {
|
||||
values arraylist.ArrayList(i32) = arraylist.init(i32, mem.c_allocator)
|
||||
defer arraylist.deinit(i32, &values)
|
||||
values arraylist.ArrayList(i32) = arraylist.init(mem.c_allocator)
|
||||
defer arraylist.deinit(&values)
|
||||
if (values.items.len != 0 or values.capacity != 0) return 1
|
||||
|
||||
i usize = 0
|
||||
while i < 20 : i += 1 {
|
||||
arraylist.append(i32, &values, i32(i)) catch |_| {
|
||||
arraylist.append(&values, i32(i)) catch |_| {
|
||||
return .out_of_memory
|
||||
}
|
||||
}
|
||||
@@ -40,34 +40,34 @@ run func() i32 ! mem.AllocError {
|
||||
values.items[3] = 33
|
||||
if (values.items[3] != 33) return 4
|
||||
|
||||
arraylist.reserve(i32, &values, 50) catch |_| {
|
||||
arraylist.reserve(&values, 50) catch |_| {
|
||||
return .out_of_memory
|
||||
}
|
||||
if (values.capacity < 50 or values.items.len != 20 or values.items[19] != 19) return 5
|
||||
capacity usize :: values.capacity
|
||||
arraylist.clear(i32, &values)
|
||||
arraylist.clear(&values)
|
||||
if (values.items.len != 0 or values.capacity != capacity) return 6
|
||||
arraylist.append(i32, &values, 7) catch |_| {
|
||||
arraylist.append(&values, 7) catch |_| {
|
||||
return .out_of_memory
|
||||
}
|
||||
if (values.items.len != 1 or values.items[0] != 7 or values.capacity != capacity) return 7
|
||||
|
||||
empty_values arraylist.ArrayList([0]u8) = arraylist.init([0]u8, mem.c_allocator)
|
||||
defer arraylist.deinit([0]u8, &empty_values)
|
||||
empty_values arraylist.ArrayList([0]u8) = arraylist.init(mem.c_allocator)
|
||||
defer arraylist.deinit(&empty_values)
|
||||
zero [0]u8 :: []
|
||||
arraylist.append([0]u8, &empty_values, zero) catch |_| {
|
||||
arraylist.append(&empty_values, zero) catch |_| {
|
||||
return .out_of_memory
|
||||
}
|
||||
if (empty_values.items.len != 1) return 8
|
||||
|
||||
failed arraylist.ArrayList(i32) = arraylist.init(i32, _fail_allocator)
|
||||
failed_as_expected bool = false
|
||||
arraylist.append(i32, &failed, 1) catch |_| {
|
||||
arraylist.append(&failed, 1) catch |_| {
|
||||
failed_as_expected = true
|
||||
yield _noop()
|
||||
}
|
||||
if (failed_as_expected == false or failed.items.len != 0 or failed.capacity != 0) return 9
|
||||
arraylist.deinit(i32, &failed)
|
||||
arraylist.deinit(&failed)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -19,10 +19,37 @@ buffer func($T type, $N usize, value T) [N]T {
|
||||
return data
|
||||
}
|
||||
|
||||
zero func($T type) T {
|
||||
value T = undefined
|
||||
return value
|
||||
}
|
||||
|
||||
array_len func($T type, $N usize, values [N]T) usize {
|
||||
return values.len
|
||||
}
|
||||
|
||||
Fixed func($T type, $N usize) type {
|
||||
return struct {
|
||||
values [N]T
|
||||
}
|
||||
}
|
||||
|
||||
fixed_len func($T type, $N usize, value @Fixed(T, N)) usize {
|
||||
return value.values.len
|
||||
}
|
||||
|
||||
take_i32 func(value i32) i32 {
|
||||
return value
|
||||
}
|
||||
|
||||
return_zero func() i32 {
|
||||
return zero()
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
a i32 :: 42
|
||||
b i32 :: 27
|
||||
if max(i32, a, b) != 42 {
|
||||
if max(a, b) != 42 {
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -33,14 +60,32 @@ main func() i32 {
|
||||
}
|
||||
|
||||
p Point :: Point { x = 11 }
|
||||
q Point :: id(Point, p)
|
||||
q Point :: id(p)
|
||||
if q.x != 11 {
|
||||
return 3
|
||||
}
|
||||
|
||||
bytes [_]u8 :: buffer(u8, 4, small_a)
|
||||
bytes [4]u8 :: buffer(small_a)
|
||||
if bytes.len != 4 {
|
||||
return 4
|
||||
}
|
||||
if array_len(bytes) != 4 {
|
||||
return 5
|
||||
}
|
||||
zero_value i32 :: zero()
|
||||
_ = zero_value
|
||||
literal :: id(7)
|
||||
if literal != 7 {
|
||||
return 6
|
||||
}
|
||||
fixed Fixed(u8, 3) :: Fixed(u8, 3) { values = [1, 2, 3] }
|
||||
if fixed_len(&fixed) != 3 {
|
||||
return 7
|
||||
}
|
||||
assigned i32 = 1
|
||||
assigned = zero()
|
||||
_ = assigned
|
||||
_ = take_i32(zero())
|
||||
_ = return_zero()
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,4 +1,25 @@
|
||||
mem :: import "@std/mem"
|
||||
|
||||
eql_test func() i32 {
|
||||
if (mem.eql("bro", "bro") == false) return 21
|
||||
if mem.eql("bro", "bra") return 22
|
||||
if mem.eql("bro", "brolang") return 23
|
||||
|
||||
empty []u8 :: ""
|
||||
if (mem.eql(empty, "") == false) return 24
|
||||
|
||||
left [3]i32 :: [1, 2, 3]
|
||||
same [3]i32 :: [1, 2, 3]
|
||||
different [3]i32 :: [1, 2, 4]
|
||||
if (mem.eql(left[..], same[..]) == false) return 25
|
||||
if mem.eql(left[..], different[..]) return 26
|
||||
return 0
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
eql_result i32 :: eql_test()
|
||||
if (eql_result != 0) return eql_result
|
||||
|
||||
typed_result i32 :: typed_allocator_test()
|
||||
if (typed_result != 0) return typed_result
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ task_list_init func(allocator mem.Allocator) TaskList {
|
||||
alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
|
||||
fallback [1]mut i32 = undefined
|
||||
failed bool = false
|
||||
values []mut i32 = mem.alloc(i32, allocator, count) catch |_| {
|
||||
values []mut i32 = mem.alloc(allocator, count) catch |_| {
|
||||
failed = true
|
||||
yield (&fallback).ptr[..0]
|
||||
}
|
||||
@@ -33,7 +33,7 @@ alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
|
||||
|
||||
free_i32s func(allocator mem.Allocator, values ?[]mut i32) void {
|
||||
if values |slice| {
|
||||
mem.free(i32, allocator, slice)
|
||||
mem.free(allocator, slice)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,12 +49,12 @@ typed_allocator_test func() i32 {
|
||||
|
||||
i32_fallback [1]mut i32 = undefined
|
||||
empty_failed bool = false
|
||||
empty []mut i32 = mem.alloc(i32, first_allocator, 0) catch |_| {
|
||||
empty []mut i32 = mem.alloc(first_allocator, 0) catch |_| {
|
||||
empty_failed = true
|
||||
yield (&i32_fallback).ptr[..0]
|
||||
}
|
||||
if (empty_failed or empty.len != 0 or first_calls[0] != 3) return 34
|
||||
mem.free(i32, first_allocator, empty)
|
||||
mem.free(first_allocator, empty)
|
||||
if (first_calls[0] != 3) return 33
|
||||
|
||||
zero_sized_fallback [1]mut [0]u8 = undefined
|
||||
@@ -70,7 +70,7 @@ typed_allocator_test func() i32 {
|
||||
|
||||
u64_fallback [1]mut u64 = undefined
|
||||
overflow_fallback_failed bool = false
|
||||
overflow_fallback []mut u64 = mem.alloc(u64, first_allocator, 0) catch |_| {
|
||||
overflow_fallback []mut u64 = mem.alloc(first_allocator, 0) catch |_| {
|
||||
overflow_fallback_failed = true
|
||||
yield (&u64_fallback).ptr[..0]
|
||||
}
|
||||
@@ -83,20 +83,20 @@ typed_allocator_test func() i32 {
|
||||
if (overflow_failed == false or first_calls[0] != 3) return 40
|
||||
|
||||
typed_failed bool = false
|
||||
typed []mut i32 = mem.alloc(i32, mem.c_allocator, 4) catch |_| {
|
||||
typed []mut i32 = mem.alloc(mem.c_allocator, 4) catch |_| {
|
||||
typed_failed = true
|
||||
yield (&i32_fallback).ptr[..0]
|
||||
}
|
||||
if (typed_failed) return 38
|
||||
defer mem.free(i32, mem.c_allocator, typed)
|
||||
defer mem.free(mem.c_allocator, typed)
|
||||
typed[0] = 10
|
||||
typed[3] = 20
|
||||
if (typed[0] + typed[3] != 30) return 39
|
||||
typed = mem.realloc(i32, mem.c_allocator, typed, 8) catch |_| {
|
||||
typed = mem.realloc(mem.c_allocator, typed, 8) catch |_| {
|
||||
return 41
|
||||
}
|
||||
if (typed.len != 8 or typed[0] != 10 or typed[3] != 20) return 42
|
||||
typed = mem.realloc(i32, mem.c_allocator, typed, 2) catch |_| {
|
||||
typed = mem.realloc(mem.c_allocator, typed, 2) catch |_| {
|
||||
return 43
|
||||
}
|
||||
if (typed.len != 2 or typed[0] != 10) return 44
|
||||
|
||||
@@ -18,7 +18,7 @@ init func($T type, allocator mem.Allocator) ArrayList(T) {
|
||||
|
||||
deinit func($T type, list @mut ArrayList(T)) void {
|
||||
allocation []mut T :: list.items.ptr[..list.capacity]
|
||||
mem.free(T, list.allocator, allocation)
|
||||
mem.free(list.allocator, allocation)
|
||||
list.items = mem.empty(T)
|
||||
list.capacity = 0
|
||||
}
|
||||
@@ -43,7 +43,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
||||
|
||||
length usize :: list.items.len
|
||||
allocation []mut T :: list.items.ptr[..list.capacity]
|
||||
grown []mut T :: mem.realloc(T, list.allocator, allocation, new_capacity) catch |_| {
|
||||
grown []mut T :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| {
|
||||
return .out_of_memory
|
||||
}
|
||||
list.items = grown.ptr[..length]
|
||||
@@ -56,7 +56,7 @@ append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
|
||||
if length == max_value(usize) {
|
||||
return .out_of_memory
|
||||
}
|
||||
try reserve(T, list, length + 1)
|
||||
try reserve(list, length + 1)
|
||||
list.items = list.items.ptr[..length + 1]
|
||||
list.items[length] = value
|
||||
return _
|
||||
|
||||
+15
-1
@@ -27,6 +27,20 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
|
||||
allocator.vtable.free(allocator.context, memory, size, alignment)
|
||||
}
|
||||
|
||||
eql func($T type, left, right []T) bool {
|
||||
if left.len != right.len {
|
||||
return false
|
||||
}
|
||||
|
||||
i usize = 0
|
||||
while i < left.len : i += 1 {
|
||||
if left[i] != right[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
_empty_storage [1]mut u64 = [0]
|
||||
|
||||
_empty_slice func($T type, count usize) []mut T {
|
||||
@@ -64,7 +78,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
return memory
|
||||
}
|
||||
if new_count == 0 {
|
||||
free(T, allocator, memory)
|
||||
free(allocator, memory)
|
||||
return _empty_slice(T, 0)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user