inferred leading comptime params
This commit is contained in:
+2
-1
@@ -57,6 +57,7 @@ roadmap and milestone history.
|
|||||||
- demand-monomorphized Brolang and C-ABI functions
|
- demand-monomorphized Brolang and C-ABI functions
|
||||||
- integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value and omitted from the runtime ABI
|
- integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value and omitted from the runtime ABI
|
||||||
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
|
- 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
|
||||||
- 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
|
- 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
|
- 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
|
||||||
@@ -94,7 +95,7 @@ roadmap and milestone history.
|
|||||||
- exporting Brolang functions to C and broader target-specific C ABI lowering
|
- exporting Brolang functions to C and broader target-specific C ABI lowering
|
||||||
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
|
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
|
||||||
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
|
- 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)`
|
- 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
|
- 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
|
- 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
|
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
|
||||||
- Qualified imported globals and functions with package-aware symbol mangling
|
- Qualified imported globals and functions with package-aware symbol mangling
|
||||||
- Demand-monomorphized Brolang and C-ABI functions
|
- Demand-monomorphized Brolang and C-ABI functions
|
||||||
- Integer 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 }`)
|
- 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`, `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`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
||||||
|
|||||||
@@ -754,10 +754,41 @@
|
|||||||
zero-count, zero-sized-type, and alignment handling
|
zero-count, zero-sized-type, and alignment handling
|
||||||
- `std/arraylist.ArrayList(T)` exposes `items`, `capacity`, and `allocator`, with fallible
|
- `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
|
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
|
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
|
||||||
|
- 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
|
32. disallow arbitrary integer division
|
||||||
- take inspiration from zig
|
- take inspiration from zig
|
||||||
|
|||||||
+601
-51
@@ -143,6 +143,12 @@ Generated_Type_Entry :: struct {
|
|||||||
result: types.Type,
|
result: types.Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type_Factory_Origin :: struct {
|
||||||
|
result: types.Type,
|
||||||
|
template: ast.Function_Id,
|
||||||
|
values: []Comptime_Value,
|
||||||
|
}
|
||||||
|
|
||||||
Checker :: struct {
|
Checker :: struct {
|
||||||
ast_module: ^ast.Module,
|
ast_module: ^ast.Module,
|
||||||
diagnostics: ^source.Diagnostics,
|
diagnostics: ^source.Diagnostics,
|
||||||
@@ -184,6 +190,7 @@ Checker :: struct {
|
|||||||
current_comptime_values: []Comptime_Value,
|
current_comptime_values: []Comptime_Value,
|
||||||
type_factories: [dynamic]Type_Factory_Entry,
|
type_factories: [dynamic]Type_Factory_Entry,
|
||||||
generated_types: [dynamic]Generated_Type_Entry,
|
generated_types: [dynamic]Generated_Type_Entry,
|
||||||
|
type_factory_origins: [dynamic]Type_Factory_Origin,
|
||||||
target: target.Target,
|
target: target.Target,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -1105,6 +1112,396 @@ valid_call_arity :: proc(function: ast.Function, count: int) -> bool {
|
|||||||
return count >= len(function.params) if function.variadic else count == len(function.params)
|
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 !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 {
|
call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int) -> types.Type {
|
||||||
if index < 0 || index >= len(function.params) {
|
if index < 0 || index >= len(function.params) {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
@@ -1112,6 +1509,14 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
|
|||||||
if is_comptime_type_param(checker, function.params[index]) {
|
if is_comptime_type_param(checker, function.params[index]) {
|
||||||
return types.INVALID
|
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)
|
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
|
||||||
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
||||||
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
||||||
@@ -1273,6 +1678,31 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
|
|||||||
ct_state_destroy(&state)
|
ct_state_destroy(&state)
|
||||||
checker.type_factories[entry_index].result = result
|
checker.type_factories[entry_index].result = result
|
||||||
checker.type_factories[entry_index].resolving = false
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1631,7 +2061,10 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
locals: [dynamic]symbol.Id
|
locals: [dynamic]symbol.Id
|
||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
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 param.comptime_value {
|
||||||
if function.c_abi {
|
if function.c_abi {
|
||||||
checker.template_diagnostics[function_id] = source.add(
|
checker.template_diagnostics[function_id] = source.add(
|
||||||
@@ -2078,9 +2511,12 @@ mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec
|
|||||||
|
|
||||||
Infer_Frame :: struct {
|
Infer_Frame :: struct {
|
||||||
expr: ast.Expr_Id,
|
expr: ast.Expr_Id,
|
||||||
|
expected: types.Type,
|
||||||
stage: u8,
|
stage: u8,
|
||||||
left: types.Type,
|
left: types.Type,
|
||||||
arg_index: int,
|
arg_index: int,
|
||||||
|
arg_mode: Call_Argument_Mode,
|
||||||
|
prefix: int,
|
||||||
args: []types.Type,
|
args: []types.Type,
|
||||||
template: ast.Function_Id,
|
template: ast.Function_Id,
|
||||||
}
|
}
|
||||||
@@ -2093,11 +2529,12 @@ infer_nested_expr :: proc(
|
|||||||
file: ast.File_Id,
|
file: ast.File_Id,
|
||||||
demanded: ^[dynamic]Spec_Id,
|
demanded: ^[dynamic]Spec_Id,
|
||||||
local_types: []types.Type = nil,
|
local_types: []types.Type = nil,
|
||||||
|
expected := types.INVALID,
|
||||||
) -> types.Type {
|
) -> types.Type {
|
||||||
outer := checker.infer_stack
|
outer := checker.infer_stack
|
||||||
checker.infer_stack = nil
|
checker.infer_stack = nil
|
||||||
checker.infer_stack.allocator = checker.allocator
|
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)
|
delete(checker.infer_stack)
|
||||||
checker.infer_stack = outer
|
checker.infer_stack = outer
|
||||||
return result
|
return result
|
||||||
@@ -2111,6 +2548,7 @@ infer_compound_expr :: proc(
|
|||||||
file: ast.File_Id,
|
file: ast.File_Id,
|
||||||
demanded: ^[dynamic]Spec_Id,
|
demanded: ^[dynamic]Spec_Id,
|
||||||
local_types: []types.Type = nil,
|
local_types: []types.Type = nil,
|
||||||
|
expected := types.INVALID,
|
||||||
) -> types.Type {
|
) -> types.Type {
|
||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
#partial switch expr.kind {
|
#partial switch expr.kind {
|
||||||
@@ -2224,10 +2662,12 @@ infer_compound_expr :: proc(
|
|||||||
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
_ = 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
|
return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID
|
||||||
case .Try:
|
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
|
return types.fallible_success(value, store) if types.kind(value, store) == .Fallible else types.INVALID
|
||||||
case .Catch:
|
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)
|
success := types.fallible_success(value, store)
|
||||||
error_type := types.fallible_error(value, store)
|
error_type := types.fallible_error(value, store)
|
||||||
if expr.right != ast.INVALID_EXPR {
|
if expr.right != ast.INVALID_EXPR {
|
||||||
@@ -2273,6 +2713,7 @@ infer_expr :: proc(
|
|||||||
file := ast.File_Id(0),
|
file := ast.File_Id(0),
|
||||||
demanded: ^[dynamic]Spec_Id = nil,
|
demanded: ^[dynamic]Spec_Id = nil,
|
||||||
local_types: []types.Type = nil,
|
local_types: []types.Type = nil,
|
||||||
|
expected := types.INVALID,
|
||||||
) -> types.Type {
|
) -> types.Type {
|
||||||
stack := checker.infer_stack
|
stack := checker.infer_stack
|
||||||
clear_dynamic_array(&stack)
|
clear_dynamic_array(&stack)
|
||||||
@@ -2283,7 +2724,7 @@ infer_expr :: proc(
|
|||||||
clear_dynamic_array(&stack)
|
clear_dynamic_array(&stack)
|
||||||
checker.infer_stack = 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
|
last := types.INVALID
|
||||||
|
|
||||||
for len(stack) > 0 {
|
for len(stack) > 0 {
|
||||||
@@ -2330,7 +2771,7 @@ infer_expr :: proc(
|
|||||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
||||||
.Comptime, .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
.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)
|
_ = pop(&stack)
|
||||||
case .Function_Literal:
|
case .Function_Literal:
|
||||||
template := ast.Function_Id(u32(expr.integer))
|
template := ast.Function_Id(u32(expr.integer))
|
||||||
@@ -2559,11 +3000,26 @@ infer_expr :: proc(
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
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].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
|
stack[frame_index].stage = 3
|
||||||
if len(expr.args) > 0 {
|
if stack[frame_index].arg_index < len(expr.args) {
|
||||||
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
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
|
continue
|
||||||
@@ -2616,37 +3072,62 @@ infer_expr :: proc(
|
|||||||
}
|
}
|
||||||
if frame.stage == 3 {
|
if frame.stage == 3 {
|
||||||
if frame.arg_index < len(expr.args) {
|
if frame.arg_index < len(expr.args) {
|
||||||
stack[frame_index].args[frame.arg_index] = last
|
param_index := call_param_index(frame.arg_mode, frame.prefix, frame.arg_index)
|
||||||
stack[frame_index].arg_index += 1
|
if param_index < len(stack[frame_index].args) {
|
||||||
if frame.arg_index+1 < len(expr.args) {
|
stack[frame_index].args[param_index] = last
|
||||||
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION})
|
}
|
||||||
|
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
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function := checker.ast_module.functions[frame.template]
|
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
|
// Deferred defaulting leaves an undemanded open constant typeless; give such an
|
||||||
// argument its default so the call can still monomorphize (the default feeds only
|
// argument its default so the call can still monomorphize (the default feeds only
|
||||||
// the spec arg vector, not a demand).
|
// the spec arg vector, not a demand).
|
||||||
for arg_index in 0..<len(expr.args) {
|
for source_index in 0..<len(expr.args) {
|
||||||
if !is_runtime_type(checker, stack[frame_index].args[arg_index]) {
|
param_index := call_param_index(frame.arg_mode, frame.prefix, source_index)
|
||||||
fallback := open_const_default_type(checker, expr.args[arg_index], locals, pkg, file)
|
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) {
|
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)
|
defer delete(comptime_values, checker.allocator)
|
||||||
if valid_call_arity(function, len(expr.args)) &&
|
if comptime_ok &&
|
||||||
comptime_ok &&
|
|
||||||
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
|
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
|
spec := INVALID_SPEC
|
||||||
if demanded == nil {
|
if demanded == nil {
|
||||||
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
||||||
@@ -2657,7 +3138,10 @@ infer_expr :: proc(
|
|||||||
if spec != INVALID_SPEC {
|
if spec != INVALID_SPEC {
|
||||||
last = checker.specs[spec].result
|
last = checker.specs[spec].result
|
||||||
} else {
|
} else {
|
||||||
|
previous_comptime := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = comptime_values
|
||||||
declared := function_channel_type(checker, function)
|
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
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2810,7 +3294,8 @@ infer_statements :: proc(
|
|||||||
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
|
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
|
||||||
value_type := types.INVALID
|
value_type := types.INVALID
|
||||||
if !is_undefined_expr(checker, statement.expr) {
|
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)
|
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) {
|
if is_runtime_type(checker, declared_local) && !has_inferred_array_count(checker, declared_local) {
|
||||||
@@ -2863,14 +3348,24 @@ infer_statements :: proc(
|
|||||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
continue
|
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`):
|
// 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)
|
// pushing through an arithmetic RHS would feed the target's (often provisional)
|
||||||
// type onto open-constant operands and poison their family. Operands of an
|
// type onto open-constant operands and poison their family. Operands of an
|
||||||
// arithmetic RHS resolve from their own authoritative uses.
|
// arithmetic RHS resolve from their own authoritative uses.
|
||||||
rhs_is_arith := is_arith_kind(checker.ast_module.exprs[statement.expr].kind)
|
rhs_is_arith := is_arith_kind(checker.ast_module.exprs[statement.expr].kind)
|
||||||
if statement.target != ast.INVALID_EXPR {
|
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]
|
target_expr := checker.ast_module.exprs[statement.target]
|
||||||
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
||||||
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
|
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
|
||||||
@@ -2903,7 +3398,7 @@ infer_statements :: proc(
|
|||||||
}
|
}
|
||||||
case .Return:
|
case .Return:
|
||||||
if statement.expr != ast.INVALID_EXPR {
|
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) {
|
if is_runtime_type(checker, result_hint) {
|
||||||
_ = record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
_ = record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
||||||
expr := checker.ast_module.exprs[statement.expr]
|
expr := checker.ast_module.exprs[statement.expr]
|
||||||
@@ -3505,12 +4000,13 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
if global.external {
|
if global.external {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
|
|
||||||
declared := resolve_inferred_array(
|
declared := resolve_inferred_array(
|
||||||
checker,
|
checker,
|
||||||
type_from_syntax(checker, global.type, global.pkg, global.file),
|
type_from_syntax(checker, global.type, global.pkg, global.file),
|
||||||
global.expr,
|
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);
|
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
|
||||||
resolved != declared {
|
resolved != declared {
|
||||||
if !types.equal(checker.global_types[index], resolved) {
|
if !types.equal(checker.global_types[index], resolved) {
|
||||||
@@ -3991,19 +4487,13 @@ Build_Expr_Frame :: struct {
|
|||||||
stage: u8,
|
stage: u8,
|
||||||
left: hir.Expr_Id,
|
left: hir.Expr_Id,
|
||||||
arg_index: int,
|
arg_index: int,
|
||||||
|
arg_mode: Call_Argument_Mode,
|
||||||
|
prefix: int,
|
||||||
built_args: []hir.Expr_Id,
|
built_args: []hir.Expr_Id,
|
||||||
arg_types: []types.Type,
|
arg_types: []types.Type,
|
||||||
template: ast.Function_Id,
|
template: ast.Function_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
next_built_template_arg :: proc(checker: ^Checker, function: ast.Function, start: int) -> int {
|
|
||||||
index := start
|
|
||||||
for index < len(function.params) && is_comptime_type_param(checker, function.params[index]) {
|
|
||||||
index += 1
|
|
||||||
}
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
||||||
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
|
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
|
||||||
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
|
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
|
||||||
return false
|
return false
|
||||||
@@ -4714,7 +5204,8 @@ build_compound_expr :: proc(
|
|||||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Try:
|
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
|
channel_type := checker.module.exprs[channel].type
|
||||||
success := types.fallible_success(channel_type, store)
|
success := types.fallible_success(channel_type, store)
|
||||||
if !types.is_valid(success) {
|
if !types.is_valid(success) {
|
||||||
@@ -4747,7 +5238,8 @@ build_compound_expr :: proc(
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Catch:
|
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
|
channel_type := checker.module.exprs[channel].type
|
||||||
success := types.fallible_success(channel_type, store)
|
success := types.fallible_success(channel_type, store)
|
||||||
if !types.is_valid(success) {
|
if !types.is_valid(success) {
|
||||||
@@ -5448,9 +5940,20 @@ build_expr :: proc(
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
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
|
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
|
||||||
"function '%s' expects %d arguments, got %d"
|
"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(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
expr.span,
|
expr.span,
|
||||||
@@ -5464,15 +5967,18 @@ build_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stack[frame_index].template = template
|
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].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 {
|
for &arg in stack[frame_index].built_args {
|
||||||
arg = hir.INVALID_EXPR
|
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
|
stack[frame_index].stage = 3
|
||||||
if stack[frame_index].arg_index < len(expr.args) {
|
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) {
|
if !is_runtime_type(checker, arg_expected) {
|
||||||
arg_expected = types.INVALID
|
arg_expected = types.INVALID
|
||||||
}
|
}
|
||||||
@@ -5527,11 +6033,18 @@ build_expr :: proc(
|
|||||||
if frame.stage == 3 {
|
if frame.stage == 3 {
|
||||||
if frame.arg_index < len(expr.args) {
|
if frame.arg_index < len(expr.args) {
|
||||||
stack[frame_index].built_args[frame.arg_index] = last
|
stack[frame_index].built_args[frame.arg_index] = last
|
||||||
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
|
param_index := call_param_index(frame.arg_mode, frame.prefix, frame.arg_index)
|
||||||
next := next_built_template_arg(checker, checker.ast_module.functions[frame.template], frame.arg_index+1)
|
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
|
stack[frame_index].arg_index = next
|
||||||
if next < len(expr.args) {
|
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) {
|
if !is_runtime_type(checker, arg_expected) {
|
||||||
arg_expected = types.INVALID
|
arg_expected = types.INVALID
|
||||||
}
|
}
|
||||||
@@ -5540,8 +6053,39 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function := checker.ast_module.functions[frame.template]
|
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)
|
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
|
arg_violation := source.INVALID_DIAGNOSTIC
|
||||||
if comptime_ok {
|
if comptime_ok {
|
||||||
previous_comptime := checker.current_comptime_values
|
previous_comptime := checker.current_comptime_values
|
||||||
@@ -5604,10 +6148,11 @@ build_expr :: proc(
|
|||||||
runtime_arg_count := runtime_count + max(0, len(source_args)-len(function.params))
|
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_args := make([]hir.Expr_Id, runtime_arg_count, checker.allocator)
|
||||||
runtime_index := 0
|
runtime_index := 0
|
||||||
for param, source_index in function.params {
|
for param, param_index in function.params {
|
||||||
if param.comptime_value {
|
if param.comptime_value {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
source_index := param_index if frame.arg_mode == .Explicit else param_index-frame.prefix
|
||||||
arg := source_args[source_index]
|
arg := source_args[source_index]
|
||||||
runtime_args[runtime_index] = coerce_expr(
|
runtime_args[runtime_index] = coerce_expr(
|
||||||
checker,
|
checker,
|
||||||
@@ -8977,6 +9522,7 @@ check :: proc(
|
|||||||
checker.anon_globals.allocator = allocator
|
checker.anon_globals.allocator = allocator
|
||||||
checker.type_factories.allocator = allocator
|
checker.type_factories.allocator = allocator
|
||||||
checker.generated_types.allocator = allocator
|
checker.generated_types.allocator = allocator
|
||||||
|
checker.type_factory_origins.allocator = allocator
|
||||||
build_symbol_indexes(&checker)
|
build_symbol_indexes(&checker)
|
||||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_demands = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
@@ -9026,8 +9572,12 @@ check :: proc(
|
|||||||
for entry in checker.generated_types {
|
for entry in checker.generated_types {
|
||||||
delete(entry.values, allocator)
|
delete(entry.values, allocator)
|
||||||
}
|
}
|
||||||
|
for origin in checker.type_factory_origins {
|
||||||
|
delete(origin.values, allocator)
|
||||||
|
}
|
||||||
delete(checker.type_factories)
|
delete(checker.type_factories)
|
||||||
delete(checker.generated_types)
|
delete(checker.generated_types)
|
||||||
|
delete(checker.type_factory_origins)
|
||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
for function, index in ast_module.functions {
|
||||||
|
|||||||
+60
-2
@@ -2573,8 +2573,8 @@ main func() void {
|
|||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
message := diagnostic.message
|
message := diagnostic.message
|
||||||
found_runtime_arg = found_runtime_arg || strings.contains(message, "must be a compile-time integer expression")
|
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_missing = found_missing || strings.contains(message, "cannot infer comptime parameter 'N'")
|
||||||
found_extra = found_extra || strings.contains(message, "expects 1 arguments, got 2")
|
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_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_range = found_range || strings.contains(message, "integer constant 300 does not fit in u8")
|
||||||
found_bad_type = found_bad_type || strings.contains(message, "requires a concrete integer type")
|
found_bad_type = found_bad_type || strings.contains(message, "requires a concrete integer type")
|
||||||
@@ -2592,6 +2592,64 @@ main func() void {
|
|||||||
testing.expect(t, found_address)
|
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)
|
@(test)
|
||||||
comptime_type_params_specialize_by_type_and_omit_runtime_args :: proc(t: ^testing.T) {
|
comptime_type_params_specialize_by_type_and_omit_runtime_args :: proc(t: ^testing.T) {
|
||||||
text := `Point :: struct {
|
text := `Point :: struct {
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ _fail_allocator mem.Allocator :: mem.Allocator {
|
|||||||
_noop func() void {}
|
_noop func() void {}
|
||||||
|
|
||||||
run func() i32 ! mem.AllocError {
|
run func() i32 ! mem.AllocError {
|
||||||
values arraylist.ArrayList(i32) = arraylist.init(i32, mem.c_allocator)
|
values arraylist.ArrayList(i32) = arraylist.init(mem.c_allocator)
|
||||||
defer arraylist.deinit(i32, &values)
|
defer arraylist.deinit(&values)
|
||||||
if (values.items.len != 0 or values.capacity != 0) return 1
|
if (values.items.len != 0 or values.capacity != 0) return 1
|
||||||
|
|
||||||
i usize = 0
|
i usize = 0
|
||||||
while i < 20 : i += 1 {
|
while i < 20 : i += 1 {
|
||||||
arraylist.append(i32, &values, i32(i)) catch |_| {
|
arraylist.append(&values, i32(i)) catch |_| {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,34 +40,34 @@ run func() i32 ! mem.AllocError {
|
|||||||
values.items[3] = 33
|
values.items[3] = 33
|
||||||
if (values.items[3] != 33) return 4
|
if (values.items[3] != 33) return 4
|
||||||
|
|
||||||
arraylist.reserve(i32, &values, 50) catch |_| {
|
arraylist.reserve(&values, 50) catch |_| {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
if (values.capacity < 50 or values.items.len != 20 or values.items[19] != 19) return 5
|
if (values.capacity < 50 or values.items.len != 20 or values.items[19] != 19) return 5
|
||||||
capacity usize :: values.capacity
|
capacity usize :: values.capacity
|
||||||
arraylist.clear(i32, &values)
|
arraylist.clear(&values)
|
||||||
if (values.items.len != 0 or values.capacity != capacity) return 6
|
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
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
if (values.items.len != 1 or values.items[0] != 7 or values.capacity != capacity) return 7
|
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)
|
empty_values arraylist.ArrayList([0]u8) = arraylist.init(mem.c_allocator)
|
||||||
defer arraylist.deinit([0]u8, &empty_values)
|
defer arraylist.deinit(&empty_values)
|
||||||
zero [0]u8 :: []
|
zero [0]u8 :: []
|
||||||
arraylist.append([0]u8, &empty_values, zero) catch |_| {
|
arraylist.append(&empty_values, zero) catch |_| {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
if (empty_values.items.len != 1) return 8
|
if (empty_values.items.len != 1) return 8
|
||||||
|
|
||||||
failed arraylist.ArrayList(i32) = arraylist.init(i32, _fail_allocator)
|
failed arraylist.ArrayList(i32) = arraylist.init(i32, _fail_allocator)
|
||||||
failed_as_expected bool = false
|
failed_as_expected bool = false
|
||||||
arraylist.append(i32, &failed, 1) catch |_| {
|
arraylist.append(&failed, 1) catch |_| {
|
||||||
failed_as_expected = true
|
failed_as_expected = true
|
||||||
yield _noop()
|
yield _noop()
|
||||||
}
|
}
|
||||||
if (failed_as_expected == false or failed.items.len != 0 or failed.capacity != 0) return 9
|
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
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,37 @@ buffer func($T type, $N usize, value T) [N]T {
|
|||||||
return data
|
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 {
|
main func() i32 {
|
||||||
a i32 :: 42
|
a i32 :: 42
|
||||||
b i32 :: 27
|
b i32 :: 27
|
||||||
if max(i32, a, b) != 42 {
|
if max(a, b) != 42 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,14 +60,32 @@ main func() i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p Point :: Point { x = 11 }
|
p Point :: Point { x = 11 }
|
||||||
q Point :: id(Point, p)
|
q Point :: id(p)
|
||||||
if q.x != 11 {
|
if q.x != 11 {
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes [_]u8 :: buffer(u8, 4, small_a)
|
bytes [4]u8 :: buffer(small_a)
|
||||||
if bytes.len != 4 {
|
if bytes.len != 4 {
|
||||||
return 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
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ task_list_init func(allocator mem.Allocator) TaskList {
|
|||||||
alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
|
alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
|
||||||
fallback [1]mut i32 = undefined
|
fallback [1]mut i32 = undefined
|
||||||
failed bool = false
|
failed bool = false
|
||||||
values []mut i32 = mem.alloc(i32, allocator, count) catch |_| {
|
values []mut i32 = mem.alloc(allocator, count) catch |_| {
|
||||||
failed = true
|
failed = true
|
||||||
yield (&fallback).ptr[..0]
|
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 {
|
free_i32s func(allocator mem.Allocator, values ?[]mut i32) void {
|
||||||
if values |slice| {
|
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
|
i32_fallback [1]mut i32 = undefined
|
||||||
empty_failed bool = false
|
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
|
empty_failed = true
|
||||||
yield (&i32_fallback).ptr[..0]
|
yield (&i32_fallback).ptr[..0]
|
||||||
}
|
}
|
||||||
if (empty_failed or empty.len != 0 or first_calls[0] != 3) return 34
|
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
|
if (first_calls[0] != 3) return 33
|
||||||
|
|
||||||
zero_sized_fallback [1]mut [0]u8 = undefined
|
zero_sized_fallback [1]mut [0]u8 = undefined
|
||||||
@@ -70,7 +70,7 @@ typed_allocator_test func() i32 {
|
|||||||
|
|
||||||
u64_fallback [1]mut u64 = undefined
|
u64_fallback [1]mut u64 = undefined
|
||||||
overflow_fallback_failed bool = false
|
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
|
overflow_fallback_failed = true
|
||||||
yield (&u64_fallback).ptr[..0]
|
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
|
if (overflow_failed == false or first_calls[0] != 3) return 40
|
||||||
|
|
||||||
typed_failed bool = false
|
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
|
typed_failed = true
|
||||||
yield (&i32_fallback).ptr[..0]
|
yield (&i32_fallback).ptr[..0]
|
||||||
}
|
}
|
||||||
if (typed_failed) return 38
|
if (typed_failed) return 38
|
||||||
defer mem.free(i32, mem.c_allocator, typed)
|
defer mem.free(mem.c_allocator, typed)
|
||||||
typed[0] = 10
|
typed[0] = 10
|
||||||
typed[3] = 20
|
typed[3] = 20
|
||||||
if (typed[0] + typed[3] != 30) return 39
|
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
|
return 41
|
||||||
}
|
}
|
||||||
if (typed.len != 8 or typed[0] != 10 or typed[3] != 20) return 42
|
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
|
return 43
|
||||||
}
|
}
|
||||||
if (typed.len != 2 or typed[0] != 10) return 44
|
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 {
|
deinit func($T type, list @mut ArrayList(T)) void {
|
||||||
allocation []mut T :: list.items.ptr[..list.capacity]
|
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.items = mem.empty(T)
|
||||||
list.capacity = 0
|
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
|
length usize :: list.items.len
|
||||||
allocation []mut T :: list.items.ptr[..list.capacity]
|
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
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
list.items = grown.ptr[..length]
|
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) {
|
if length == max_value(usize) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
try reserve(T, list, length + 1)
|
try reserve(list, length + 1)
|
||||||
list.items = list.items.ptr[..length + 1]
|
list.items = list.items.ptr[..length + 1]
|
||||||
list.items[length] = value
|
list.items[length] = value
|
||||||
return _
|
return _
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
|||||||
return memory
|
return memory
|
||||||
}
|
}
|
||||||
if new_count == 0 {
|
if new_count == 0 {
|
||||||
free(T, allocator, memory)
|
free(allocator, memory)
|
||||||
return _empty_slice(T, 0)
|
return _empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user