feat: infer generic arguments from expected results
This commit is contained in:
@@ -79,7 +79,9 @@ the parser supports arithmetic expressions with nested parenthesized groups, suc
|
|||||||
|
|
||||||
function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. argument lists allow newlines and a trailing comma; outside parentheses, newlines remain statement boundaries. type checking validates callable values, argument counts and argument types.
|
function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. argument lists allow newlines and a trailing comma; outside parentheses, newlines remain statement boundaries. type checking validates callable values, argument counts and argument types.
|
||||||
|
|
||||||
generic procedures mark compile-time parameters with `$`, such as `identity proc($T type, value T) T`. pass all parameters explicitly (`identity(i16, 42)`) or omit every compile-time argument when runtime argument types determine them (`identity(value)`). inference follows pointers, arrays, slices, optionals and specialized record constructors, including array counts; conflicting or undetermined arguments are diagnosed. specializations are cached by declaration and compile-time arguments, retain nominal generated-record identity, and erase compile-time parameters from runtime calls.
|
generic procedures mark compile-time parameters with `$`, such as `identity proc($T type, value T) T`. pass all parameters explicitly (`identity(i16, 42)`) or omit every compile-time argument when runtime argument types or an expected result type determine them (`identity(value)`). inference follows pointers, arrays, slices, optionals, fallible channels, callback signatures and specialized record constructors, including array counts; conflicting or undetermined arguments are diagnosed. specializations are cached by declaration and compile-time arguments, retain nominal generated-record identity, and erase compile-time parameters from runtime calls.
|
||||||
|
|
||||||
|
expected result types flow from typed bindings, assignments, returns, ordinary call parameters, aggregate fields/elements, defaults, match arms, value-block yields and cleanup handlers. they also reach explicit `$()` expressions, `some!` payloads and arithmetic/bitwise operands; shift counts keep their independent types. rigid runtime arguments constrain inference before result context and contextual literal defaults; result context also types nested generic calls. `answer proc($T type) T { return 42 }` can therefore be called as `value i16 :: answer()`. optional presence and nominal generated types remain exact, explicit arguments are never overridden, and type-producing procedures still require every input explicitly.
|
||||||
|
|
||||||
```hon
|
```hon
|
||||||
Fixed proc($T type, $N usize) type {
|
Fixed proc($T type, $N usize) type {
|
||||||
@@ -196,7 +198,7 @@ native external declarations use `malloc c_func(size c_ulong) ?*mut anyopaque`,
|
|||||||
|
|
||||||
the existing `std/arraylist` and `std/mem` work without replacement bindings or library code. interpreter/native checks cover initialization, reserve/growth, append/pop/clear, allocation failure without losing the original list, zero-sized elements and deferred cleanup on error. native checks use the real `mem.c_allocator` and imported `ffi/c` declarations for aggregate element storage and over-aligned allocation/reallocation; interpreter allocation checks use a fixed-capacity test allocator, not foreign calls. broader metadata operations and formatted IO remain later acceptance gates.
|
the existing `std/arraylist` and `std/mem` work without replacement bindings or library code. interpreter/native checks cover initialization, reserve/growth, append/pop/clear, allocation failure without losing the original list, zero-sized elements and deferred cleanup on error. native checks use the real `mem.c_allocator` and imported `ffi/c` declarations for aggregate element storage and over-aligned allocation/reallocation; interpreter allocation checks use a fixed-capacity test allocator, not foreign calls. broader metadata operations and formatted IO remain later acceptance gates.
|
||||||
|
|
||||||
existing `std/hashmap.HashMap`, `AutoHashMap` and `StringHashMap` execute without replacement library code. checks cover collision chains, zero-hash normalization, growth/rehashing, duplicate-key errors, absent lookups, aggregate copy isolation, allocation failure preserving the map and deferred cleanup. native checks use the real C allocator for integer boundaries, booleans, sparse/aliased enums, nested distinct/array/record keys (ignoring record padding), content-based strings and custom callbacks; interpreter checks use separate fixed-capacity typed buffers. pass initializer parameters explicitly, such as `hashmap.init(i16, i16, null, null, allocator)`; initializer inference from an expected result type remains open. a typed `StringHashMap` literal retains its private library callbacks.
|
existing `std/hashmap.HashMap`, `AutoHashMap` and `StringHashMap` execute without replacement library code. checks cover collision chains, zero-hash normalization, growth/rehashing, duplicate-key errors, absent lookups, aggregate copy isolation, allocation failure preserving the map and deferred cleanup. native checks use the real C allocator for integer boundaries, booleans, sparse/aliased enums, nested distinct/array/record keys (ignoring record padding), content-based strings and custom callbacks; interpreter checks use separate fixed-capacity typed buffers. a declared result supplies the initializer parameters: `map hashmap.AutoHashMap(i16, i16) := hashmap.init(allocator)`; `StringHashMap` and custom `HashMap` results also infer their exact private/custom callbacks. explicit calls such as `hashmap.init(i16, i16, null, null, allocator)` remain supported.
|
||||||
|
|
||||||
records use `Point :: struct { x i16, y i16 = 2 }`, with comma- or newline-separated fields. named record declarations have nominal identity; two separately declared records are not interchangeable. construct them with `Point { x = 40 }` or a contextual keyed literal, such as `point Point := { x = 40 }`. omitted fields use their declared defaults; missing required fields, duplicate/unknown fields, incompatible values and recursive by-value layouts are diagnosed. explicit initializers run in source order, followed by omitted defaults in declaration order. an explicit value overrides its field's default without evaluating that default.
|
records use `Point :: struct { x i16, y i16 = 2 }`, with comma- or newline-separated fields. named record declarations have nominal identity; two separately declared records are not interchangeable. construct them with `Point { x = 40 }` or a contextual keyed literal, such as `point Point := { x = 40 }`. omitted fields use their declared defaults; missing required fields, duplicate/unknown fields, incompatible values and recursive by-value layouts are diagnosed. explicit initializers run in source order, followed by omitted defaults in declaration order. an explicit value overrides its field's default without evaluating that default.
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ non-FFI allocators and cover zero-sized storage, growth, failure and cleanup.
|
|||||||
existing hash maps execute collision chains, rehashing, duplicate/absent lookups,
|
existing hash maps execute collision chains, rehashing, duplicate/absent lookups,
|
||||||
aggregate copies and allocator failure/cleanup through both paths; native checks
|
aggregate copies and allocator failure/cleanup through both paths; native checks
|
||||||
cover automatic integer/bool/enum/distinct/array/record keys, content-based strings
|
cover automatic integer/bool/enum/distinct/array/record keys, content-based strings
|
||||||
and custom callbacks with the real C allocator. initializer parameters remain explicit.
|
and custom callbacks with the real C allocator. expected result types infer initializer
|
||||||
|
types/callbacks, including automatic, string and custom maps.
|
||||||
`unreachable` is a demanded source trap; terminal literal-true loops need no return.
|
`unreachable` is a demanded source trap; terminal literal-true loops need no return.
|
||||||
`compile_error!` retains owned user messages and skips unselected compile-time bodies.
|
`compile_error!` retains owned user messages and skips unselected compile-time bodies.
|
||||||
boolean and optional-capture `if ... else` expressions reuse checked matches for
|
boolean and optional-capture `if ... else` expressions reuse checked matches for
|
||||||
@@ -139,8 +140,9 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [x] optional conditional-expression checkpoint: `if value |payload| then_value else else_value` reuses canonical presence matches through interpreter/native execution. single-evaluation guards and lazy branches preserve exact immutable by-value captures, nested optional presence, aggregate isolation and runtime pointer/callback/slice/zero-sized payloads. contextual optional/fallible results, constant-input procedures, generated types/defaults and explicit compile-time subject selection retain checked execution and owned payload snapshots. nonoptional guards, capture scope/shadowing, malformed captures, missing `else`, incompatible branches and compile-time overflow retain diagnostics. yielding value blocks are covered below; broader constant materialization remains open.
|
- [x] optional conditional-expression checkpoint: `if value |payload| then_value else else_value` reuses canonical presence matches through interpreter/native execution. single-evaluation guards and lazy branches preserve exact immutable by-value captures, nested optional presence, aggregate isolation and runtime pointer/callback/slice/zero-sized payloads. contextual optional/fallible results, constant-input procedures, generated types/defaults and explicit compile-time subject selection retain checked execution and owned payload snapshots. nonoptional guards, capture scope/shadowing, malformed captures, missing `else`, incompatible branches and compile-time overflow retain diagnostics. yielding value blocks are covered below; broader constant materialization remains open.
|
||||||
- [x] yielding-block checkpoint: value blocks and `yield expression` produce exact/contextual results through interpreter/native execution, including conditional branches, match arms and catch handlers. nested value sources select the nearest target; ordinary blocks/loops preserve it. yielded aggregates snapshot before LIFO cleanup, successful yields skip `errdefer`, and return/error/break/continue retain enclosing targets. ordinary constants, generic branch specialization and type-producing blocks generate owned values, types, storage and defaults. missing operands/results, incompatible yields, capture mutation and outward cleanup transfers retain diagnostics and demand-driven traps; imported provenance and repeatable compilation are verified. labeled yields and value-producing loops remain outside this checkpoint.
|
- [x] yielding-block checkpoint: value blocks and `yield expression` produce exact/contextual results through interpreter/native execution, including conditional branches, match arms and catch handlers. nested value sources select the nearest target; ordinary blocks/loops preserve it. yielded aggregates snapshot before LIFO cleanup, successful yields skip `errdefer`, and return/error/break/continue retain enclosing targets. ordinary constants, generic branch specialization and type-producing blocks generate owned values, types, storage and defaults. missing operands/results, incompatible yields, capture mutation and outward cleanup transfers retain diagnostics and demand-driven traps; imported provenance and repeatable compilation are verified. labeled yields and value-producing loops remain outside this checkpoint.
|
||||||
- [x] compound/bitwise checkpoint: arithmetic and bitwise compound updates evaluate mutable local/field/index/pointer places and their old values once before the RHS through interpreter/native execution. integer complement, and/or/xor, truncating left shifts, signed/unsigned right shifts and saturating left shifts preserve widths and distinct identity; unsigned counts retain independent types. known invalid counts diagnose even in untaken runtime branches; dynamic counts trap without native LLVM poison. checked arithmetic, float-only division, readonly diagnostics, scoped cleanup, ordinary constants, generated storage, generic branch selection and repeatable compilation retain existing semantics. type-valued `|` remains composition while runtime `|` and capture delimiters retain their grammar. full hash-map acceptance remains open.
|
- [x] compound/bitwise checkpoint: arithmetic and bitwise compound updates evaluate mutable local/field/index/pointer places and their old values once before the RHS through interpreter/native execution. integer complement, and/or/xor, truncating left shifts, signed/unsigned right shifts and saturating left shifts preserve widths and distinct identity; unsigned counts retain independent types. known invalid counts diagnose even in untaken runtime branches; dynamic counts trap without native LLVM poison. checked arithmetic, float-only division, readonly diagnostics, scoped cleanup, ordinary constants, generated storage, generic branch selection and repeatable compilation retain existing semantics. type-valued `|` remains composition while runtime `|` and capture delimiters retain their grammar. full hash-map acceptance remains open.
|
||||||
- [x] hash-map execution checkpoint: existing `std/hashmap.HashMap`, `AutoHashMap` and `StringHashMap` run collision chains, zero-hash normalization, growth/rehashing, duplicate-key errors, absent lookups and owned aggregate values without replacement library code. interpreter checks use separate typed allocation buffers and preserve the original map on allocation failure, including cleanup; native `mem.c_allocator` checks cover all integer widths/boundaries, booleans, sparse/aliased enums, nested distinct/array/record keys with padding ignored, content-based byte strings and custom callbacks at `-O0`/`-O2`. specialization retains `usize` collection indices, nominal/explicit pointer-cast result types, rigid slice borrowing/string context and source-known optional callbacks; ordinary checking rejects incompatible elements and readonly reversal. `unreachable` demand-traps with provenance through both paths and compile-time execution; literal-true loops distinguish escaping and nested breaks. `compile_error!` owns computed string diagnostics and skips unselected compile-time bodies. initializer inference from expected result types remains open.
|
- [x] hash-map execution checkpoint: existing `std/hashmap.HashMap`, `AutoHashMap` and `StringHashMap` run collision chains, zero-hash normalization, growth/rehashing, duplicate-key errors, absent lookups and owned aggregate values without replacement library code. interpreter checks use separate typed allocation buffers and preserve the original map on allocation failure, including cleanup; native `mem.c_allocator` checks cover all integer widths/boundaries, booleans, sparse/aliased enums, nested distinct/array/record keys with padding ignored, content-based byte strings and custom callbacks at `-O0`/`-O2`. specialization retains `usize` collection indices, nominal/explicit pointer-cast result types, rigid slice borrowing/string context and source-known optional callbacks; ordinary checking rejects incompatible elements and readonly reversal. `unreachable` demand-traps with provenance through both paths and compile-time execution; literal-true loops distinguish escaping and nested breaks. `compile_error!` owns computed string diagnostics and skips unselected compile-time bodies. initializer inference from expected result types is covered below.
|
||||||
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including untagged unions and storage-bearing values), expected-result generic inference (including hash-map initializers), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates.
|
- [x] expected-result inference checkpoint: omitted compile-time type/value arguments infer from rigid runtime arguments and expected results before contextual literal defaults. typed bindings, assignments, returns, ordinary/nested calls, aggregate fields/elements/defaults, optional/fallible channels, callbacks, match arms, yielding/catch blocks, arithmetic/bitwise operands and explicit `$()` expressions retain exact checked types through interpreter/native execution. generated nominal results infer counts and private/custom callback identities; existing `hashmap.init(allocator)` initializes `AutoHashMap`, `StringHashMap` and custom `HashMap` without replacement library code. rigid conflicts, unrelated nominal generators and unresolved arguments diagnose; explicit calls and fully explicit type-producer inputs retain their contracts. owned specializations and repeat compilation are verified.
|
||||||
|
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including untagged unions and storage-bearing values), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates.
|
||||||
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
||||||
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
||||||
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
|
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
|||||||
` if (T == i16) return value else return value.missing
|
` if (T == i16) return value else return value.missing
|
||||||
`}
|
`}
|
||||||
`Box proc(T type) type { return struct { value T } }
|
`Box proc(T type) type { return struct { value T } }
|
||||||
|
`make_box proc($T type) Box(T) { return { value = 40 } }
|
||||||
`count proc() usize {
|
`count proc() usize {
|
||||||
` result := 0
|
` result := 0
|
||||||
` while (result < 2) result = result + 1
|
` while (result < 2) result = result + 1
|
||||||
@@ -267,7 +268,9 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
|||||||
`main proc() i16 {
|
`main proc() i16 {
|
||||||
` if $(i16 != i16) { return $(i8(128)) }
|
` if $(i16 != i16) { return $(i8(128)) }
|
||||||
` values :: $(data())
|
` values :: $(data())
|
||||||
` box Box(i16) :: { value = identity(i16, values[0]) }
|
` box Box(i16) :: make_box()
|
||||||
|
` inferred i16 :: identity(40)
|
||||||
|
` if (inferred != box.value) return 0
|
||||||
` fixed Fixed(count()) :: { values = [box.value, 2] }
|
` fixed Fixed(count()) :: { values = [box.value, 2] }
|
||||||
` same Fixed(2) :: fixed
|
` same Fixed(2) :: fixed
|
||||||
` packet :: $(packet_data())
|
` packet :: $(packet_data())
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
errdefer_outside_fallible = "errdefer requires an enclosing fallible function",
|
errdefer_outside_fallible = "errdefer requires an enclosing fallible function",
|
||||||
cleanup_control_flow = "cleanup cannot transfer control out of itself",
|
cleanup_control_flow = "cleanup cannot transfer control out of itself",
|
||||||
comptime_argument_required = "generic argument must be a compile-time value",
|
comptime_argument_required = "generic argument must be a compile-time value",
|
||||||
cannot_infer_generic = "cannot infer consistent compile-time arguments from runtime arguments",
|
cannot_infer_generic = "cannot infer consistent compile-time arguments from arguments and result context",
|
||||||
specialization_cycle = "compile-time specialization depends on its unfinished result",
|
specialization_cycle = "compile-time specialization depends on its unfinished result",
|
||||||
specialization_limit = "compile-time specialization exceeds its recursion or instance limit",
|
specialization_limit = "compile-time specialization exceeds its recursion or instance limit",
|
||||||
comptime_execution_limit = "compile-time execution exceeds its recursion or step limit",
|
comptime_execution_limit = "compile-time execution exceeds its recursion or step limit",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ test import "@source/ast"
|
|||||||
test import "@source/parser"
|
test import "@source/parser"
|
||||||
test import "@source/sema"
|
test import "@source/sema"
|
||||||
test import "@source/sema/check"
|
test import "@source/sema/check"
|
||||||
|
test import "@source/sema/specialize"
|
||||||
test import "@source/ir"
|
test import "@source/ir"
|
||||||
test import "@source/lower"
|
test import "@source/lower"
|
||||||
test import "@source/interpreter"
|
test import "@source/interpreter"
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ State :: struct {
|
|||||||
type_execution_depth usize = 0
|
type_execution_depth usize = 0
|
||||||
type_execution_remaining usize = 0
|
type_execution_remaining usize = 0
|
||||||
value_expansion_depth usize = 0
|
value_expansion_depth usize = 0
|
||||||
|
return_type ast.NodeId = no_node
|
||||||
|
yield_type ast.NodeId = no_node
|
||||||
}
|
}
|
||||||
|
|
||||||
#! elaborates templates into cached concrete declarations in the caller-owned `builder` arena.
|
#! elaborates templates into cached concrete declarations in the caller-owned `builder` arena.
|
||||||
@@ -269,13 +271,66 @@ expand_compile_error proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
expand proc(state @mut State, id ast.NodeId) void ! Error {
|
expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||||
|
try expand_expected(state, id, no_node)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
expand_expected proc(state @mut State, id, expected ast.NodeId) void ! Error {
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
if (state.expanded.items[usize(id)] != 0) return
|
if (state.expanded.items[usize(id)] != 0) return
|
||||||
state.expanded.items[usize(id)] = 1
|
state.expanded.items[usize(id)] = 1
|
||||||
class :: ast.node_class(&state.tree, id)
|
class :: ast.node_class(&state.tree, id)
|
||||||
|
saved_return :: state.return_type
|
||||||
|
saved_yield :: state.yield_type
|
||||||
|
defer {
|
||||||
|
state.return_type = saved_return
|
||||||
|
state.yield_type = saved_yield
|
||||||
|
}
|
||||||
|
if (class == .function) {
|
||||||
|
state.return_type = ast.function_decl(&state.tree, id).return_type
|
||||||
|
state.yield_type = no_node
|
||||||
|
}
|
||||||
|
if (class == .statement) {
|
||||||
|
match ast.stmt_kind(&state.tree, id) {
|
||||||
|
.declaration: {
|
||||||
|
view :: ast.statement_decl(&state.tree, id)
|
||||||
|
annotation := no_node
|
||||||
|
if (view.type) |type| {
|
||||||
|
try expand(state, type)
|
||||||
|
annotation = type
|
||||||
|
}
|
||||||
|
try expand_expected(state, view.initializer, annotation)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
.assignment_stmt: {
|
||||||
|
view :: ast.assignment_stmt(&state.tree, id)
|
||||||
|
try expand(state, view.target)
|
||||||
|
try expand_expected(state, view.value, try expression_type(state, view.target, 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
.return_stmt, .yield_stmt: {
|
||||||
|
yielding :: ast.stmt_kind(&state.tree, id) == .yield_stmt
|
||||||
|
value :: if (yielding) ast.yield_stmt(&state.tree, id).value else ast.return_stmt(&state.tree, id).value
|
||||||
|
annotation :: if (yielding) state.yield_type else state.return_type
|
||||||
|
if (value) |operand| try expand_expected(state, operand, annotation)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (class == .field) {
|
||||||
|
view :: ast.field(&state.tree, id)
|
||||||
|
annotation := no_node
|
||||||
|
if (view.type) |type| {
|
||||||
|
try expand(state, type)
|
||||||
|
annotation = type
|
||||||
|
}
|
||||||
|
if (view.value) |value| try expand_expected(state, value, annotation)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .block_expr) {
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .block_expr) {
|
||||||
body :: ast.block_expr(&state.tree, id)
|
body :: ast.block_expr(&state.tree, id)
|
||||||
annotation :: try value_block_type(state, body, 0)
|
annotation :: if (expected != no_node) expected else try value_block_type(state, body, 0)
|
||||||
if (is_type(state, annotation) or type_record_storage(state, annotation, 0)) {
|
if (is_type(state, annotation) or type_record_storage(state, annotation, 0)) {
|
||||||
selected :: try evaluate_type_value_block(state, id, annotation)
|
selected :: try evaluate_type_value_block(state, id, annotation)
|
||||||
if (selected == no_node) {
|
if (selected == no_node) {
|
||||||
@@ -289,6 +344,7 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
}
|
}
|
||||||
state.value_expansion_depth += 1
|
state.value_expansion_depth += 1
|
||||||
defer state.value_expansion_depth -= 1
|
defer state.value_expansion_depth -= 1
|
||||||
|
state.yield_type = annotation
|
||||||
try expand(state, body)
|
try expand(state, body)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -374,8 +430,8 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
}
|
}
|
||||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .catch_expr) {
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .catch_expr) {
|
||||||
expression :: ast.catch_expr(&state.tree, id)
|
expression :: ast.catch_expr(&state.tree, id)
|
||||||
|
try expand_expected(state, expression.operand, expected)
|
||||||
if (expression.capture) |capture| {
|
if (expression.capture) |capture| {
|
||||||
try expand(state, expression.operand)
|
|
||||||
annotation :: try expression_type(state, expression.operand, 0)
|
annotation :: try expression_type(state, expression.operand, 0)
|
||||||
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .fallible_type) {
|
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .fallible_type) {
|
||||||
hashmap.put(&state.capture_types, capture, ast.binary_expr(&state.tree, annotation).right) catch |err| {
|
hashmap.put(&state.capture_types, capture, ast.binary_expr(&state.tree, annotation).right) catch |err| {
|
||||||
@@ -386,17 +442,21 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
annotation :: if (expected != no_node) expected else try expression_type(state, id, 0)
|
||||||
|
state.yield_type = annotation
|
||||||
|
try expand_expected(state, expression.handler, annotation)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (class == .statement and ast.stmt_kind(&state.tree, id) == .expression) {
|
if (class == .statement and ast.stmt_kind(&state.tree, id) == .expression) {
|
||||||
expression :: ast.statement_expr(&state.tree, id)
|
expression :: ast.statement_expr(&state.tree, id)
|
||||||
if (
|
if (
|
||||||
ast.expr_kind(&state.tree, expression) == .match_expr
|
ast.expr_kind(&state.tree, expression) == .match_expr
|
||||||
and try specialize_match(state, expression, id)
|
and try specialize_match(state, expression, id, no_node)
|
||||||
) return
|
) return
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
class == .expression and ast.expr_kind(&state.tree, id) == .match_expr
|
class == .expression and ast.expr_kind(&state.tree, id) == .match_expr
|
||||||
and try specialize_match(state, id, no_node)
|
and try specialize_match(state, id, no_node, expected)
|
||||||
) return
|
) return
|
||||||
if (class == .statement and ast.stmt_kind(&state.tree, id) == .if_stmt) {
|
if (class == .statement and ast.stmt_kind(&state.tree, id) == .if_stmt) {
|
||||||
conditional :: ast.if_stmt(&state.tree, id)
|
conditional :: ast.if_stmt(&state.tree, id)
|
||||||
@@ -435,6 +495,7 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
}
|
}
|
||||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .comptime_expr) {
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .comptime_expr) {
|
||||||
operand :: ast.comptime_operand(&state.tree, id)
|
operand :: ast.comptime_operand(&state.tree, id)
|
||||||
|
try expand_expected(state, operand, expected)
|
||||||
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||||
value :: try evaluate(state, operand)
|
value :: try evaluate(state, operand)
|
||||||
if (value.annotation == no_node or !type_record_storage(state, value.annotation, 0))
|
if (value.annotation == no_node or !type_record_storage(state, value.annotation, 0))
|
||||||
@@ -456,6 +517,18 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
}
|
}
|
||||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .builtin_call) {
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .builtin_call) {
|
||||||
name :: ast.builtin_call(&state.tree, id).name
|
name :: ast.builtin_call(&state.tree, id).name
|
||||||
|
if (
|
||||||
|
mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "some")
|
||||||
|
and expected != no_node and ast.call_arg_count(&state.tree, id) == 1
|
||||||
|
) {
|
||||||
|
annotation := try canonical_type(state, expected)
|
||||||
|
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .fallible_type)
|
||||||
|
annotation = try canonical_type(state, ast.binary_expr(&state.tree, annotation).left)
|
||||||
|
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .optional_type) {
|
||||||
|
try expand_expected(state, ast.call_arg(&state.tree, id, 0), ast.type_operand(&state.tree, annotation))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "compile_error")) {
|
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "compile_error")) {
|
||||||
try expand_compile_error(state, id)
|
try expand_compile_error(state, id)
|
||||||
return
|
return
|
||||||
@@ -482,9 +555,112 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
and ast.node_class(&state.tree, resolved.data.declaration) == .function
|
and ast.node_class(&state.tree, resolved.data.declaration) == .function
|
||||||
and template(state, resolved.data.declaration)
|
and template(state, resolved.data.declaration)
|
||||||
) {
|
) {
|
||||||
try specialize_call(state, id, resolved.data.declaration)
|
try specialize_call(state, id, resolved.data.declaration, expected)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
signature := no_node
|
||||||
|
if (resolved.kind == .declaration and ast.node_class(&state.tree, resolved.data.declaration) == .function)
|
||||||
|
signature = resolved.data.declaration
|
||||||
|
else signature = try expression_type(state, callee, 0)
|
||||||
|
if (
|
||||||
|
signature != no_node
|
||||||
|
and (ast.node_class(&state.tree, signature) == .function
|
||||||
|
or ast.expr_kind(&state.tree, signature) == .function_type)
|
||||||
|
and ast.function_param_count(&state.tree, signature) == ast.call_arg_count(&state.tree, id)
|
||||||
|
) {
|
||||||
|
for (0..ast.call_arg_count(&state.tree, id)) |i| {
|
||||||
|
annotation :: ast.param(&state.tree, ast.function_param(&state.tree, signature, i)).type
|
||||||
|
try expand_expected(state, ast.call_arg(&state.tree, id, i), annotation)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (class == .expression and expected != no_node) {
|
||||||
|
kind :: ast.expr_kind(&state.tree, id)
|
||||||
|
if (kind == .unary) {
|
||||||
|
view :: ast.unary_expr(&state.tree, id)
|
||||||
|
if (view.operator == .negate or view.operator == .bit_not or view.operator == .logical_not) {
|
||||||
|
try expand_expected(state, view.operand, expected)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kind == .binary and !is_type(state, expected)) {
|
||||||
|
view :: ast.binary_expr(&state.tree, id)
|
||||||
|
match view.operator {
|
||||||
|
.add, .subtract, .multiply, .divide, .bit_and, .bit_or, .bit_xor,
|
||||||
|
.logical_and, .logical_or: {
|
||||||
|
try expand_expected(state, view.left, expected)
|
||||||
|
try expand_expected(state, view.right, expected)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
.shift_left, .shift_right, .shift_left_saturating: {
|
||||||
|
try expand_expected(state, view.left, expected)
|
||||||
|
try expand(state, view.right)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kind == .try_expr) {
|
||||||
|
try expand_expected(state, ast.type_operand(&state.tree, id), expected)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (kind == .match_expr) {
|
||||||
|
try expand(state, ast.match_expr(&state.tree, id).subject)
|
||||||
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
||||||
|
arm_id :: ast.match_arm(&state.tree, id, i)
|
||||||
|
arm :: ast.match_arm_view(&state.tree, arm_id)
|
||||||
|
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j|
|
||||||
|
try expand(state, ast.match_pattern(&state.tree, arm_id, j))
|
||||||
|
state.yield_type = expected
|
||||||
|
annotation :: if (arm.expression) expected else no_node
|
||||||
|
try expand_expected(state, arm.body, annotation)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (class == .expression) {
|
||||||
|
kind :: ast.expr_kind(&state.tree, id)
|
||||||
|
if (kind == .record_literal or kind == .array_literal) {
|
||||||
|
annotation := expected
|
||||||
|
if (kind == .record_literal) {
|
||||||
|
if (ast.record_literal(&state.tree, id).type) |type| {
|
||||||
|
try expand(state, type)
|
||||||
|
annotation = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shape := if (annotation != no_node) try canonical_type(state, annotation) else no_node
|
||||||
|
if (shape != no_node and ast.expr_kind(&state.tree, shape) == .fallible_type)
|
||||||
|
shape = try canonical_type(state, ast.binary_expr(&state.tree, shape).left)
|
||||||
|
if (shape != no_node and ast.expr_kind(&state.tree, shape) == .optional_type)
|
||||||
|
shape = try canonical_type(state, ast.type_operand(&state.tree, shape))
|
||||||
|
if (
|
||||||
|
kind == .array_literal and shape != no_node
|
||||||
|
and (ast.expr_kind(&state.tree, shape) == .array_type or ast.expr_kind(&state.tree, shape) == .slice_type)
|
||||||
|
) {
|
||||||
|
element :: if (ast.expr_kind(&state.tree, shape) == .array_type)
|
||||||
|
ast.array_type(&state.tree, shape).element else ast.slice_type(&state.tree, shape).element
|
||||||
|
for (0..ast.array_element_count(&state.tree, id)) |i|
|
||||||
|
try expand_expected(state, ast.array_element(&state.tree, id, i), element)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
kind == .record_literal and shape != no_node
|
||||||
|
and (ast.expr_kind(&state.tree, shape) == .record_type or ast.expr_kind(&state.tree, shape) == .union_type)
|
||||||
|
) {
|
||||||
|
for (0..ast.record_field_count(&state.tree, id)) |i| {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
|
||||||
|
slot :: if (field.name != null) type_record_slot(state, shape, field.name?) else i
|
||||||
|
field_type := no_node
|
||||||
|
if (slot < ast.record_field_count(&state.tree, shape)) {
|
||||||
|
if (ast.field(&state.tree, ast.record_field(&state.tree, shape, slot)).type) |type|
|
||||||
|
field_type = type
|
||||||
|
}
|
||||||
|
if (field.value) |value| try expand_expected(state, value, field_type)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
count :: ast.child_count(&state.tree, id)
|
count :: ast.child_count(&state.tree, id)
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
@@ -1436,7 +1612,7 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
|
|||||||
# runtime bodies share the type-producer selector, but retain ordinary runtime
|
# runtime bodies share the type-producer selector, but retain ordinary runtime
|
||||||
# parameters and control transfers in the selected arm.
|
# parameters and control transfers in the selected arm.
|
||||||
@hide:file
|
@hide:file
|
||||||
specialize_match proc(state @mut State, id, statement ast.NodeId) bool ! Error {
|
specialize_match proc(state @mut State, id, statement, expected ast.NodeId) bool ! Error {
|
||||||
explicit :: ast.expr_kind(&state.tree, ast.match_expr(&state.tree, id).subject) == .comptime_expr
|
explicit :: ast.expr_kind(&state.tree, ast.match_expr(&state.tree, id).subject) == .comptime_expr
|
||||||
try expand_inline_match(state, id, no_node)
|
try expand_inline_match(state, id, no_node)
|
||||||
if (ast.expr_kind(&state.tree, id) != .match_expr) return true
|
if (ast.expr_kind(&state.tree, id) != .match_expr) return true
|
||||||
@@ -1494,7 +1670,7 @@ specialize_match proc(state @mut State, id, statement ast.NodeId) bool ! Error {
|
|||||||
try reject_evaluation(state, id)
|
try reject_evaluation(state, id)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
try expand(state, selected)
|
try expand_expected(state, selected, expected)
|
||||||
if (statement != no_node) {
|
if (statement != no_node) {
|
||||||
replacement :: try ast.add_expression_stmt(state.builder, selected)
|
replacement :: try ast.add_expression_stmt(state.builder, selected)
|
||||||
try replace(state, statement, replacement)
|
try replace(state, statement, replacement)
|
||||||
@@ -1632,7 +1808,48 @@ compiletime_result proc(state @State, annotation ast.NodeId) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
|
inference_contextual proc(state @State, id ast.NodeId) bool {
|
||||||
|
kind :: ast.expr_kind(&state.tree, id)
|
||||||
|
if (kind == .literal_null or kind == .literal_string) return true
|
||||||
|
if (ast.expr_kind(&state.tree, id) == .record_literal) {
|
||||||
|
annotation :: ast.record_literal(&state.tree, id).type
|
||||||
|
return annotation == null or ast.expr_kind(&state.tree, annotation?) == .enum_literal
|
||||||
|
}
|
||||||
|
return flexible(state, id) or contextual_literal(state, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
inference_phase proc(state @State, id ast.NodeId) usize {
|
||||||
|
if (inference_contextual(state, id)) return 2
|
||||||
|
if (ast.expr_kind(&state.tree, id) == .call) {
|
||||||
|
callee :: binding(state, ast.call_expr(&state.tree, id).callee)
|
||||||
|
if (
|
||||||
|
callee.kind == .declaration and ast.node_class(&state.tree, callee.data.declaration) == .function
|
||||||
|
and template(state, callee.data.declaration)
|
||||||
|
and ast.call_arg_count(&state.tree, id) != ast.function_param_count(&state.tree, callee.data.declaration)
|
||||||
|
) return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
annotation_ready proc(
|
||||||
|
state @State, function, id ast.NodeId, arguments []Value, depth usize,
|
||||||
|
) bool {
|
||||||
|
if (depth >= 128) return false
|
||||||
|
resolved :: binding(state, id)
|
||||||
|
if (resolved.kind == .declaration) {
|
||||||
|
index :: parameter_index(state, function, resolved.data.declaration)
|
||||||
|
if (index != no_index) return arguments[index].kind != .invalid
|
||||||
|
}
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
||||||
|
if (!annotation_ready(state, function, ast.child(&state.tree, id, i), arguments, depth + 1)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
specialize_call proc(state @mut State, call, function, expected ast.NodeId) void ! Error {
|
||||||
if (state.depth >= 128 or state.instances.items.len >= 4096) {
|
if (state.depth >= 128 or state.instances.items.len >= 4096) {
|
||||||
try reject(state, call, .specialization_limit)
|
try reject(state, call, .specialization_limit)
|
||||||
return
|
return
|
||||||
@@ -1664,8 +1881,9 @@ specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
|
|||||||
if (view.comptime and !explicit) continue
|
if (view.comptime and !explicit) continue
|
||||||
argument :: ast.call_arg(&state.tree, call, position)
|
argument :: ast.call_arg(&state.tree, call, position)
|
||||||
position += 1
|
position += 1
|
||||||
try expand(state, argument)
|
|
||||||
if (type_result or view.comptime) {
|
if (type_result or view.comptime) {
|
||||||
|
context_annotation :: try argument_annotation(state, function, view.type, arguments)
|
||||||
|
try expand_expected(state, argument, context_annotation)
|
||||||
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||||
annotation := view.type
|
annotation := view.type
|
||||||
contextual := contextual_literal(state, argument)
|
contextual := contextual_literal(state, argument)
|
||||||
@@ -1689,7 +1907,16 @@ specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!explicit) {
|
if (!explicit) {
|
||||||
for (0..usize(2)) |phase| {
|
# rigid arguments constrain first; result context then types nested generic
|
||||||
|
# calls before flexible literals choose their default widths.
|
||||||
|
for (0..usize(3)) |phase| {
|
||||||
|
if (phase == 1 and expected != no_node) {
|
||||||
|
result :: ast.function_decl(&state.tree, function).return_type
|
||||||
|
if (!try infer_result(state, function, result, expected, arguments)) {
|
||||||
|
try reject(state, call, .cannot_infer_generic)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
position = 0
|
position = 0
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
parameter :: ast.function_param(&state.tree, function, i)
|
parameter :: ast.function_param(&state.tree, function, i)
|
||||||
@@ -1697,12 +1924,12 @@ specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
|
|||||||
if (view.comptime) continue
|
if (view.comptime) continue
|
||||||
argument :: runtime_arguments.items[position]
|
argument :: runtime_arguments.items[position]
|
||||||
position += 1
|
position += 1
|
||||||
contextual := flexible(state, argument) or contextual_literal(state, argument)
|
contextual :: inference_contextual(state, argument)
|
||||||
if (ast.expr_kind(&state.tree, argument) == .record_literal) {
|
if (inference_phase(state, argument) != phase) continue
|
||||||
annotation :: ast.record_literal(&state.tree, argument).type
|
if (annotation_ready(state, function, view.type, arguments, 0)) {
|
||||||
contextual = annotation == null or ast.expr_kind(&state.tree, annotation?) == .enum_literal
|
annotation :: try argument_annotation(state, function, view.type, arguments)
|
||||||
|
try expand_expected(state, argument, annotation)
|
||||||
}
|
}
|
||||||
if (contextual != (phase == 1)) continue
|
|
||||||
actual :: try expression_type(state, argument, 0)
|
actual :: try expression_type(state, argument, 0)
|
||||||
# contextual literals need not have an independent type; the rewritten
|
# contextual literals need not have an independent type; the rewritten
|
||||||
# call checks them against the substituted signature after inference.
|
# call checks them against the substituted signature after inference.
|
||||||
@@ -1723,6 +1950,16 @@ specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
position = 0
|
||||||
|
for (0..count) |i| {
|
||||||
|
view :: ast.param(&state.tree, ast.function_param(&state.tree, function, i))
|
||||||
|
if (view.comptime or type_result) continue
|
||||||
|
argument :: runtime_arguments.items[position]
|
||||||
|
position += 1
|
||||||
|
if (state.expanded.items[usize(argument)] != 0) continue
|
||||||
|
annotation :: try argument_annotation(state, function, view.type, arguments)
|
||||||
|
try expand_expected(state, argument, annotation)
|
||||||
|
}
|
||||||
key := usize(function)
|
key := usize(function)
|
||||||
for arguments |argument| {
|
for arguments |argument| {
|
||||||
if (argument.kind != .invalid) key = mix(key, try value_hash(state, argument))
|
if (argument.kind != .invalid) key = mix(key, try value_hash(state, argument))
|
||||||
@@ -2021,10 +2258,25 @@ contextual_literal proc(state @State, id ast.NodeId) bool {
|
|||||||
return kind == .record_literal or kind == .array_literal or kind == .enum_literal or kind == .block_expr
|
return kind == .record_literal or kind == .array_literal or kind == .enum_literal or kind == .block_expr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
annotation_generic proc(state @State, function, id ast.NodeId, depth usize) bool {
|
||||||
|
if (depth >= 128) return true
|
||||||
|
resolved :: binding(state, id)
|
||||||
|
if (
|
||||||
|
resolved.kind == .declaration
|
||||||
|
and parameter_index(state, function, resolved.data.declaration) != no_index
|
||||||
|
) return true
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
||||||
|
if (annotation_generic(state, function, ast.child(&state.tree, id, i), depth + 1)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
argument_annotation proc(
|
argument_annotation proc(
|
||||||
state @mut State, function, annotation ast.NodeId, arguments []Value,
|
state @mut State, function, annotation ast.NodeId, arguments []Value,
|
||||||
) ast.NodeId ! Error {
|
) ast.NodeId ! Error {
|
||||||
|
if (!annotation_generic(state, function, annotation, 0)) return annotation
|
||||||
mapping :: try mem.alloc(ast.NodeId, state.allocator, state.original_count)
|
mapping :: try mem.alloc(ast.NodeId, state.allocator, state.original_count)
|
||||||
defer mem.free(state.allocator, mapping)
|
defer mem.free(state.allocator, mapping)
|
||||||
mem.set(mapping, no_node)
|
mem.set(mapping, no_node)
|
||||||
@@ -2057,6 +2309,7 @@ argument_annotation proc(
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Error {
|
evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Error {
|
||||||
|
try expand_expected(state, id, annotation)
|
||||||
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0)) {
|
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0)) {
|
||||||
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
||||||
state.type_execution_depth += 1
|
state.type_execution_depth += 1
|
||||||
@@ -2667,6 +2920,7 @@ type_snapshot_catch proc(
|
|||||||
view :: ast.catch_expr(&state.tree, id)
|
view :: ast.catch_expr(&state.tree, id)
|
||||||
operand :: try type_snapshot(state, execution, view.operand)
|
operand :: try type_snapshot(state, execution, view.operand)
|
||||||
if (operand == no_node) return no_node
|
if (operand == no_node) return no_node
|
||||||
|
try expand_expected(state, operand, annotation)
|
||||||
operand_type :: try expression_type(state, operand, 0)
|
operand_type :: try expression_type(state, operand, 0)
|
||||||
if (operand_type == no_node or ast.expr_kind(&state.tree, operand_type) != .fallible_type) return no_node
|
if (operand_type == no_node or ast.expr_kind(&state.tree, operand_type) != .fallible_type) return no_node
|
||||||
channels :: ast.binary_expr(&state.tree, operand_type)
|
channels :: ast.binary_expr(&state.tree, operand_type)
|
||||||
@@ -3707,7 +3961,7 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
|
|||||||
return concrete.node
|
return concrete.node
|
||||||
}
|
}
|
||||||
if (!try expand_type_matches(state, expression)) return no_node
|
if (!try expand_type_matches(state, expression)) return no_node
|
||||||
try expand(state, expression)
|
try expand_expected(state, expression, annotation)
|
||||||
resolved :: binding(state, expression)
|
resolved :: binding(state, expression)
|
||||||
if (resolved.kind == .invalid) {
|
if (resolved.kind == .invalid) {
|
||||||
state.evaluation_diagnostic = resolved.data.diagnostic
|
state.evaluation_diagnostic = resolved.data.diagnostic
|
||||||
@@ -5233,6 +5487,25 @@ infer proc(state @mut State, function, pattern, actual ast.NodeId, arguments []m
|
|||||||
element :: if (actual_kind == .optional_type) ast.type_operand(&state.tree, concrete) else concrete
|
element :: if (actual_kind == .optional_type) ast.type_operand(&state.tree, concrete) else concrete
|
||||||
return try infer(state, function, ast.type_operand(&state.tree, pattern), element, arguments, contextual)
|
return try infer(state, function, ast.type_operand(&state.tree, pattern), element, arguments, contextual)
|
||||||
}
|
}
|
||||||
|
.fallible_type: {
|
||||||
|
if (actual_kind != .fallible_type) return false
|
||||||
|
p :: ast.binary_expr(&state.tree, pattern)
|
||||||
|
a :: ast.binary_expr(&state.tree, concrete)
|
||||||
|
return (try infer(state, function, p.left, a.left, arguments, contextual)
|
||||||
|
and try infer(state, function, p.right, a.right, arguments, contextual))
|
||||||
|
}
|
||||||
|
.function_type: {
|
||||||
|
if (actual_kind != .function_type) return false
|
||||||
|
count :: ast.function_param_count(&state.tree, pattern)
|
||||||
|
if (count != ast.function_param_count(&state.tree, concrete)) return false
|
||||||
|
for (0..count) |i| {
|
||||||
|
p :: ast.param(&state.tree, ast.function_param(&state.tree, pattern, i)).type
|
||||||
|
a :: ast.param(&state.tree, ast.function_param(&state.tree, concrete, i)).type
|
||||||
|
if (!try infer(state, function, p, a, arguments, contextual)) return false
|
||||||
|
}
|
||||||
|
return try infer(state, function, ast.callable_result(&state.tree, pattern),
|
||||||
|
ast.callable_result(&state.tree, concrete), arguments, contextual)
|
||||||
|
}
|
||||||
.call: {
|
.call: {
|
||||||
callee :: binding(state, ast.call_expr(&state.tree, pattern).callee)
|
callee :: binding(state, ast.call_expr(&state.tree, pattern).callee)
|
||||||
if (callee.kind != .declaration) return false
|
if (callee.kind != .declaration) return false
|
||||||
@@ -5253,6 +5526,44 @@ infer proc(state @mut State, function, pattern, actual ast.NodeId, arguments []m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
infer_result proc(
|
||||||
|
state @mut State,
|
||||||
|
function, pattern, actual ast.NodeId,
|
||||||
|
arguments []mut Value,
|
||||||
|
) bool ! Error {
|
||||||
|
concrete :: try canonical_type(state, actual)
|
||||||
|
if (concrete == no_node) return true
|
||||||
|
if (
|
||||||
|
ast.expr_kind(&state.tree, concrete) == .optional_type
|
||||||
|
and ast.expr_kind(&state.tree, pattern) != .optional_type
|
||||||
|
) {
|
||||||
|
resolved :: binding(state, pattern)
|
||||||
|
if (resolved.kind == .declaration) {
|
||||||
|
index :: parameter_index(state, function, resolved.data.declaration)
|
||||||
|
if (index != no_index) {
|
||||||
|
if (arguments[index].kind == .invalid)
|
||||||
|
return try infer(state, function, pattern, concrete, arguments, false)
|
||||||
|
if (arguments[index].kind == .type and try same_type(state, arguments[index].node, concrete)) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return try infer_result(state, function, pattern, ast.type_operand(&state.tree, concrete), arguments)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
ast.expr_kind(&state.tree, concrete) == .fallible_type
|
||||||
|
and ast.expr_kind(&state.tree, pattern) != .fallible_type
|
||||||
|
) return try infer_result(state, function, pattern, ast.binary_expr(&state.tree, concrete).left, arguments)
|
||||||
|
if (ast.expr_kind(&state.tree, pattern) == .fallible_type) {
|
||||||
|
view :: ast.binary_expr(&state.tree, pattern)
|
||||||
|
if (ast.expr_kind(&state.tree, concrete) != .fallible_type)
|
||||||
|
return try infer_result(state, function, view.left, concrete, arguments)
|
||||||
|
channels :: ast.binary_expr(&state.tree, concrete)
|
||||||
|
return (try infer_result(state, function, view.left, channels.left, arguments)
|
||||||
|
and try infer_result(state, function, view.right, channels.right, arguments))
|
||||||
|
}
|
||||||
|
return try infer(state, function, pattern, concrete, arguments, false)
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
materialize proc(state @mut State, value Value) Value ! Error {
|
materialize proc(state @mut State, value Value) Value ! Error {
|
||||||
result := value
|
result := value
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import "@source/parser"
|
|||||||
@hide:file
|
@hide:file
|
||||||
expect_specialization proc(
|
expect_specialization proc(
|
||||||
source []u8, expected diagnostics.Code, expected_count usize,
|
source []u8, expected diagnostics.Code, expected_count usize,
|
||||||
) void ! (mem.AllocError | diagnostics.CapacityError | strpool.InternError | testing.Error) {
|
) void ! (mem.AllocError | diagnostics.CapacityError | strpool.InternError | lexer.SourceError | testing.Error) {
|
||||||
strings := strpool.init(mem.c_allocator)
|
strings := strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strings)
|
defer strpool.deinit(&strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
@@ -57,7 +57,7 @@ infers_nested_nominal_pointer_parameters_and_readonly_slice_views test {
|
|||||||
`}
|
`}
|
||||||
`observe proc($T type, values []T) usize { return values.len }
|
`observe proc($T type, values []T) usize { return values.len }
|
||||||
`reserve proc($T type, storage @mut Storage(T), count usize) void {
|
`reserve proc($T type, storage @mut Storage(T), count usize) void {
|
||||||
` _ = observe(storage.items.ptr[..count])
|
` observe(storage.items.ptr[..count])
|
||||||
`}
|
`}
|
||||||
`append proc($T type, storage @mut Storage(T), value T) void {
|
`append proc($T type, storage @mut Storage(T), value T) void {
|
||||||
` reserve(storage, storage.items.len)
|
` reserve(storage, storage.items.len)
|
||||||
@@ -65,7 +65,7 @@ infers_nested_nominal_pointer_parameters_and_readonly_slice_views test {
|
|||||||
`}
|
`}
|
||||||
`release proc($T type, storage @mut Storage(T)) void {
|
`release proc($T type, storage @mut Storage(T)) void {
|
||||||
` allocation :: storage.items.ptr[..storage.capacity]
|
` allocation :: storage.items.ptr[..storage.capacity]
|
||||||
` _ = observe(allocation)
|
` observe(allocation)
|
||||||
`}
|
`}
|
||||||
`forward proc(storage @mut Storage(i16)) void {
|
`forward proc(storage @mut Storage(i16)) void {
|
||||||
` append(storage, 7)
|
` append(storage, 7)
|
||||||
@@ -127,3 +127,127 @@ infers_integer_builtin_results_from_their_type_or_rigid_operand test {
|
|||||||
, .cannot_infer_generic, 0,
|
, .cannot_infer_generic, 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infers_direct_results_before_contextual_literal_defaults test {
|
||||||
|
try expect_specialization(
|
||||||
|
`make proc($T type) T { return T(7) }
|
||||||
|
`identity proc($T type, value T) T { return value }
|
||||||
|
`sized proc($N usize, values [N]i16) [N]i16 { return values }
|
||||||
|
`consume proc(value i16) i16 { return value }
|
||||||
|
`main proc() i16 {
|
||||||
|
` value i16 := identity(1)
|
||||||
|
` value = make()
|
||||||
|
` row [2]i16 :: sized([2, 3])
|
||||||
|
` return consume(make()) + value + row[0]
|
||||||
|
`}
|
||||||
|
, .cannot_infer_generic, 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
infers_type_and_value_arguments_from_generated_nominal_results test {
|
||||||
|
try expect_specialization(
|
||||||
|
`Fixed proc($T type, $N usize) type {
|
||||||
|
` return struct { value T = 0, count usize = N }
|
||||||
|
`}
|
||||||
|
`make proc($T type, $N usize) Fixed(T, N) { return {} }
|
||||||
|
`identity proc($T type, $N usize, value T) Fixed(T, N) {
|
||||||
|
` return { value = value }
|
||||||
|
`}
|
||||||
|
`main proc() i16 {
|
||||||
|
` first Fixed(i16, 2) :: make()
|
||||||
|
` second Fixed(i16, 3) :: identity(i16(7))
|
||||||
|
` cached Fixed(i16, 2) :: make()
|
||||||
|
` return first.value + second.value + cached.value
|
||||||
|
`}
|
||||||
|
, .cannot_infer_generic, 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
propagates_expected_results_through_nested_aggregates_and_success_channels test {
|
||||||
|
try expect_specialization(
|
||||||
|
`Error :: enum { failed }
|
||||||
|
`Record :: struct { value i16, rows [2]mut i16 }
|
||||||
|
`make proc($T type) T { return 7 }
|
||||||
|
`optional proc() ?i16 { return make() }
|
||||||
|
`fallible proc() i16 ! Error { return make() }
|
||||||
|
`main proc() Record {
|
||||||
|
` record Record := { value = make(), rows = [make(), make()] }
|
||||||
|
` record.value = make()
|
||||||
|
` record.rows[0] = make()
|
||||||
|
` maybe ?i16 :: make()
|
||||||
|
` result i16 ! Error :: make()
|
||||||
|
` return { value = make(), rows = [make(), make()] }
|
||||||
|
`}
|
||||||
|
, .cannot_infer_generic, 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
propagates_expected_results_through_match_arms_and_value_block_yields test {
|
||||||
|
try expect_specialization(
|
||||||
|
`make proc($T type) T { return T(7) }
|
||||||
|
`choose proc(flag bool) i16 {
|
||||||
|
` return match flag {
|
||||||
|
` true: make(),
|
||||||
|
` false: { yield make() },
|
||||||
|
` }
|
||||||
|
`}
|
||||||
|
`main proc() i16 {
|
||||||
|
` value i16 :: { yield make() }
|
||||||
|
` return value + choose(true)
|
||||||
|
`}
|
||||||
|
, .cannot_infer_generic, 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rejects_expected_results_that_conflict_with_rigid_runtime_arguments test {
|
||||||
|
try expect_specialization(
|
||||||
|
`identity proc($T type, value T) T { return value }
|
||||||
|
`main proc() i16 { return identity(u8(7)) }
|
||||||
|
, .cannot_infer_generic, 1,
|
||||||
|
)
|
||||||
|
try expect_specialization(
|
||||||
|
`Box proc($T type) type { return struct { value T } }
|
||||||
|
`make proc($T type, value T) Box(T) { return { value = value } }
|
||||||
|
`main proc() Box(i16) { return make(u8(7)) }
|
||||||
|
, .cannot_infer_generic, 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rejects_unrelated_nominal_results_and_unresolved_compile_time_arguments test {
|
||||||
|
try expect_specialization(
|
||||||
|
`Box proc($T type) type { return struct { value T = 0 } }
|
||||||
|
`Other proc($T type) type { return struct { value T = 0 } }
|
||||||
|
`make proc($T type) Box(T) { return {} }
|
||||||
|
`main proc() Other(i16) { return make() }
|
||||||
|
, .cannot_infer_generic, 1,
|
||||||
|
)
|
||||||
|
try expect_specialization(
|
||||||
|
`make proc($T type, $N usize) T { return T(7) }
|
||||||
|
`main proc() i16 { return make() }
|
||||||
|
, .cannot_infer_generic, 1,
|
||||||
|
)
|
||||||
|
try expect_specialization(
|
||||||
|
`make proc($T type) T { return T(7) }
|
||||||
|
`main proc() void { make() }
|
||||||
|
, .cannot_infer_generic, 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
preserves_explicit_arguments_and_explicit_type_procedure_inputs test {
|
||||||
|
try expect_specialization(
|
||||||
|
`identity proc($T type, value T) T { return value }
|
||||||
|
`main proc() i16 { return identity(u8, 7) }
|
||||||
|
, .return_type_mismatch, 1,
|
||||||
|
)
|
||||||
|
try expect_specialization(
|
||||||
|
`Box proc($T type) type { return struct { value T = 0 } }
|
||||||
|
`make proc($T type) Box(T) { return {} }
|
||||||
|
`main proc() Box(i16) { return make(u8) }
|
||||||
|
, .return_type_mismatch, 1,
|
||||||
|
)
|
||||||
|
try expect_specialization(
|
||||||
|
`Box proc($T type) type { return struct { value T } }
|
||||||
|
`main proc() void { value Box() :: { value = 7 } }
|
||||||
|
, .argument_count_mismatch, 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
+157
-11
@@ -998,6 +998,153 @@ CASES = {
|
|||||||
}
|
}
|
||||||
""", b"42\n", None,
|
""", b"42\n", None,
|
||||||
),
|
),
|
||||||
|
"expected_result_inference_direct_return_without_runtime_arguments": (
|
||||||
|
"""
|
||||||
|
answer proc($T type) T { return 42 }
|
||||||
|
main proc() i16 { return answer() }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_assignment_return_and_nested_parameters": (
|
||||||
|
"""
|
||||||
|
answer proc($T type) T { return 42 }
|
||||||
|
identity proc($T type, value T) T { return value }
|
||||||
|
read proc() i16 { return identity(42) }
|
||||||
|
consume proc(value i16) i16 { return value }
|
||||||
|
main proc() [4]i16 {
|
||||||
|
value i16 := identity(0)
|
||||||
|
value = answer()
|
||||||
|
return [value, read(), consume(identity(42)), consume(identity(answer()))]
|
||||||
|
}
|
||||||
|
""", b"[42, 42, 42, 42]\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_nested_aggregate_fields_and_elements": (
|
||||||
|
"""
|
||||||
|
identity proc($T type, value T) T { return value }
|
||||||
|
answer proc($T type) T { return 42 }
|
||||||
|
Inner :: struct { value i16 }
|
||||||
|
Outer :: struct { rows [2]Inner }
|
||||||
|
main proc() [2]i16 {
|
||||||
|
result Outer :: { rows = [{ value = identity(42) }, { value = answer() }] }
|
||||||
|
return [result.rows[0].value, result.rows[1].value]
|
||||||
|
}
|
||||||
|
""", b"[42, 42]\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_precedes_contextual_literal_defaults": (
|
||||||
|
"""
|
||||||
|
Key :: enum { first, last }
|
||||||
|
Record :: struct { value i16 }
|
||||||
|
identity proc($T type, value T) T { return value }
|
||||||
|
main proc() i16 {
|
||||||
|
number u8 :: identity(42)
|
||||||
|
key Key :: identity(.last)
|
||||||
|
record Record :: identity({ value = 42 })
|
||||||
|
absent ?i16 :: identity(null)
|
||||||
|
text []u8 :: identity("ab")
|
||||||
|
if (number != 42 or key != .last or absent != null or text.len != 2) return -1
|
||||||
|
return record.value
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_generated_nominal_type_and_value_arguments": (
|
||||||
|
"""
|
||||||
|
Box proc($T type, $count usize) type {
|
||||||
|
return struct { value T, length usize = count }
|
||||||
|
}
|
||||||
|
create proc($T type, $count usize, value T) Box(T, count) {
|
||||||
|
return { value = value }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
box Box(i16, 2) :: create(40)
|
||||||
|
explicit Box(i16, 2) :: create(i16, 2, 40)
|
||||||
|
if (box.value != explicit.value or box.length != 2) return -1
|
||||||
|
return box.value + i16(box.length)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_optional_and_fallible_success_contexts": (
|
||||||
|
"""
|
||||||
|
Error :: enum { failed }
|
||||||
|
identity proc($T type, value T) T { return value }
|
||||||
|
optional proc() ?i16 { return identity(42) }
|
||||||
|
fallible proc() i16 ! Error { return identity(42) }
|
||||||
|
main proc() [2]i16 { return [optional()?, fallible() catch 0] }
|
||||||
|
""", b"[42, 42]\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_match_arms_and_value_block_yields": (
|
||||||
|
"""
|
||||||
|
answer proc($T type) T { return 42 }
|
||||||
|
choose proc(flag bool) i16 {
|
||||||
|
return match flag {
|
||||||
|
true: answer(),
|
||||||
|
false: { yield answer() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() [2]i16 { return [choose(true), choose(false)] }
|
||||||
|
""", b"[42, 42]\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_inside_compile_time_type_producers": (
|
||||||
|
"""
|
||||||
|
answer proc($T type) T { return 2 }
|
||||||
|
Fixed proc() type {
|
||||||
|
count usize :: answer()
|
||||||
|
return [count]i16
|
||||||
|
}
|
||||||
|
main proc() Fixed() { return [19, 23] }
|
||||||
|
""", b"[19, 23]\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_cleanup_comptime_and_expression_contexts": (
|
||||||
|
"""
|
||||||
|
Error :: enum { failed }
|
||||||
|
answer proc($T type) T { return 21 }
|
||||||
|
fail proc($T type) T ! Error { return .failed }
|
||||||
|
recover proc() i16 { return fail() catch |error| { yield answer() } }
|
||||||
|
propagate proc() i16 ! Error { return try fail() }
|
||||||
|
Default :: struct { value i16 = answer() }
|
||||||
|
main proc() [7]i16 {
|
||||||
|
value ?i16 :: some!(answer())
|
||||||
|
constant i16 :: $(answer())
|
||||||
|
defaults Default :: {}
|
||||||
|
return [answer() + answer(), -answer(), ~answer(), fail() catch answer(),
|
||||||
|
recover(), (value?), constant + defaults.value]
|
||||||
|
}
|
||||||
|
""", b"[42, -21, -22, 21, 21, 21, 42]\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_structural_returns_and_nested_optional_presence": (
|
||||||
|
"""
|
||||||
|
Error :: enum { failed }
|
||||||
|
exact proc(value i16) i16 { return value }
|
||||||
|
callback proc($T type) @proc(value T) T { return exact }
|
||||||
|
optional proc($T type) ?T { return 42 }
|
||||||
|
fallible proc($T, $E type) T ! E { return 42 }
|
||||||
|
identity proc($T type, value T) T { return value }
|
||||||
|
main proc() [4]i16 {
|
||||||
|
call @proc(value i16) i16 :: callback()
|
||||||
|
maybe ?i16 :: optional()
|
||||||
|
result i16 ! Error :: fallible()
|
||||||
|
nested ??i16 :: identity(some!(null))
|
||||||
|
if (nested == null or nested? != null) return [0, 0, 0, 0]
|
||||||
|
return [call(42), (maybe?), result catch 0, 42]
|
||||||
|
}
|
||||||
|
""", b"[42, 42, 42, 42]\n", None,
|
||||||
|
),
|
||||||
|
"expected_result_inference_rejects_conflicting_rigid_argument": (
|
||||||
|
"""
|
||||||
|
identity proc($T type, value T) T { return value }
|
||||||
|
main proc() u16 {
|
||||||
|
value i16 :: 42
|
||||||
|
return identity(value)
|
||||||
|
}
|
||||||
|
""", b"", b"cannot infer consistent compile-time arguments",
|
||||||
|
),
|
||||||
|
"expected_result_inference_requires_context_for_result_only_arguments": (
|
||||||
|
"""
|
||||||
|
answer proc($T type) T { return 42 }
|
||||||
|
main proc() i16 {
|
||||||
|
value :: answer()
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""", b"", b"cannot infer consistent compile-time arguments",
|
||||||
|
),
|
||||||
"generic_contextual_enum_rejects_unknown_member": (
|
"generic_contextual_enum_rejects_unknown_member": (
|
||||||
"""
|
"""
|
||||||
Key :: enum { first }
|
Key :: enum { first }
|
||||||
@@ -9554,7 +9701,7 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
return left - divtrunc!(left, 100) * 100 == right - divtrunc!(right, 100) * 100
|
return left - divtrunc!(left, 100) * 100 == right - divtrunc!(right, 100) * 100
|
||||||
}
|
}
|
||||||
exercise proc(allocator mem.Allocator, state @mut State) i16 ! (hashmap.PutError | mem.AllocError) {
|
exercise proc(allocator mem.Allocator, state @mut State) i16 ! (hashmap.PutError | mem.AllocError) {
|
||||||
map := hashmap.init(i16, Value, collision_hash, equivalent, allocator)
|
map hashmap.HashMap(i16, Value, collision_hash, equivalent) := hashmap.init(allocator)
|
||||||
defer hashmap.deinit(&map)
|
defer hashmap.deinit(&map)
|
||||||
if (map.count != 0 or map.entries.len != 0 or state.allocations != 0) return -1
|
if (map.count != 0 or map.entries.len != 0 or state.allocations != 0) return -1
|
||||||
if (hashmap.get(&map, 0) != null) return -2
|
if (hashmap.get(&map, 0) != null) return -2
|
||||||
@@ -9604,7 +9751,7 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
return 42
|
return 42
|
||||||
}
|
}
|
||||||
exercise_automatic_and_string_keys proc(allocator mem.Allocator) i16 ! (hashmap.PutError | mem.AllocError) {
|
exercise_automatic_and_string_keys proc(allocator mem.Allocator) i16 ! (hashmap.PutError | mem.AllocError) {
|
||||||
automatic := hashmap.init(i16, Value, null, null, allocator)
|
automatic hashmap.AutoHashMap(i16, Value) := hashmap.init(allocator)
|
||||||
defer hashmap.deinit(&automatic)
|
defer hashmap.deinit(&automatic)
|
||||||
for (i16(0)..i16(12)) |key| {
|
for (i16(0)..i16(12)) |key| {
|
||||||
try hashmap.put(&automatic, key, Value{ number = key, items = [key, key + 1] })
|
try hashmap.put(&automatic, key, Value{ number = key, items = [key, key + 1] })
|
||||||
@@ -9619,7 +9766,7 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
if (hashmap.get(&automatic, minval!(i16))?.number != 42
|
if (hashmap.get(&automatic, minval!(i16))?.number != 42
|
||||||
or hashmap.get(&automatic, maxval!(i16))?.items[0] != 19) return -21
|
or hashmap.get(&automatic, maxval!(i16))?.items[0] != 19) return -21
|
||||||
if (hashmap.get(&automatic, -1) != null) return -22
|
if (hashmap.get(&automatic, -1) != null) return -22
|
||||||
strings hashmap.StringHashMap(Value) := { entries = &[], count = 0, allocator = allocator }
|
strings hashmap.StringHashMap(Value) := hashmap.init(allocator)
|
||||||
defer hashmap.deinit(&strings)
|
defer hashmap.deinit(&strings)
|
||||||
left [3]mut u8 := [97, 0, 98]
|
left [3]mut u8 := [97, 0, 98]
|
||||||
right [3]mut u8 := [97, 0, 98]
|
right [3]mut u8 := [97, 0, 98]
|
||||||
@@ -9673,7 +9820,7 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
Inner :: distinct u64
|
Inner :: distinct u64
|
||||||
Id :: distinct Inner
|
Id :: distinct Inner
|
||||||
integer_keys proc($K type) bool ! (hashmap.PutError | mem.AllocError) {
|
integer_keys proc($K type) bool ! (hashmap.PutError | mem.AllocError) {
|
||||||
map hashmap.AutoHashMap(K, usize) := hashmap.init(K, usize, null, null, mem.c_allocator)
|
map hashmap.AutoHashMap(K, usize) := hashmap.init(mem.c_allocator)
|
||||||
defer hashmap.deinit(&map)
|
defer hashmap.deinit(&map)
|
||||||
keys [4]K :: [minval!(K), maxval!(K), K(1), K(2)]
|
keys [4]K :: [minval!(K), maxval!(K), K(1), K(2)]
|
||||||
for keys |key, index| { try hashmap.put(&map, key, index) }
|
for keys |key, index| { try hashmap.put(&map, key, index) }
|
||||||
@@ -9698,7 +9845,7 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
if (!try integer_keys(i32) or !try integer_keys(u32)) return -3
|
if (!try integer_keys(i32) or !try integer_keys(u32)) return -3
|
||||||
if (!try integer_keys(i64) or !try integer_keys(u64)) return -4
|
if (!try integer_keys(i64) or !try integer_keys(u64)) return -4
|
||||||
if (!try integer_keys(isize) or !try integer_keys(usize)) return -5
|
if (!try integer_keys(isize) or !try integer_keys(usize)) return -5
|
||||||
flags hashmap.AutoHashMap(bool, i16) := hashmap.init(bool, i16, null, null, mem.c_allocator)
|
flags hashmap.AutoHashMap(bool, i16) := hashmap.init(mem.c_allocator)
|
||||||
defer hashmap.deinit(&flags)
|
defer hashmap.deinit(&flags)
|
||||||
try hashmap.put(&flags, false, 19)
|
try hashmap.put(&flags, false, 19)
|
||||||
try hashmap.put(&flags, true, 23)
|
try hashmap.put(&flags, true, 23)
|
||||||
@@ -9706,14 +9853,14 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
duplicate := false
|
duplicate := false
|
||||||
hashmap.put(&flags, false, 99) catch |err| { duplicate = err == .key_exists }
|
hashmap.put(&flags, false, 99) catch |err| { duplicate = err == .key_exists }
|
||||||
if (!duplicate or flags.count != 2 or hashmap.get(&flags, false)? != 19) return -7
|
if (!duplicate or flags.count != 2 or hashmap.get(&flags, false)? != 19) return -7
|
||||||
sparse hashmap.AutoHashMap(SparseAlias, i16) := hashmap.init(SparseAlias, i16, null, null, mem.c_allocator)
|
sparse hashmap.AutoHashMap(SparseAlias, i16) := hashmap.init(mem.c_allocator)
|
||||||
defer hashmap.deinit(&sparse)
|
defer hashmap.deinit(&sparse)
|
||||||
try hashmap.put(&sparse, .negative, 7)
|
try hashmap.put(&sparse, .negative, 7)
|
||||||
try hashmap.put(&sparse, .first, 3)
|
try hashmap.put(&sparse, .first, 3)
|
||||||
try hashmap.put(&sparse, .far, 90)
|
try hashmap.put(&sparse, .far, 90)
|
||||||
if (sparse.count != 3 or hashmap.get(&sparse, Sparse.negative)? != 7) return -8
|
if (sparse.count != 3 or hashmap.get(&sparse, Sparse.negative)? != 7) return -8
|
||||||
if (hashmap.get(&sparse, .first)? != 3 or hashmap.get(&sparse, .far)? != 90) return -9
|
if (hashmap.get(&sparse, .first)? != 3 or hashmap.get(&sparse, .far)? != 90) return -9
|
||||||
ids hashmap.AutoHashMap(Id, i16) := hashmap.init(Id, i16, null, null, mem.c_allocator)
|
ids hashmap.AutoHashMap(Id, i16) := hashmap.init(mem.c_allocator)
|
||||||
defer hashmap.deinit(&ids)
|
defer hashmap.deinit(&ids)
|
||||||
try hashmap.put(&ids, Id(Inner(0)), 19)
|
try hashmap.put(&ids, Id(Inner(0)), 19)
|
||||||
try hashmap.put(&ids, Id(Inner(maxval!(u64))), 23)
|
try hashmap.put(&ids, Id(Inner(maxval!(u64))), 23)
|
||||||
@@ -9734,15 +9881,14 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
return left - divtrunc!(left, 100) * 100 == right - divtrunc!(right, 100) * 100
|
return left - divtrunc!(left, 100) * 100 == right - divtrunc!(right, 100) * 100
|
||||||
}
|
}
|
||||||
main proc() i16 ! (hashmap.PutError | mem.AllocError) {
|
main proc() i16 ! (hashmap.PutError | mem.AllocError) {
|
||||||
# The alias supplies the library's private string callbacks.
|
strings hashmap.StringHashMap(i16) := hashmap.init(mem.c_allocator)
|
||||||
strings hashmap.StringHashMap(i16) := { entries = &[], count = 0, allocator = mem.c_allocator }
|
|
||||||
defer hashmap.deinit(&strings)
|
defer hashmap.deinit(&strings)
|
||||||
left [3]mut u8 := [97, 0, 98]
|
left [3]mut u8 := [97, 0, 98]
|
||||||
right [3]mut u8 := [97, 0, 98]
|
right [3]mut u8 := [97, 0, 98]
|
||||||
other [3]mut u8 := [97, 0, 99]
|
other [3]mut u8 := [97, 0, 99]
|
||||||
if ((&left).ptr == (&right).ptr) return -7
|
if ((&left).ptr == (&right).ptr) return -7
|
||||||
try hashmap.put(&strings, left[..], 42)
|
try hashmap.put(&strings, left[..], 42)
|
||||||
records hashmap.AutoHashMap(Key, i16) := hashmap.init(Key, i16, null, null, mem.c_allocator)
|
records hashmap.AutoHashMap(Key, i16) := hashmap.init(mem.c_allocator)
|
||||||
defer hashmap.deinit(&records)
|
defer hashmap.deinit(&records)
|
||||||
first Key := { padded = { tag = 7, number = maxval!(u64) }, rows = [{ values = [2, 9] }, { values = [4, 8] }] }
|
first Key := { padded = { tag = 7, number = maxval!(u64) }, rows = [{ values = [2, 9] }, { values = [4, 8] }] }
|
||||||
same Key := { padded = { tag = 7, number = maxval!(u64) }, rows = [{ values = [2, 9] }, { values = [4, 8] }] }
|
same Key := { padded = { tag = 7, number = maxval!(u64) }, rows = [{ values = [2, 9] }, { values = [4, 8] }] }
|
||||||
@@ -9774,7 +9920,7 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
try hashmap.put(&strings, "", 7)
|
try hashmap.put(&strings, "", 7)
|
||||||
try hashmap.put(&strings, other[..], 23)
|
try hashmap.put(&strings, other[..], 23)
|
||||||
if (strings.count != 3 or hashmap.get(&strings, "")? != 7 or hashmap.get(&strings, other[..])? != 23) return -11
|
if (strings.count != 3 or hashmap.get(&strings, "")? != 7 or hashmap.get(&strings, other[..])? != 23) return -11
|
||||||
custom := hashmap.init(u16, i16, custom_hash, custom_eql, mem.c_allocator)
|
custom hashmap.HashMap(u16, i16, custom_hash, custom_eql) := hashmap.init(mem.c_allocator)
|
||||||
defer hashmap.deinit(&custom)
|
defer hashmap.deinit(&custom)
|
||||||
for (u16(1)..u16(20)) |key| { try hashmap.put(&custom, key, i16(key * 2)) }
|
for (u16(1)..u16(20)) |key| { try hashmap.put(&custom, key, i16(key * 2)) }
|
||||||
for (u16(1)..u16(20)) |key| {
|
for (u16(1)..u16(20)) |key| {
|
||||||
|
|||||||
Reference in New Issue
Block a user