feat: execute fallible type-valued compile-time storage
This commit is contained in:
@@ -116,7 +116,9 @@ initialized records/tuples containing `type` fields live inside compile-time pro
|
||||
|
||||
type-valued records/tuples may also start with `undefined`: `config struct { child type, count usize } := undefined`, then `config.child = i16` and `config.count = 2`. nested record fields, arrays of records, tuple ordinals and `field!` updates use the same persistent snapshots as initialized storage; partial copies retain independent initialization state. defaults do not initialize `undefined` fields. initialized leaves and array lengths can be read while other leaves remain uninitialized, but premature leaf reads and incomplete procedure arguments/results diagnose read-before-initialization. fully initialized results cross compile-time procedure boundaries and supply erased generic inputs, generated storage and defaults. empty shapes are initialized vacuously; each expanded record field/array cell shares the construction bound.
|
||||
|
||||
optionals containing types also stay in owned compile-time storage: `?type`, nested `??type`, and optional records/arrays of metadata support null, contextual payloads, explicit `some!`, whole-value updates, immutable captures, null comparisons and checked unwraps. `some!(null)` preserves an absent inner optional inside a present outer one. constant-input procedures exchange these snapshots; erased generic inputs retain content-based specialization identity and nominal type leaves. `if`/`while` captures and conditional expressions select generated types or runtime bodies without runtime metadata. for generated array children, bind the unwrap first (`T :: selected?`, then `[count]T`); postfix `?` after an array type unwraps the whole expression. undefined presence and incomplete payloads diagnose before observation or procedure boundaries. runtime metadata storage and fallible type-valued channels remain unsupported.
|
||||
optionals containing types also stay in owned compile-time storage: `?type`, nested `??type`, and optional records/arrays of metadata support null, contextual payloads, explicit `some!`, whole-value updates, immutable captures, null comparisons and checked unwraps. `some!(null)` preserves an absent inner optional inside a present outer one. constant-input procedures exchange these snapshots; erased generic inputs retain content-based specialization identity and nominal type leaves. `if`/`while` captures and conditional expressions select generated types or runtime bodies without runtime metadata. for generated array children, bind the unwrap first (`T :: selected?`, then `[count]T`); postfix `?` after an array type unwraps the whole expression. undefined presence and incomplete payloads diagnose before observation or procedure boundaries. runtime metadata storage remains unsupported.
|
||||
|
||||
fallible channels containing types also execute entirely at compile time: `type ! Error`, metadata records/arrays in either channel, and nested optionals retain owned active payloads. every producer input is explicit and constant; erased generic inputs, whole-value replacement and saved copies preserve exact nominal leaves and content-based specialization identity. `try` checks the complete error subset before propagating; `catch` captures an immutable error snapshot and selects generated types or runtime handlers. named metadata structs and tagged unions compose with ordinary error enums without runtime reflection. returns snapshot before scoped LIFO `defer`/`errdefer`; fallible `void` also permits bare returns and successful fallthrough. incomplete storage, invalid channels/payloads, capture mutation and runtime inputs diagnose. imported private types survive source/session disposal and repeated compilation. runtime locals still cannot store metadata aggregates; keep those values inside compile-time producers or pass them directly to erased inputs.
|
||||
|
||||
fixed type arrays may also start with `undefined` inside type/type-array producers: `types [2]mut type := undefined`, then fill `types[index] = i16` in a loop. nested and whole-array updates preserve shape, mutability and independent initialization state in local copies; `.len` is available before elements initialize. reading an uninitialized type leaf, returning an incomplete array or passing one to a compile-time procedure diagnoses a read before initialization. empty shapes are valid, but every dimension must be explicit and nonnegative. construction charges each expanded cell against the shared execution bound, including nested zero-leaf shapes.
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ initialized type-valued records/tuples support schemas/defaults, nested
|
||||
field/index updates and owned copies/captures inside compile-time producers.
|
||||
optional type-valued storage preserves nested presence, owned metadata payloads,
|
||||
checked unwraps and immutable captures across compile-time procedure boundaries.
|
||||
fallible type-valued channels own their active payloads, propagate checked errors,
|
||||
and select generated storage through `try`/`catch` and scoped cleanup.
|
||||
`field!` selects record/tuple/enum and sequence members using compile-time names,
|
||||
including runtime places and type-control updates without runtime reflection.
|
||||
`struct_type!` generates ordinary/C-layout records from constant name/type/default
|
||||
@@ -157,7 +159,8 @@ test discovery and FFI remain outside these completed slices.
|
||||
- [x] untagged-union materialization checkpoint: address-free untagged unions own full native-layout byte snapshots through interpreter/native execution, including scalar/IEEE reinterpretation, nested padded records/arrays, smaller overlapping updates, zero-sized storage, distinct members and generated nominal unions. explicit `$()` values, compile-time arguments, full-byte specialization-cache identity, generated defaults, nested optionals/tagged payloads and both fallible channels preserve exact types and independent copies. uninitialized tail/padding bytes diagnose read-before-initialization; every member layout is checked recursively to reject data pointers, callbacks, strings and slices rather than retain interpreter handles. imported helpers/diagnostics, source/session disposal and repeated compilation retain ownership. address-bearing union layouts and broader constant materialization remain open.
|
||||
- [x] readonly typed-slice materialization checkpoint: ordinary compile-time calls snapshot readonly slices of materializable elements into module-lifetime backing through interpreter/native execution. nominal bytes, wider integers, arrays/records, source-bound callbacks, nested readonly slices, empty/zero-sized elements and full-width signed/unsigned sentinels retain exact types and logical lengths. explicit `$()` values, computed/direct content-based specialization identity, generated defaults and nested optional/tagged/fallible channels preserve ownership across helper returns, imported private declarations, source/session disposal and repeat compilation. initialization, address-range, sentinel, mutable-slice and nonnull-pointer escape diagnostics remain checked; 100,000-value/128-level snapshot bounds reject oversized and cyclic storage. mutable slices, nonnull data pointers and address-bearing untagged union layouts remain open.
|
||||
- [x] uninitialized type-record checkpoint: compile-time records/tuples containing type values start with `undefined` and fill through nested field/index updates or whole-value replacement. persistent snapshots preserve independent partial copies; defaults do not initialize undefined fields. initialized leaves and empty shapes work while premature type/value reads and incomplete procedure arguments/results diagnose read-before-initialization. fully initialized results supply erased generic inputs, generated storage/defaults and cached nominal types through interpreter/native execution. ordinary mutability/schema validation and shared per-field/cell construction bounds remain checked; general type-valued execution and broader materialization remain open.
|
||||
- [x] optional type-storage checkpoint: `?type` and optional type-valued records/arrays use owned compile-time snapshots, including nested `some!(null)`, contextual payload construction, mutable whole-value/field/index replacement, immutable if/while/conditional captures, null comparisons and checked unwraps. constant-input procedures and erased generic arguments preserve specialization-cache identity and nominal leaves while generating runtime storage through interpreter/native execution. undefined presence, incomplete payloads, non-type values, capture mutation and runtime dependencies retain diagnostics. imported private nominal types, session/source disposal and repeat compilation preserve ownership. fallible type-valued channels, general type-valued execution and broader constant materialization remain open.
|
||||
- [x] optional type-storage checkpoint: `?type` and optional type-valued records/arrays use owned compile-time snapshots, including nested `some!(null)`, contextual payload construction, mutable whole-value/field/index replacement, immutable if/while/conditional captures, null comparisons and checked unwraps. constant-input procedures and erased generic arguments preserve specialization-cache identity and nominal leaves while generating runtime storage through interpreter/native execution. undefined presence, incomplete payloads, non-type values, capture mutation and runtime dependencies retain diagnostics. imported private nominal types, session/source disposal and repeat compilation preserve ownership. fallible type-valued channels are covered below; general type-valued execution and broader constant materialization remain open.
|
||||
- [x] fallible type-storage checkpoint: explicit success/error channels containing types use owned compile-time snapshots, including nested optional/record/array payloads, whole-value updates, independent copies, constant-input procedures and erased generic inputs. `try` validates error subsets and propagates through enclosing producers; `catch` binds immutable exact error payloads and selects generated types or runtime handlers. composed metadata errors preserve named variants, nominal leaves and cache identity. returned snapshots precede scoped LIFO `defer`/`errdefer` cleanup; fallible `void` supports bare returns and successful fallthrough. incomplete/uninitialized channels, invalid payloads/compositions, capture mutation and runtime dependencies retain diagnostics. imported private types, source/session disposal and repeat compilation preserve ownership through interpreter/native execution. runtime metadata storage, general type-valued execution and broader materialization remain open.
|
||||
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), 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`.
|
||||
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
||||
|
||||
@@ -1208,3 +1208,91 @@ optional_type_metadata_recompiles_owned_private_nominal_payloads test {
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
fallible_type_metadata_recompiles_owned_private_nominal_payloads test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
modules [2]mut ir.Module := undefined
|
||||
{
|
||||
session := init(mem.c_allocator)
|
||||
defer deinit(&session)
|
||||
root :: try add_package(&session)
|
||||
dependency :: try add_package(&session)
|
||||
bytes := try owned_text(
|
||||
`lib :: import "../library"
|
||||
`Child :: struct { value bool }
|
||||
`ErrorChild :: struct { value bool }
|
||||
`main proc() i16 {
|
||||
` if (lib.Box($(lib.make(true))) != lib.Box(lib.make(true))) return -1
|
||||
` if (lib.Box($(lib.make(false))) != lib.Box(lib.make(false))) return -2
|
||||
` Selected :: lib.Selected(true) catch bool
|
||||
` Recovered :: lib.Recovered()
|
||||
` if (Selected == Child or Recovered == ErrorChild) return -3
|
||||
` selected Selected :: { value = 40 }
|
||||
` recovered Recovered :: { value = 2 }
|
||||
` snapshot lib.Snapshot($(lib.make(true))) :: {
|
||||
` original = { values = [{ value = 19 }, { value = 21 }] },
|
||||
` replacement = { values = [{ value = 2 }] },
|
||||
` }
|
||||
` if (snapshot.original.values[0].value + snapshot.original.values[1].value != 40) return -4
|
||||
` if (snapshot.replacement.values[0].value != 2) return -5
|
||||
` failed lib.Box($(lib.make(false))) :: { values = [{ value = 2 }] }
|
||||
` if (failed.values[0].value != recovered.value) return -6
|
||||
` return selected.value + recovered.value
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&bytes)
|
||||
root_source :: try add_source(&session, root, "/fallible_types/app/main.hon", &bytes, &diagnostic_store)
|
||||
imported := try owned_text(
|
||||
`@hide:file
|
||||
`Child :: struct { value i16 }
|
||||
`@hide:file
|
||||
`ErrorChild :: struct { value i16 }
|
||||
`@hide:file
|
||||
`Config :: struct { child type, count usize }
|
||||
`@hide:file
|
||||
`Failure :: struct { child type, count usize }
|
||||
`make proc(success bool) Config ! Failure {
|
||||
` if (success) return Config { child = Child, count = 2 }
|
||||
` return Failure { child = ErrorChild, count = 1 }
|
||||
`}
|
||||
`Box proc(input Config ! Failure) type {
|
||||
` info :: input catch |err| { return struct { values [err.count]err.child } }
|
||||
` return struct { values [info.count]info.child }
|
||||
`}
|
||||
`Selected proc(success bool) type ! Failure {
|
||||
` info :: try make(success)
|
||||
` return info.child
|
||||
`}
|
||||
`Recovered proc() type {
|
||||
` return Selected(false) catch |err| err.child
|
||||
`}
|
||||
`Snapshot proc(input Config ! Failure) type {
|
||||
` changed := input
|
||||
` saved :: changed
|
||||
` changed = make(false)
|
||||
` return struct { original Box(saved), replacement Box(changed) }
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&imported)
|
||||
_ = try add_source(&session, dependency, "/fallible_types/library/types.hon", &imported, &diagnostic_store)
|
||||
try arraylist.append(&session.imports, Import{
|
||||
node = declaration(&session, root_source, 0).node,
|
||||
target = dependency,
|
||||
})
|
||||
modules[0] = try compile(&session, root, &diagnostic_store)
|
||||
errdefer ir.deinit(&modules[0])
|
||||
modules[1] = try compile(&session, root, &diagnostic_store)
|
||||
}
|
||||
defer ir.deinit(&modules[0])
|
||||
defer ir.deinit(&modules[1])
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
for modules |module| {
|
||||
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,6 +431,12 @@ expand_expected proc(state @mut State, id, expected ast.NodeId) void ! Error {
|
||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .catch_expr) {
|
||||
expression :: ast.catch_expr(&state.tree, id)
|
||||
try expand_expected(state, expression.operand, expected)
|
||||
operand_type :: try expression_type(state, expression.operand, 0)
|
||||
if (type_record_storage(state, operand_type, 0)
|
||||
and ast.expr_kind(&state.tree, operand_type) == .fallible_type) {
|
||||
try specialize_type_catch(state, id, operand_type, expected)
|
||||
return
|
||||
}
|
||||
if (expression.capture) |capture| {
|
||||
annotation :: try expression_type(state, expression.operand, 0)
|
||||
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .fallible_type) {
|
||||
@@ -1393,6 +1399,11 @@ inline_sum_fields proc(state @mut State, input ast.NodeId, fields @mut arraylist
|
||||
or ast.stmt_kind(&state.tree, declaration) != .declaration) continue
|
||||
if (ast.statement_decl(&state.tree, declaration).initializer != type) continue
|
||||
name :: try member_name(state, declaration)
|
||||
for (fields.items) |previous| {
|
||||
field :: ast.field(&state.tree, previous)
|
||||
if (ast.name_id(&state.tree, field.name?) != ast.name_id(&state.tree, name)) continue
|
||||
return try same_type(state, field.type?, type)
|
||||
}
|
||||
field :: try ast.add_field(state.builder, ast.node_span(&state.tree, type), name, type, null)
|
||||
try refresh(state)
|
||||
try arraylist.append(fields, field)
|
||||
@@ -1401,6 +1412,9 @@ inline_sum_fields proc(state @mut State, input ast.NodeId, fields @mut arraylist
|
||||
return false
|
||||
}
|
||||
if (kind != .enum_type and kind != .union_type) return false
|
||||
if (kind == .enum_type and ast.enum_type(&state.tree, type).backing != null) return false
|
||||
if (kind == .union_type and !ast.union_type(&state.tree, type).auto_tag
|
||||
and ast.union_type(&state.tree, type).tag == null) return false
|
||||
count :: if (kind == .enum_type) ast.enum_member_count(&state.tree, type)
|
||||
else ast.record_field_count(&state.tree, type)
|
||||
for (0..count) |i| {
|
||||
@@ -1412,8 +1426,10 @@ inline_sum_fields proc(state @mut State, input ast.NodeId, fields @mut arraylist
|
||||
if (kind == .union_type and field.type == null) return false
|
||||
duplicate := false
|
||||
for (fields.items) |previous| {
|
||||
if (ast.name_id(&state.tree, ast.field(&state.tree, previous).name?)
|
||||
== ast.name_id(&state.tree, field.name?)) duplicate = true
|
||||
name :: ast.field(&state.tree, previous).name?
|
||||
if (ast.name_id(&state.tree, name) != ast.name_id(&state.tree, field.name?)) continue
|
||||
if (name != field.name?) return false
|
||||
duplicate = true
|
||||
}
|
||||
if (duplicate) continue
|
||||
payload :: if (kind == .enum_type) try builtin(state, member, .void) else field.type?
|
||||
@@ -1537,7 +1553,7 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
|
||||
union_view :: ast.union_type(&state.tree, members_type)
|
||||
if (kind != .sum_type and union_view.tag != null)
|
||||
tag_type = try canonical_type(state, union_view.tag?)
|
||||
else if (type_record_storage(state, annotation, 0)) tag_type = try metadata_tags(state, annotation)
|
||||
else if (type_record_storage(state, annotation, 0)) tag_type = try metadata_tags(state, members_type)
|
||||
else {
|
||||
concrete :: try materialize(state, Value{ kind = .type, node = annotation })
|
||||
tag_type = try ast.add_union_tag_type(state.builder, span, concrete.node)
|
||||
@@ -1813,6 +1829,11 @@ type_storage_annotation proc(tree @ast.Tree, bindings []sema.Binding, input ast.
|
||||
kind :: ast.expr_kind(tree, input)
|
||||
if (kind == .alias_expr or kind == .optional_type)
|
||||
return type_storage_annotation(tree, bindings, ast.type_operand(tree, input), depth + 1)
|
||||
if (kind == .fallible_type or kind == .sum_type) {
|
||||
channels :: ast.binary_expr(tree, input)
|
||||
return (type_storage_annotation(tree, bindings, channels.left, depth + 1)
|
||||
or type_storage_annotation(tree, bindings, channels.right, depth + 1))
|
||||
}
|
||||
if (kind == .array_type)
|
||||
return type_storage_annotation(tree, bindings, ast.array_type(tree, input).element, depth + 1)
|
||||
if (kind == .slice_type)
|
||||
@@ -2958,6 +2979,7 @@ TypeExecution :: struct {
|
||||
first_owned ast.NodeId
|
||||
returned ast.NodeId = no_node
|
||||
result_type ast.NodeId
|
||||
failed bool = false
|
||||
yielded ast.NodeId = no_node
|
||||
yield_type ast.NodeId = no_node
|
||||
yield_depth usize = 0
|
||||
@@ -2990,8 +3012,22 @@ return_type_value proc(state @mut State, block, first_owned, result_type ast.Nod
|
||||
defer arraylist.deinit(&execution.locals)
|
||||
defer mem.free(state.allocator, execution.mapping)
|
||||
mem.set(execution.mapping, no_node)
|
||||
if (try execute_type_block(state, &execution, block) != .returned) return no_node
|
||||
return execution.returned
|
||||
flow :: try execute_type_block(state, &execution, block)
|
||||
if (flow == .returned) return execution.returned
|
||||
if (flow == .next) return try type_void_completion(state, result_type, block)
|
||||
return no_node
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_void_completion proc(state @mut State, annotation, at ast.NodeId) ast.NodeId ! Error {
|
||||
type :: try canonical_type(state, annotation)
|
||||
if (type == no_node or ast.expr_kind(&state.tree, type) != .fallible_type) return no_node
|
||||
child :: try canonical_type(state, ast.binary_expr(&state.tree, type).left)
|
||||
resolved :: binding(state, child)
|
||||
if (resolved.kind != .builtin or resolved.data.builtin != .void) return no_node
|
||||
value :: try ast.add_literal(state.builder, .void, ast.node_span(&state.tree, at))
|
||||
try refresh(state)
|
||||
return try type_execution_value(state, value, annotation)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -3131,6 +3167,12 @@ prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id as
|
||||
return
|
||||
}
|
||||
}
|
||||
if (kind == .try_expr) {
|
||||
execution.mapping[usize(id)] = try type_snapshot_try(state, execution, id)
|
||||
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
||||
execution.pending_flow = .invalid
|
||||
return
|
||||
}
|
||||
if (kind == .catch_expr) {
|
||||
execution.mapping[usize(id)] = try type_snapshot_catch(state, execution, id, no_node)
|
||||
if (execution.mapping[usize(id)] == no_node and execution.pending_flow == .next)
|
||||
@@ -3235,11 +3277,17 @@ type_snapshot_catch proc(
|
||||
owned :: try type_execution_value(state, operand, operand_type)
|
||||
if (owned == no_node) return no_node
|
||||
span :: ast.node_span(&state.tree, id)
|
||||
failed := false
|
||||
if (type_record_storage(state, operand_type, 0)) {
|
||||
if (!try type_array_initialized(state, owned, id)) return no_node
|
||||
failed = !try same_type(state,
|
||||
ast.typed_constant(&state.tree, ast.typed_constant(&state.tree, owned).value).type, channels.left)
|
||||
} else {
|
||||
boolean :: try builtin(state, id, .bool)
|
||||
yes :: try ast.add_boolean(state.builder, span, true)
|
||||
no :: try ast.add_boolean(state.builder, span, false)
|
||||
failed :: try ast.add_return_stmt(state.builder, span, yes)
|
||||
failure_statements [1]ast.NodeId :: [failed]
|
||||
returned :: try ast.add_return_stmt(state.builder, span, yes)
|
||||
failure_statements [1]ast.NodeId :: [returned]
|
||||
handler :: try ast.add_block(state.builder, span, failure_statements[..])
|
||||
caught :: try ast.add_catch_expr(state.builder, span, owned, null, handler, true)
|
||||
discarded :: try ast.add_expression_stmt(state.builder, caught)
|
||||
@@ -3249,8 +3297,10 @@ type_snapshot_catch proc(
|
||||
try refresh(state)
|
||||
guard :: try evaluate_ir_body(state, id, body, boolean)
|
||||
if (guard.kind != .boolean) return no_node
|
||||
failed = guard.boolean
|
||||
}
|
||||
payload :: ast.typed_constant(&state.tree, owned).value
|
||||
if (!guard.boolean) return try type_execution_value(state, payload, channels.left)
|
||||
if (!failed) return try type_execution_value(state, payload, channels.left)
|
||||
saved :: execution.locals.items.len
|
||||
defer execution.locals.items = execution.locals.items[..saved]
|
||||
if (view.capture) |capture| {
|
||||
@@ -3279,6 +3329,125 @@ type_snapshot_catch proc(
|
||||
return try type_execution_value(state, selected, result_type)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
specialize_type_catch proc(state @mut State, id, annotation, expected ast.NodeId) void ! Error {
|
||||
view :: ast.catch_expr(&state.tree, id)
|
||||
value :: try evaluate_expected(state, view.operand, annotation)
|
||||
if (value.kind != .aggregate) {
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
channels :: ast.binary_expr(&state.tree, annotation)
|
||||
payload :: value.node
|
||||
success :: try same_type(state, ast.typed_constant(&state.tree, payload).type, channels.left)
|
||||
selected := no_node
|
||||
if (success) selected = try type_execution_value(state, payload, channels.left)
|
||||
else {
|
||||
execution := TypeExecution{
|
||||
locals = arraylist.init(state.allocator),
|
||||
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
||||
first_owned = ast.NodeId(0),
|
||||
result_type = no_node,
|
||||
}
|
||||
defer arraylist.deinit(&execution.locals)
|
||||
defer mem.free(state.allocator, execution.mapping)
|
||||
mem.set(execution.mapping, no_node)
|
||||
if (view.capture) |capture| {
|
||||
resolved :: binding(state, capture)
|
||||
if (resolved.kind == .invalid) {
|
||||
state.evaluation_diagnostic = resolved.data.diagnostic
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
error :: try type_execution_value(state, payload, channels.right)
|
||||
if (error == no_node) {
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
try arraylist.append(&execution.locals, TypeLocal{
|
||||
declaration = capture, value = error, annotation = channels.right,
|
||||
})
|
||||
}
|
||||
selected = try type_snapshot(state, &execution, view.handler)
|
||||
if (view.handler_is_block and selected != no_node) {
|
||||
selected = try ast.add_block_expr(state.builder, ast.node_span(&state.tree, id), selected)
|
||||
try refresh(state)
|
||||
}
|
||||
}
|
||||
if (selected == no_node) {
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
context :: if (expected != no_node) expected else channels.left
|
||||
try expand_expected(state, selected, context)
|
||||
if (is_type(state, channels.left) or type_record_storage(state, channels.left, 0)) {
|
||||
selected = try type_execution_value(state, selected, channels.left)
|
||||
if (selected == no_node) {
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
result_type := channels.left
|
||||
contextual :: try canonical_type(state, context)
|
||||
if (contextual != no_node) {
|
||||
kind :: ast.expr_kind(&state.tree, contextual)
|
||||
if (kind == .optional_type
|
||||
and try same_type(state, ast.type_operand(&state.tree, contextual), channels.left))
|
||||
result_type = context
|
||||
if (kind == .fallible_type
|
||||
and try same_type(state, ast.binary_expr(&state.tree, contextual).left, channels.left))
|
||||
result_type = context
|
||||
}
|
||||
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id), result_type, selected)
|
||||
try refresh(state)
|
||||
}
|
||||
if (is_type(state, try expression_type(state, selected, 0))) {
|
||||
selected = try ast.add_type_operand(state.builder, ast.node_span(&state.tree, id), selected, true)
|
||||
try refresh(state)
|
||||
}
|
||||
try replace(state, id, selected)
|
||||
try refresh(state)
|
||||
state.bindings.items[usize(id)] = binding(state, selected)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_snapshot_try proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
||||
operand :: try type_snapshot(state, execution, ast.type_operand(&state.tree, id))
|
||||
if (operand == no_node) return no_node
|
||||
try expand(state, operand)
|
||||
annotation :: try expression_type(state, operand, 0)
|
||||
if (annotation == no_node or ast.expr_kind(&state.tree, annotation) != .fallible_type) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.invalid_fallible_operand, ast.node_span(&state.tree, id))
|
||||
return no_node
|
||||
}
|
||||
result :: try canonical_type(state, execution.result_type)
|
||||
if (execution.cleanup_depth > 0 or result == no_node
|
||||
or ast.expr_kind(&state.tree, result) != .fallible_type) {
|
||||
code diagnostics.Code :: if (execution.cleanup_depth > 0)
|
||||
.cleanup_control_flow else .try_outside_fallible
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, id))
|
||||
return no_node
|
||||
}
|
||||
channels :: ast.binary_expr(&state.tree, annotation)
|
||||
if (!try type_subset(state, channels.right, ast.binary_expr(&state.tree, result).right)) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.incompatible_propagated_error, ast.node_span(&state.tree, id))
|
||||
return no_node
|
||||
}
|
||||
# propagation is a catch whose error branch returns from the enclosing frame.
|
||||
span :: ast.node_span(&state.tree, id)
|
||||
capture :: try ast.add_identifier(state.builder, span, strpool.no_str)
|
||||
try refresh(state)
|
||||
value :: try reference(state, id, capture)
|
||||
returned :: try ast.add_return_stmt(state.builder, span, value)
|
||||
statements [1]ast.NodeId :: [returned]
|
||||
handler :: try ast.add_block(state.builder, span, statements[..])
|
||||
caught :: try ast.add_catch_expr(state.builder, span, operand, capture, handler, true)
|
||||
try refresh(state)
|
||||
return try type_snapshot_catch(state, execution, caught, channels.left)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_dispatch_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
||||
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, id).subject)
|
||||
@@ -3521,7 +3690,7 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
} else {
|
||||
if (ast.expr_kind(&state.tree, value.node) != .record_literal) return no_index
|
||||
active :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
|
||||
tags :: try metadata_tags(state, annotation)
|
||||
tags :: try metadata_tags(state, try inline_match_shape(state, annotation))
|
||||
tag :: try ast.add_enum_literal(state.builder, span, active.name?)
|
||||
dispatch = try ast.add_typed_constant(state.builder, span, tags, tag)
|
||||
}
|
||||
@@ -3553,12 +3722,13 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
valid = false
|
||||
continue
|
||||
}
|
||||
slot :: type_record_slot(state, annotation, ast.enum_literal(&state.tree, pattern))
|
||||
shape :: try inline_match_shape(state, annotation)
|
||||
slot :: type_record_slot(state, shape, ast.enum_literal(&state.tree, pattern))
|
||||
if (slot == no_index) {
|
||||
valid = false
|
||||
continue
|
||||
}
|
||||
payload :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot)).type?
|
||||
payload :: ast.field(&state.tree, ast.record_field(&state.tree, shape, slot)).type?
|
||||
if (binding(state, payload).kind == .builtin and binding(state, payload).data.builtin == .void)
|
||||
valid = false
|
||||
if (expected != no_node and !try same_type(state, expected, payload)) valid = false
|
||||
@@ -3902,8 +4072,48 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
||||
if (is_type(state, annotation)) return true
|
||||
type :: try canonical_type(state, annotation)
|
||||
if (type == no_node) return false
|
||||
if (!type_record_storage(state, type, 0)) {
|
||||
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, type), type, true)
|
||||
try refresh(state)
|
||||
try arraylist.append(&state.rewrites.constraints, constraint)
|
||||
return true
|
||||
}
|
||||
if (ast.expr_kind(&state.tree, type) == .optional_type)
|
||||
return try type_record_check(state, ast.type_operand(&state.tree, type))
|
||||
if (ast.expr_kind(&state.tree, type) == .fallible_type) {
|
||||
channels :: ast.binary_expr(&state.tree, type)
|
||||
error_type :: try canonical_type(state, channels.right)
|
||||
if (error_type == no_node) return false
|
||||
kind :: ast.expr_kind(&state.tree, error_type)
|
||||
if (kind != .enum_type and kind != .record_type and kind != .union_type and kind != .sum_type) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.invalid_fallible_type, ast.node_span(&state.tree, type))
|
||||
return false
|
||||
}
|
||||
if (!type_record_storage(state, error_type, 0)) {
|
||||
checked :: try ast.add_binary_type(state.builder, ast.node_span(&state.tree, type),
|
||||
try builtin(state, type, .void), error_type, true)
|
||||
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, type), checked, true)
|
||||
try refresh(state)
|
||||
try arraylist.append(&state.rewrites.constraints, constraint)
|
||||
} else if (kind == .union_type and !ast.union_type(&state.tree, error_type).auto_tag
|
||||
and ast.union_type(&state.tree, error_type).tag == null) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.invalid_fallible_type, ast.node_span(&state.tree, type))
|
||||
return false
|
||||
}
|
||||
if (!try type_record_check(state, channels.left)) return false
|
||||
return try type_record_check(state, channels.right)
|
||||
}
|
||||
if (ast.expr_kind(&state.tree, type) == .sum_type) {
|
||||
members :: ast.binary_expr(&state.tree, type)
|
||||
if (!try type_record_check(state, members.left) or !try type_record_check(state, members.right)) return false
|
||||
if (try inline_match_shape(state, type) != no_node) return true
|
||||
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.invalid_sum_type, ast.node_span(&state.tree, type))
|
||||
return false
|
||||
}
|
||||
if (ast.expr_kind(&state.tree, type) == .slice_type)
|
||||
return try type_record_check(state, ast.slice_type(&state.tree, type).element)
|
||||
if (ast.expr_kind(&state.tree, type) == .array_type) {
|
||||
@@ -4345,6 +4555,108 @@ type_optional_value proc(state @mut State, expression, type ast.NodeId) ast.Node
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_variant proc(state @mut State, annotation, name ast.NodeId) bool ! Error {
|
||||
type :: try canonical_type(state, annotation)
|
||||
if (type == no_node) return false
|
||||
kind :: ast.expr_kind(&state.tree, type)
|
||||
if (kind == .sum_type) {
|
||||
members :: ast.binary_expr(&state.tree, type)
|
||||
return try type_variant(state, members.left, name) or try type_variant(state, members.right, name)
|
||||
}
|
||||
if (kind == .union_type) return type_record_slot(state, type, name) != no_index
|
||||
if (kind != .enum_type) return false
|
||||
for (0..ast.enum_member_count(&state.tree, type)) |i| {
|
||||
member :: ast.field(&state.tree, ast.enum_member(&state.tree, type, i)).name?
|
||||
if (ast.name_id(&state.tree, member) == ast.name_id(&state.tree, name)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
# the payload's declared channel is retained even when success and error values
|
||||
# have overlapping shapes. no type-bearing snapshot enters runtime IR.
|
||||
@hide:file
|
||||
type_fallible_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error {
|
||||
if (!try type_record_check(state, type)) return no_node
|
||||
span :: ast.node_span(&state.tree, expression)
|
||||
kind :: ast.expr_kind(&state.tree, expression)
|
||||
if (kind == .literal_undefined) {
|
||||
result :: try ast.add_typed_constant(state.builder, span, type, expression)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
channels :: ast.binary_expr(&state.tree, type)
|
||||
actual :: try expression_type(state, expression, 0)
|
||||
if (actual != no_node and ast.expr_kind(&state.tree, actual) == .fallible_type) {
|
||||
if (try same_type(state, actual, type)) return expression
|
||||
source :: ast.binary_expr(&state.tree, actual)
|
||||
if (!try same_type(state, source.left, channels.left)
|
||||
or !try type_subset(state, source.right, channels.right)) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span)
|
||||
return no_node
|
||||
}
|
||||
return try type_fallible_value(state, ast.typed_constant(&state.tree, expression).value, type)
|
||||
}
|
||||
channel := channels.left
|
||||
if (actual != no_node and !try same_type(state, actual, channels.left)
|
||||
and try type_subset(state, actual, channels.right)) channel = channels.right
|
||||
name := no_node
|
||||
if (kind == .enum_literal) name = ast.enum_literal(&state.tree, expression)
|
||||
if (kind == .record_literal and ast.record_literal(&state.tree, expression).type != null) {
|
||||
tag :: ast.record_literal(&state.tree, expression).type?
|
||||
if (ast.expr_kind(&state.tree, tag) == .enum_literal) name = ast.enum_literal(&state.tree, tag)
|
||||
}
|
||||
if (name != no_node and try type_variant(state, channels.right, name)
|
||||
and !try type_variant(state, channels.left, name)) channel = channels.right
|
||||
if (kind == .record_literal and ast.record_literal(&state.tree, expression).type == null) {
|
||||
success :: try canonical_type(state, channels.left)
|
||||
failure :: try canonical_type(state, channels.right)
|
||||
if ((success == no_node or ast.expr_kind(&state.tree, success) != .record_type)
|
||||
and ast.expr_kind(&state.tree, failure) == .record_type) channel = channels.right
|
||||
}
|
||||
payload := try type_execution_value(state, expression, channel)
|
||||
if (payload == no_node) return no_node
|
||||
if (ast.expr_kind(&state.tree, payload) != .typed_constant) {
|
||||
payload = try ast.add_typed_constant(state.builder, span, channel, payload)
|
||||
try refresh(state)
|
||||
}
|
||||
result :: try ast.add_typed_constant(state.builder, span, type, payload)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_sum_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error {
|
||||
if (!try type_record_check(state, type)) return no_node
|
||||
actual :: try expression_type(state, expression, 0)
|
||||
if (actual != no_node and try same_type(state, actual, type)) return expression
|
||||
shape :: try inline_match_shape(state, type)
|
||||
if (shape == no_node) return no_node
|
||||
span :: ast.node_span(&state.tree, expression)
|
||||
input := expression
|
||||
if (actual != no_node and try type_subset(state, actual, type)) {
|
||||
source :: try canonical_type(state, actual)
|
||||
if (ast.expr_kind(&state.tree, source) == .record_type) {
|
||||
for (0..ast.record_field_count(&state.tree, shape)) |i| {
|
||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, shape, i))
|
||||
if (!try same_type(state, field.type?, actual)) continue
|
||||
member :: try ast.add_field(state.builder, span, field.name, null, expression)
|
||||
fields [1]ast.NodeId :: [member]
|
||||
input = try ast.add_record_literal(state.builder, span, null, fields[..])
|
||||
try refresh(state)
|
||||
break
|
||||
}
|
||||
} else if (ast.expr_kind(&state.tree, expression) == .typed_constant)
|
||||
input = ast.typed_constant(&state.tree, expression).value
|
||||
}
|
||||
value :: try type_union_value(state, input, shape)
|
||||
if (value == no_node) return no_node
|
||||
result :: try ast.add_typed_constant(state.builder, span, type,
|
||||
ast.typed_constant(&state.tree, value).value)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.NodeId ! Error {
|
||||
if (input == no_node or annotation == no_node) return no_node
|
||||
@@ -4412,6 +4724,10 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
|
||||
type :: try canonical_type(state, annotation)
|
||||
if (type != no_node and ast.expr_kind(&state.tree, type) == .optional_type)
|
||||
return try type_optional_value(state, expression, type)
|
||||
if (type != no_node and ast.expr_kind(&state.tree, type) == .fallible_type)
|
||||
return try type_fallible_value(state, expression, type)
|
||||
if (type != no_node and ast.expr_kind(&state.tree, type) == .sum_type)
|
||||
return try type_sum_value(state, expression, type)
|
||||
}
|
||||
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0))
|
||||
return try type_record_value(state, expression, annotation)
|
||||
@@ -4562,13 +4878,24 @@ execute_type_block proc(state @mut State, execution @mut TypeExecution, block as
|
||||
while (reached > 0) {
|
||||
reached -= 1
|
||||
statement :: ast.block_statement(&state.tree, block, reached)
|
||||
if (ast.stmt_kind(&state.tree, statement) != .defer_stmt) continue
|
||||
kind :: ast.stmt_kind(&state.tree, statement)
|
||||
if (kind != .defer_stmt and !(kind == .errdefer_stmt and execution.failed)) continue
|
||||
saved_depth :: execution.cleanup_yield_depth
|
||||
saved_loop :: execution.cleanup_loop_depth
|
||||
execution.cleanup_loop_depth = execution.loop_depth
|
||||
execution.cleanup_yield_depth = execution.yield_depth
|
||||
execution.cleanup_depth += 1
|
||||
cleanup :: try execute_type_block(state, execution, ast.cleanup_stmt(&state.tree, statement).body)
|
||||
cleanup_view :: ast.cleanup_stmt(&state.tree, statement)
|
||||
if (kind == .errdefer_stmt and cleanup_view.capture != null) {
|
||||
annotation :: ast.binary_expr(&state.tree, try canonical_type(state, execution.result_type)).right
|
||||
value :: ast.typed_constant(&state.tree, execution.returned).value
|
||||
try arraylist.append(&execution.locals, TypeLocal{
|
||||
declaration = cleanup_view.capture?, value = value, annotation = annotation,
|
||||
})
|
||||
}
|
||||
cleanup :: try execute_type_block(state, execution, cleanup_view.body)
|
||||
if (kind == .errdefer_stmt and cleanup_view.capture != null)
|
||||
execution.locals.items = execution.locals.items[..execution.locals.items.len - 1]
|
||||
execution.cleanup_depth -= 1
|
||||
execution.cleanup_yield_depth = saved_depth
|
||||
execution.cleanup_loop_depth = saved_loop
|
||||
@@ -4641,10 +4968,13 @@ execute_type_statement_body proc(state @mut State, execution @mut TypeExecution,
|
||||
}
|
||||
.defer_stmt: {}
|
||||
.errdefer_stmt: {
|
||||
result :: try canonical_type(state, execution.result_type)
|
||||
if (result == no_node or ast.expr_kind(&state.tree, result) != .fallible_type) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.errdefer_outside_fallible, ast.node_span(&state.tree, statement))
|
||||
return .invalid
|
||||
}
|
||||
}
|
||||
.yield_stmt: {
|
||||
if (execution.yield_depth == 0) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
@@ -4673,6 +5003,17 @@ execute_type_statement_body proc(state @mut State, execution @mut TypeExecution,
|
||||
execution.returned = try type_execution_value(state, expression, execution.result_type)
|
||||
if (execution.returned != no_node
|
||||
and !try type_array_initialized(state, execution.returned, value)) return .invalid
|
||||
if (execution.returned != no_node and type_record_storage(state, execution.result_type, 0)) {
|
||||
type :: try canonical_type(state, execution.result_type)
|
||||
if (type != no_node and ast.expr_kind(&state.tree, type) == .fallible_type) {
|
||||
payload :: ast.typed_constant(&state.tree, execution.returned).value
|
||||
execution.failed = !try same_type(state, ast.typed_constant(&state.tree, payload).type,
|
||||
ast.binary_expr(&state.tree, type).left)
|
||||
}
|
||||
}
|
||||
if (execution.returned != no_node) return .returned
|
||||
} else {
|
||||
execution.returned = try type_void_completion(state, execution.result_type, statement)
|
||||
if (execution.returned != no_node) return .returned
|
||||
}
|
||||
return .invalid
|
||||
|
||||
+349
@@ -2415,6 +2415,355 @@ CASES = {
|
||||
}
|
||||
""", b"", b"compile-time value",
|
||||
),
|
||||
"fallible_types_record_channels_select_runtime_storage": (
|
||||
"""
|
||||
Config :: struct { child ?type, count usize }
|
||||
Failure :: struct { child type, count usize }
|
||||
make proc(failed bool) Config ! Failure {
|
||||
if (failed) return Failure { child = bool, count = 2 }
|
||||
return Config { child = i16, count = 2 }
|
||||
}
|
||||
Selected proc(failed bool) type {
|
||||
config :: make(failed) catch |err| Config { child = err.child, count = err.count }
|
||||
T :: config.child?
|
||||
return [config.count]T
|
||||
}
|
||||
main proc() [2]i16 {
|
||||
values Selected(false) :: [40, 2]
|
||||
flags Selected(true) :: [true, false]
|
||||
if (!flags[0] or flags[1]) return [0, 0]
|
||||
return [values[0] + values[1], i16(flags.len)]
|
||||
}
|
||||
""", b"[42, 2]\n", None,
|
||||
),
|
||||
"fallible_types_optional_success_try_and_composed_errors": (
|
||||
"""
|
||||
First :: enum { first }
|
||||
Second :: enum { second }
|
||||
choose proc(present bool, failed bool) ?type ! First {
|
||||
if (failed) return .first
|
||||
if (present) return i16
|
||||
return null
|
||||
}
|
||||
forward proc(present bool, failed bool) ?type ! (First | Second) {
|
||||
selected :: try choose(present, failed)
|
||||
return selected
|
||||
}
|
||||
Selected proc(present bool, failed bool) type {
|
||||
selected :: forward(present, failed) catch |err| match err {
|
||||
.first: some!(bool),
|
||||
.second: some!(u16),
|
||||
}
|
||||
return if selected |T| T else u8
|
||||
}
|
||||
main proc() [2]i16 {
|
||||
value Selected(true, false) :: 40
|
||||
missing Selected(false, false) :: 2
|
||||
recovered Selected(true, true) :: true
|
||||
if (!recovered) return [0, 0]
|
||||
return [value + i16(missing), i16(sizeof!(Selected(false, true)))]
|
||||
}
|
||||
""", b"[42, 1]\n", None,
|
||||
),
|
||||
"fallible_types_copies_procedure_inputs_and_erased_cache_identity": (
|
||||
"""
|
||||
Config :: struct { child type, count usize }
|
||||
Failure :: struct { child type, count usize }
|
||||
good proc() Config ! Failure { return Config { child = i16, count = 2 } }
|
||||
bad proc() Config ! Failure { return Failure { child = bool, count = 2 } }
|
||||
pass proc(value Config ! Failure) Config ! Failure { return value }
|
||||
Token proc($value Config ! Failure) type { return struct {} }
|
||||
Selected proc(value Config ! Failure) type {
|
||||
config :: value catch |err| Config { child = err.child, count = err.count }
|
||||
return [config.count]config.child
|
||||
}
|
||||
exact proc($value Config ! Failure, input Selected(value)) Selected(value) { return input }
|
||||
Storage proc() type {
|
||||
value Config ! Failure := pass(good())
|
||||
saved :: value
|
||||
copied := pass(value)
|
||||
value = bad()
|
||||
copied = Config { child = u8, count = 3 }
|
||||
if (Token(saved) != Token(good())) return bool
|
||||
if (Token(value) != Token(bad())) return bool
|
||||
if (Token(saved) == Token(value) or Token(saved) == Token(copied)) return bool
|
||||
if (Token(saved) != Token(Config { child = i16, count = 2 })) return bool
|
||||
if (Token(value) == Token(Failure { child = u8, count = 2 })) return bool
|
||||
if (Token(value) == Token(Failure { child = bool, count = 3 })) return bool
|
||||
if (Token(saved) == Token(Failure { child = i16, count = 2 })) return bool
|
||||
return Selected(saved)
|
||||
}
|
||||
main proc() i16 {
|
||||
values Storage() :: exact(good(), [40, 2])
|
||||
flags :: exact(bad(), [true, false])
|
||||
if (!flags[0] or flags[1]) return 0
|
||||
return values[0] + values[1]
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"fallible_types_nested_optional_arrays_preserve_owned_replacements": (
|
||||
"""
|
||||
Config :: struct { child type, count usize }
|
||||
Failure :: struct { child ?type, count usize }
|
||||
Packet :: struct { rows [2]mut (?Config ! Failure) }
|
||||
make proc() Packet {
|
||||
return { rows = [
|
||||
some!(Config { child = i16, count = 2 }),
|
||||
Failure { child = bool, count = 3 },
|
||||
] }
|
||||
}
|
||||
pass proc(value Packet) Packet { return value }
|
||||
Storage proc() type {
|
||||
value := make()
|
||||
saved :: pass(value)
|
||||
copied := saved
|
||||
value.rows[0] = null
|
||||
value.rows[1] = some!(Config { child = u8, count = 4 })
|
||||
copied.rows[0] = Failure { child = null, count = 5 }
|
||||
if ((value.rows[0] catch null) != null) return bool
|
||||
changed :: value.rows[1] catch null
|
||||
if (changed?.child != u8 or changed?.count != 4) return bool
|
||||
copied.rows[0] catch |err| {
|
||||
if (err.child != null or err.count != 5) return bool
|
||||
yield null
|
||||
}
|
||||
config :: (saved.rows[0] catch null)?
|
||||
saved.rows[1] catch |err| {
|
||||
if (err.child? != bool or err.count != 3) return bool
|
||||
return [config.count]config.child
|
||||
}
|
||||
return bool
|
||||
}
|
||||
main proc() Storage() { return [40, 2] }
|
||||
""", b"[40, 2]\n", None,
|
||||
),
|
||||
"fallible_types_return_snapshots_and_cleanup_order": (
|
||||
"""
|
||||
Config :: struct { child type, count usize, trace usize }
|
||||
Failure :: struct { child type, count usize, trace usize }
|
||||
make proc(failed bool) Config ! Failure {
|
||||
trace usize := 0
|
||||
value Config ! Failure := undefined
|
||||
defer {
|
||||
if (trace != (if (failed) 321 else 31)) unreachable
|
||||
value catch |err| {
|
||||
if (err.child != u8 or err.count != 9 or err.trace != 9) unreachable
|
||||
yield Config { child = u8, count = 9, trace = 9 }
|
||||
}
|
||||
}
|
||||
defer value = Failure { child = u8, count = 9, trace = 9 }
|
||||
defer trace = trace * 10 + 1
|
||||
errdefer |err| {
|
||||
if (err.child != bool or err.count != 2 or err.trace != 3) unreachable
|
||||
trace = trace * 10 + 2
|
||||
}
|
||||
{ defer trace = trace * 10 + 3 }
|
||||
value = Config { child = i16, count = 2, trace = trace }
|
||||
if (failed) value = Failure { child = bool, count = 2, trace = trace }
|
||||
return value
|
||||
}
|
||||
Storage proc() type {
|
||||
config :: make(false) catch { return bool }
|
||||
make(true) catch |err| {
|
||||
if (err.child != bool or err.count != 2) return bool
|
||||
if (config.child != i16 or config.count != 2) return bool
|
||||
return struct {
|
||||
values [config.count]config.child
|
||||
success usize = config.trace
|
||||
failure usize = err.trace
|
||||
}
|
||||
}
|
||||
return bool
|
||||
}
|
||||
main proc() [3]i16 {
|
||||
storage Storage() :: { values = [40, 2] }
|
||||
return [storage.values[0] + storage.values[1], i16(storage.success), i16(storage.failure)]
|
||||
}
|
||||
""", b"[42, 3, 3]\n", None,
|
||||
),
|
||||
"fallible_types_reject_incomplete_success_payload": (
|
||||
"""
|
||||
Config :: struct { child type, count usize }
|
||||
Error :: enum { failed }
|
||||
bad proc() Config ! Error {
|
||||
value Config := undefined
|
||||
value.child = i16
|
||||
return value
|
||||
}
|
||||
Selected proc() type { return (bad() catch { return bool }).child }
|
||||
main proc() Selected() { return 42 }
|
||||
""", b"", b"read before initialization",
|
||||
),
|
||||
"fallible_types_reject_incomplete_error_payload": (
|
||||
"""
|
||||
Failure :: struct { child type, count usize }
|
||||
bad proc() type ! Failure {
|
||||
value Failure := undefined
|
||||
value.child = i16
|
||||
return value
|
||||
}
|
||||
Selected proc() type { return bad() catch |err| err.child }
|
||||
main proc() Selected() { return 42 }
|
||||
""", b"", b"read before initialization",
|
||||
),
|
||||
"fallible_types_reject_uninitialized_channel": (
|
||||
"""
|
||||
Error :: enum { failed }
|
||||
Selected proc() type {
|
||||
value type ! Error := undefined
|
||||
return value catch bool
|
||||
}
|
||||
main proc() Selected() { return 42 }
|
||||
""", b"", b"read before initialization",
|
||||
),
|
||||
"fallible_types_reject_non_type_payload": (
|
||||
"""
|
||||
Error :: enum { failed }
|
||||
bad proc() type ! Error { return 42 }
|
||||
Selected proc() type { return bad() catch bool }
|
||||
main proc() Selected() { return 42 }
|
||||
""", b"", b"incompatible value type",
|
||||
),
|
||||
"fallible_types_reject_incompatible_error_propagation": (
|
||||
"""
|
||||
First :: enum { first }
|
||||
Second :: enum { second }
|
||||
leaf proc() type ! First { return .first }
|
||||
bad proc() type ! Second { return try leaf() }
|
||||
Selected proc() type { return bad() catch i16 }
|
||||
main proc() Selected() { return 42 }
|
||||
""", b"", b"propagated errors are not a subset",
|
||||
),
|
||||
"fallible_types_reject_catch_capture_mutation": (
|
||||
"""
|
||||
Failure :: struct { child type }
|
||||
bad proc() type ! Failure { return Failure { child = i16 } }
|
||||
Selected proc() type {
|
||||
return bad() catch |err| {
|
||||
err.child = bool
|
||||
yield err.child
|
||||
}
|
||||
}
|
||||
main proc() Selected() { return 42 }
|
||||
""", b"", b"writable array element",
|
||||
),
|
||||
"fallible_types_reject_errdefer_capture_mutation": (
|
||||
"""
|
||||
Failure :: struct { child type }
|
||||
bad proc() type ! Failure {
|
||||
errdefer |err| { err.child = bool }
|
||||
return Failure { child = i16 }
|
||||
}
|
||||
Selected proc() type { return bad() catch |err| err.child }
|
||||
main proc() Selected() { return 42 }
|
||||
""", b"", b"writable array element",
|
||||
),
|
||||
"fallible_types_reject_runtime_dependency": (
|
||||
"""
|
||||
Error :: enum { failed }
|
||||
choose proc(failed bool) type ! Error {
|
||||
if (failed) return .failed
|
||||
return i16
|
||||
}
|
||||
main proc() i16 {
|
||||
failed := false
|
||||
T :: choose(failed) catch bool
|
||||
value T :: 42
|
||||
return value
|
||||
}
|
||||
""", b"", b"compile-time value",
|
||||
),
|
||||
"fallible_types_direct_catches_select_types_and_runtime_handlers": (
|
||||
"""
|
||||
Error :: enum { failed }
|
||||
Failure :: struct { child type }
|
||||
choose proc(failed bool) type ! Error {
|
||||
if (failed) return .failed
|
||||
return i16
|
||||
}
|
||||
compute proc() i16 ! Failure { return Failure { child = i16 } }
|
||||
recover proc(input i16) i16 {
|
||||
return compute() catch |err| {
|
||||
value err.child :: input
|
||||
yield value + 2
|
||||
}
|
||||
}
|
||||
main proc() [2]i16 {
|
||||
Selected :: choose(false) catch bool
|
||||
Recovered :: choose(true) catch i16
|
||||
first Selected :: 40
|
||||
second Recovered :: 2
|
||||
return [first + second, recover(40)]
|
||||
}
|
||||
""", b"[42, 42]\n", None,
|
||||
),
|
||||
"fallible_types_composed_metadata_errors_preserve_payloads_and_nominals": (
|
||||
"""
|
||||
Child :: distinct i16
|
||||
Failure :: struct { child type, count usize }
|
||||
Other :: enum { missing }
|
||||
leaf proc(failed bool) type ! Failure {
|
||||
if (failed) return Failure { child = Child, count = 2 }
|
||||
return bool
|
||||
}
|
||||
forward proc(failed bool) type ! (Failure | Other) { return try leaf(failed) }
|
||||
Storage proc() type {
|
||||
return forward(true) catch |err| match err {
|
||||
.Failure |info|: [info.count]info.child,
|
||||
.missing: bool,
|
||||
}
|
||||
}
|
||||
main proc() i16 {
|
||||
values Storage() :: [40, 2]
|
||||
return i16(values[0]) + i16(values[1])
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"fallible_types_reject_invalid_direct_catch_result": (
|
||||
"""
|
||||
Error :: enum { failed }
|
||||
choose proc() type ! Error { return .failed }
|
||||
main proc() i16 {
|
||||
Selected :: choose() catch 42
|
||||
return 42
|
||||
}
|
||||
""", b"", b"incompatible value type",
|
||||
),
|
||||
"fallible_types_void_completion_and_success_precedence": (
|
||||
"""
|
||||
Success :: enum { same, good }
|
||||
Failure :: union(enum) { same type, bad type }
|
||||
work proc(mode usize) void ! Failure {
|
||||
errdefer |err| {
|
||||
if (mode != 2) unreachable
|
||||
match err { .same |T|: if (T != i16) unreachable, .bad: unreachable }
|
||||
}
|
||||
if (mode == 0) return
|
||||
if (mode == 2) return Failure { same = i16 }
|
||||
}
|
||||
choose proc() Success ! Failure { return .same }
|
||||
Storage proc() type {
|
||||
work(0) catch { return bool }
|
||||
work(1) catch { return bool }
|
||||
tag :: choose() catch { return bool }
|
||||
if (tag != .same) return bool
|
||||
work(2) catch |err| {
|
||||
return match err { .same |T|: T, .bad: bool }
|
||||
}
|
||||
return bool
|
||||
}
|
||||
main proc() Storage() { return 42 }
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"fallible_types_reject_backed_enum_metadata_composition": (
|
||||
"""
|
||||
Failure :: struct { child type }
|
||||
Other :: enum(i16) { failed = 1 }
|
||||
bad proc() type ! (Failure | Other) { return i16 }
|
||||
Selected proc() type { return bad() catch bool }
|
||||
main proc() Selected() { return 42 }
|
||||
""", b"", b"sum composition requires compatible",
|
||||
),
|
||||
"type_records_undefined_nested_updates_preserve_partial_copies": (
|
||||
"""
|
||||
Config :: struct {
|
||||
|
||||
Reference in New Issue
Block a user