feat: support optional-capture conditional expressions
Reuse canonical matches for optional presence and exact by-value payload captures across checking, lowering and compile-time selection. Snapshot the optional-match flag before selector construction: AST additions can reallocate storage and invalidate the borrowed tree until refresh. Verified 268 bootstrap tests and 908 interpreter/native programs at -O0 and -O2, plus native allocator, LLVM and CLI checks.
This commit is contained in:
@@ -200,7 +200,7 @@ enums use `Kind :: enum { number, empty }`, qualified members such as `Kind.numb
|
|||||||
|
|
||||||
untagged `Bits :: union { integer u64, real f64 }` overlays its members in shared storage. keyed construction selects exactly one member; reading another member reinterprets the same bytes. tagged unions use `union(enum)` or `union(Kind)`; an explicit enum must contain every union variant. construct them with `Choice { number = 42 }`, contextual `.number{42}`, `.point{x = 40, y = 2}` for record payloads, or `.empty` for `void` payloads. field reads/writes validate the active tag. nested aggregates copy by value, including across calls. CLI union results print `{payload}` (or `{}` for a void payload); untagged results print the first member.
|
untagged `Bits :: union { integer u64, real f64 }` overlays its members in shared storage. keyed construction selects exactly one member; reading another member reinterprets the same bytes. tagged unions use `union(enum)` or `union(Kind)`; an explicit enum must contain every union variant. construct them with `Choice { number = 42 }`, contextual `.number{42}`, `.point{x = 40, y = 2}` for record payloads, or `.empty` for `void` payloads. field reads/writes validate the active tag. nested aggregates copy by value, including across calls. CLI union results print `{payload}` (or `{}` for a void payload); untagged results print the first member.
|
||||||
|
|
||||||
`if (guard) then_value else else_value` is a boolean value expression. the guard runs once and only the selected expression executes; `else` is required and binds to the nearest conditional. both runtime branches must fit the result context, including optional/fallible channels and aggregate copies. constant-input procedures, direct `$()` evaluation, generic branch specialization and generated type positions reuse checked match execution. guards require parentheses unless they are calls or explicit `$()` expressions. parenthesize a conditional used as a match-arm value to distinguish it from an `if` statement. optional payload captures and yielding value blocks remain unsupported.
|
`if (guard) then_value else else_value` is a boolean value expression. `if value |payload| then_value else else_value` instead tests optional presence and binds the exact payload as an immutable by-value capture in the then expression; nested optionals preserve present-null payloads. the guard runs once and only the selected expression executes; `else` is required and binds to the nearest conditional. both runtime branches must fit the result context, including optional/fallible channels and aggregate copies. constant-input procedures, direct `$()` evaluation, generic branch specialization, explicit compile-time subject selection and generated type positions reuse checked match execution. boolean guards require parentheses unless they are calls or explicit `$()` expressions; optional captures also permit bare guards. parenthesize a conditional used as a match-arm value to distinguish it from an `if` statement. yielding value blocks remain unsupported.
|
||||||
|
|
||||||
`match value { .number |n|: n + 2, .empty: 0 }` selects one arm after evaluating its subject once. matches support enums, tagged unions, composed error sets, numeric/boolean scalars, comma-separated patterns, numeric ranges, final `else` arms, arm-local payload captures, statement bodies and expression results. checking rejects missing coverage, duplicate/overlapping patterns, unreachable arms and incompatible captures/results. block arms can return or transfer loop control; yielding value blocks and pointer captures belong to later gates.
|
`match value { .number |n|: n + 2, .empty: 0 }` selects one arm after evaluating its subject once. matches support enums, tagged unions, composed error sets, numeric/boolean scalars, comma-separated patterns, numeric ranges, final `else` arms, arm-local payload captures, statement bodies and expression results. checking rejects missing coverage, duplicate/overlapping patterns, unreachable arms and incompatible captures/results. block arms can return or transfer loop control; yielding value blocks and pointer captures belong to later gates.
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ runtime `undefined` locals and compute fully initialized materializable constant
|
|||||||
the existing `ArrayList` uses nested generic inference, file-local helpers and
|
the existing `ArrayList` uses nested generic inference, file-local helpers and
|
||||||
the real imported C allocator through native execution; interpreter checks use
|
the real imported C allocator through native execution; interpreter checks use
|
||||||
non-FFI allocators and cover zero-sized storage, growth, failure and cleanup.
|
non-FFI allocators and cover zero-sized storage, growth, failure and cleanup.
|
||||||
boolean `if ... else` expressions reuse checked matches for runtime/constant values,
|
boolean and optional-capture `if ... else` expressions reuse checked matches for
|
||||||
contextual optional/fallible results and generated types; yielding blocks remain open.
|
runtime/constant values, contextual optional/fallible results and generated types;
|
||||||
|
yielding blocks remain open.
|
||||||
the driver retains frontend storage and source provenance through
|
the driver retains frontend storage and source provenance through
|
||||||
`@source/compilation`, with compilation-wide AST node identities.
|
`@source/compilation`, with compilation-wide AST node identities.
|
||||||
file commands retain single-file behavior; directory commands compile the
|
file commands retain single-file behavior; directory commands compile the
|
||||||
@@ -124,9 +125,10 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [x] type-metadata reflection checkpoint: `typeinfo!(T)` uses the actual `@std/meta` ABI to classify supported types and expose array child/length, immediate distinct backing, record names/tuple/layout and enum/record fields through interpreter/native execution. readonly owned metadata collections retain exact nominal/type leaves, ordinal indices, composed enum identity, checked projections, by-value iteration/copies and compile-time procedure boundaries. ordinary/inline matches select runtime bodies; reflected fields drive `field!`, generics and generated storage/defaults. arity/type/import/ABI failures, match/capture validation, bounds, readonly updates, runtime escape rejection, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and ownership. existing `meta.EnumFieldStruct` and `enummap.EnumMap` generated storage are verified without replacement library code; full `enummap.init/get` is covered below, while general type-valued execution and broader constant materialization remain open.
|
- [x] type-metadata reflection checkpoint: `typeinfo!(T)` uses the actual `@std/meta` ABI to classify supported types and expose array child/length, immediate distinct backing, record names/tuple/layout and enum/record fields through interpreter/native execution. readonly owned metadata collections retain exact nominal/type leaves, ordinal indices, composed enum identity, checked projections, by-value iteration/copies and compile-time procedure boundaries. ordinary/inline matches select runtime bodies; reflected fields drive `field!`, generics and generated storage/defaults. arity/type/import/ABI failures, match/capture validation, bounds, readonly updates, runtime escape rejection, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and ownership. existing `meta.EnumFieldStruct` and `enummap.EnumMap` generated storage are verified without replacement library code; full `enummap.init/get` is covered below, while general type-valued execution and broader constant materialization remain open.
|
||||||
- [x] enum-map acceptance checkpoint: existing `enummap.init/get` run through interpreter/native execution with sparse/aliased/composed enum keys, absent entries and owned aggregate values. contextual `.tag` and aggregate arguments reuse key/value types inferred from rigid generated-map arguments; unresolved generics and rigid conflicts still diagnose. `some!` constructs exact, single-evaluation optional payloads and preserves nested present-null values through compile-time results, cache keys and generated defaults. concrete mutable runtime locals initialize from `undefined`, including nested record/array updates, pointer access and by-value copies; ordinary compile-time procedures materialize fully initialized storage and reject incomplete snapshots. native undefined storage is poison, not zeroed or per-leaf checked. `get` now explicitly returns `null` on lookup fallthrough, preserving the ordinary nonvoid return contract.
|
- [x] enum-map acceptance checkpoint: existing `enummap.init/get` run through interpreter/native execution with sparse/aliased/composed enum keys, absent entries and owned aggregate values. contextual `.tag` and aggregate arguments reuse key/value types inferred from rigid generated-map arguments; unresolved generics and rigid conflicts still diagnose. `some!` constructs exact, single-evaluation optional payloads and preserves nested present-null values through compile-time results, cache keys and generated defaults. concrete mutable runtime locals initialize from `undefined`, including nested record/array updates, pointer access and by-value copies; ordinary compile-time procedures materialize fully initialized storage and reject incomplete snapshots. native undefined storage is poison, not zeroed or per-leaf checked. `get` now explicitly returns `null` on lookup fallthrough, preserving the ordinary nonvoid return contract.
|
||||||
- [x] array-list acceptance checkpoint: existing `std/arraylist` and `std/mem` initialize, reserve/grow, append/pop/clear and release ordinary/aggregate/zero-sized storage without replacement library code. nested generic inference canonicalizes generated types through pointers and derives exact slice `.ptr` types; rigid conflicts remain diagnostic. `minval!`/`maxval!` preserve integer types and checked `divtrunc!` traps on zero and signed overflow through interpreter/native execution. file-local helpers retain real `@hide:file` visibility, newline `else` preserves match default arms, and repeated `_` parameters remain sinks. generated C records, nominal opaque pointers and foreign callbacks load from existing `ffi/c`; unused unsupported signatures neither diagnose nor emit external symbols, while demanded unsupported ABI/types diagnose. interpreter fixed-capacity allocator checks cover failure without losing storage and deferred error cleanup; native `mem.c_allocator` checks cover aggregate growth and over-aligned allocation/reallocation at `-O0`/`-O2`. arbitrary interpreter FFI, variadic/aggregate C calls and broader library acceptance remain open.
|
- [x] array-list acceptance checkpoint: existing `std/arraylist` and `std/mem` initialize, reserve/grow, append/pop/clear and release ordinary/aggregate/zero-sized storage without replacement library code. nested generic inference canonicalizes generated types through pointers and derives exact slice `.ptr` types; rigid conflicts remain diagnostic. `minval!`/`maxval!` preserve integer types and checked `divtrunc!` traps on zero and signed overflow through interpreter/native execution. file-local helpers retain real `@hide:file` visibility, newline `else` preserves match default arms, and repeated `_` parameters remain sinks. generated C records, nominal opaque pointers and foreign callbacks load from existing `ffi/c`; unused unsupported signatures neither diagnose nor emit external symbols, while demanded unsupported ABI/types diagnose. interpreter fixed-capacity allocator checks cover failure without losing storage and deferred error cleanup; native `mem.c_allocator` checks cover aggregate growth and over-aligned allocation/reallocation at `-O0`/`-O2`. arbitrary interpreter FFI, variadic/aggregate C calls and broader library acceptance remain open.
|
||||||
- [x] boolean conditional-expression checkpoint: `if (guard) then_value else else_value` reuses canonical matches through interpreter/native execution. guards evaluate once and only the selected expression executes; nested conditionals bind `else` to the nearest conditional. exact result types, aggregate copy isolation, contextual optional/fallible channels, direct `$()` values, constant-input procedures, generic branch selection and nested generated type positions retain existing checked execution. missing `else`, nonboolean guards and incompatible runtime branch types diagnose. optional captures, yielding value blocks, compound updates and bitwise operators remain open before hash-map acceptance.
|
- [x] boolean conditional-expression checkpoint: `if (guard) then_value else else_value` reuses canonical matches through interpreter/native execution. guards evaluate once and only the selected expression executes; nested conditionals bind `else` to the nearest conditional. exact result types, aggregate copy isolation, contextual optional/fallible channels, direct `$()` values, constant-input procedures, generic branch selection and nested generated type positions retain existing checked execution. missing `else`, nonboolean guards and incompatible runtime branch types diagnose. optional captures are covered below; yielding value blocks, compound updates and bitwise operators remain open before hash-map acceptance.
|
||||||
|
- [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 and broader constant materialization remain open.
|
||||||
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including untagged unions and storage-bearing values), plus full existing-library acceptance for hash maps, broader `meta` operations and `io.print`. required expression-valued control flow, compound updates, formatting parsing and broader imported C-binding support may interleave with later gates.
|
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including untagged unions and storage-bearing values), plus full existing-library acceptance for hash maps, broader `meta` operations and `io.print`. required expression-valued control flow, compound updates, formatting parsing and broader imported C-binding support may interleave with later gates.
|
||||||
- [ ] remaining source constructs: optional-capture conditional expressions, yielding value blocks and `yield`, compound updates, bitwise operators and multiline strings. boolean conditional expressions are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining source constructs: yielding value blocks and `yield`, compound updates, bitwise operators and multiline strings. boolean and optional-capture conditional expressions 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.
|
||||||
- [ ] self-hosting proof: bootstrap-built Honey builds the production compiler (excluding test-only imports); that executable rebuilds the compiler; the rebuilt compiler passes representative interpreter/native examples and CLI/error scenarios without Bro at runtime.
|
- [ ] self-hosting proof: bootstrap-built Honey builds the production compiler (excluding test-only imports); that executable rebuilds the compiler; the rebuilt compiler passes representative interpreter/native examples and CLI/error scenarios without Bro at runtime.
|
||||||
|
|||||||
+24
-4
@@ -7,7 +7,7 @@
|
|||||||
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
|
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
|
||||||
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
|
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
|
||||||
#! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence.
|
#! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence.
|
||||||
#! boolean value expressions also accept 'if' guard expr 'else' expr, canonicalized to matches; else is required.
|
#! value expressions also accept 'if' guard ['|' identifier '|'] expr 'else' expr, canonicalized to matches; else is required.
|
||||||
#! * block .... -> '{' {stmt} '}'
|
#! * block .... -> '{' {stmt} '}'
|
||||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||||
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
||||||
@@ -207,6 +207,7 @@ NodeData :: union {
|
|||||||
@hide:file foreign_type_flag u8 :: 0b0010
|
@hide:file foreign_type_flag u8 :: 0b0010
|
||||||
@hide:file variadic_flag u8 :: 0b0100
|
@hide:file variadic_flag u8 :: 0b0100
|
||||||
@hide:file opaque_flag u8 :: 0b1000
|
@hide:file opaque_flag u8 :: 0b1000
|
||||||
|
@hide:file optional_match_flag u8 :: 0b0001_0000
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
Node :: struct {
|
Node :: struct {
|
||||||
@@ -348,8 +349,17 @@ FieldView :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EnumTypeView :: struct { backing ?NodeId }
|
EnumTypeView :: struct { backing ?NodeId }
|
||||||
UnionTypeView :: struct { tag ?NodeId, auto_tag bool }
|
|
||||||
MatchView :: struct { subject NodeId }
|
UnionTypeView :: struct {
|
||||||
|
tag ?NodeId,
|
||||||
|
auto_tag bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchView :: struct {
|
||||||
|
subject NodeId,
|
||||||
|
optional_capture bool = false,
|
||||||
|
}
|
||||||
|
|
||||||
MatchArmView :: struct {
|
MatchArmView :: struct {
|
||||||
capture ?NodeId
|
capture ?NodeId
|
||||||
body NodeId
|
body NodeId
|
||||||
@@ -833,6 +843,13 @@ add_match proc(builder @mut Builder, span Span, subject NodeId, arms []NodeId) N
|
|||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! marks the canonical presence match emitted for an optional-capture conditional.
|
||||||
|
set_match_optional proc(builder @mut Builder, id NodeId) void {
|
||||||
|
node :: &builder.storage.nodes.items[usize(id)]
|
||||||
|
debug.assert(node.kind == .expr_match)
|
||||||
|
node.flags = node.flags | optional_match_flag
|
||||||
|
}
|
||||||
|
|
||||||
add_match_arm proc(
|
add_match_arm proc(
|
||||||
builder @mut Builder, span Span, patterns []NodeId, capture ?NodeId,
|
builder @mut Builder, span Span, patterns []NodeId, capture ?NodeId,
|
||||||
body NodeId, expression, catch_all bool,
|
body NodeId, expression, catch_all bool,
|
||||||
@@ -1747,7 +1764,10 @@ enum_literal proc(tree @Tree, id NodeId) NodeId {
|
|||||||
match_expr proc(tree @Tree, id NodeId) MatchView {
|
match_expr proc(tree @Tree, id NodeId) MatchView {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .expr_match)
|
debug.assert(node.kind == .expr_match)
|
||||||
return MatchView{ subject = node.main.node_id }
|
return MatchView{
|
||||||
|
subject = node.main.node_id,
|
||||||
|
optional_capture = (node.flags & optional_match_flag) != 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match_arm_count proc(tree @Tree, id NodeId) usize {
|
match_arm_count proc(tree @Tree, id NodeId) usize {
|
||||||
|
|||||||
@@ -528,8 +528,17 @@ recompiles_generated_records_without_borrowing_previous_ir test {
|
|||||||
` [child, 2, "owned"],
|
` [child, 2, "owned"],
|
||||||
` )
|
` )
|
||||||
`}
|
`}
|
||||||
|
`Captured proc(value ?i16) type {
|
||||||
|
` return if value |payload| struct { value i16 = payload } else bool
|
||||||
|
`}
|
||||||
|
`selected proc($value ?i16) i16 {
|
||||||
|
` return if $(value) |payload| payload else 7
|
||||||
|
`}
|
||||||
`main proc() i16 {
|
`main proc() i16 {
|
||||||
` parent Parent(40) :: {}
|
` parent Parent(40) :: {}
|
||||||
|
` captured Captured(40) :: {}
|
||||||
|
` absent Captured(null) :: false
|
||||||
|
` if (absent or captured.value != selected(40) or selected(null) != 7) return 0
|
||||||
` same Parent(40) :: parent
|
` same Parent(40) :: parent
|
||||||
` child Child(40) :: same.child
|
` child Child(40) :: same.child
|
||||||
` if (Child(40) == Child(41) or Parent(40) == Parent(41)) return 0
|
` if (Child(40) == Child(41) or Parent(40) == Parent(41)) return 0
|
||||||
|
|||||||
@@ -1043,6 +1043,7 @@ lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
|||||||
subject_type :: typecheck.type_of(state.checked, view.subject)
|
subject_type :: typecheck.type_of(state.checked, view.subject)
|
||||||
tagged :: subject_type.kind == .record
|
tagged :: subject_type.kind == .record
|
||||||
discriminant := subject
|
discriminant := subject
|
||||||
|
if (view.optional_capture) discriminant = try optional_present(state, subject, subject_type, span)
|
||||||
if (tagged) {
|
if (tagged) {
|
||||||
tag_type :: typecheck.record_type(state.checked, subject_type.record_type).tag_type
|
tag_type :: typecheck.record_type(state.checked, subject_type.record_type).tag_type
|
||||||
discriminant = try emit(state, ir.Instruction{
|
discriminant = try emit(state, ir.Instruction{
|
||||||
@@ -1095,6 +1096,7 @@ lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
|||||||
if (arm_view.capture) |capture| {
|
if (arm_view.capture) |capture| {
|
||||||
capture_type :: typecheck.type_of(state.checked, capture)
|
capture_type :: typecheck.type_of(state.checked, capture)
|
||||||
captured := subject
|
captured := subject
|
||||||
|
if (view.optional_capture) captured = try optional_payload(state, subject, subject_type, span)
|
||||||
if (tagged) captured = try emit(state, ir.Instruction{
|
if (tagged) captured = try emit(state, ir.Instruction{
|
||||||
op = .field, type = lower_type(state, capture_type), array_type = try array_shape(state, capture_type),
|
op = .field, type = lower_type(state, capture_type), array_type = try array_shape(state, capture_type),
|
||||||
left = subject, slot = u32(typecheck.union_variant_index(state.checked, pattern)), span = ast.node_span(tree, pattern),
|
left = subject, slot = u32(typecheck.union_variant_index(state.checked, pattern)), span = ast.node_span(tree, pattern),
|
||||||
|
|||||||
@@ -465,6 +465,7 @@ parse_if proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
parse_if_expr proc(state @mut State) ast.NodeId ! ParseError {
|
parse_if_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
keyword :: try consume(state, .if)
|
keyword :: try consume(state, .if)
|
||||||
condition :: try parse_guard(state)
|
condition :: try parse_guard(state)
|
||||||
|
capture :: try parse_optional_capture(state)
|
||||||
then_value :: try parse_expr(state)
|
then_value :: try parse_expr(state)
|
||||||
skip_newlines(state)
|
skip_newlines(state)
|
||||||
if (!next_is(state, .else) or next_n_is(state, 1, .colon)) return .unexpected_token
|
if (!next_is(state, .else) or next_n_is(state, 1, .colon)) return .unexpected_token
|
||||||
@@ -476,18 +477,20 @@ parse_if_expr proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
end = ast.builder_span(state.builder, else_value).end,
|
end = ast.builder_span(state.builder, else_value).end,
|
||||||
source = keyword.span.source,
|
source = keyword.span.source,
|
||||||
}
|
}
|
||||||
# canonical boolean matches share inference, specialization and branch execution.
|
# canonical matches share inference, specialization and branch execution.
|
||||||
patterns [1]ast.NodeId :: [try ast.add_boolean(state.builder, keyword.span, true)]
|
patterns [1]ast.NodeId :: [try ast.add_boolean(state.builder, keyword.span, true)]
|
||||||
then_arm :: try ast.add_match_arm(
|
then_arm :: try ast.add_match_arm(
|
||||||
state.builder, ast.builder_span(state.builder, then_value),
|
state.builder, ast.builder_span(state.builder, then_value),
|
||||||
patterns[..], null, then_value, true, false,
|
patterns[..], capture, then_value, true, false,
|
||||||
)
|
)
|
||||||
else_arm :: try ast.add_match_arm(
|
else_arm :: try ast.add_match_arm(
|
||||||
state.builder, ast.builder_span(state.builder, else_value),
|
state.builder, ast.builder_span(state.builder, else_value),
|
||||||
&[], null, else_value, true, true,
|
&[], null, else_value, true, true,
|
||||||
)
|
)
|
||||||
arms [2]ast.NodeId :: [then_arm, else_arm]
|
arms [2]ast.NodeId :: [then_arm, else_arm]
|
||||||
return try ast.add_match(state.builder, span, condition, arms[..])
|
result :: try ast.add_match(state.builder, span, condition, arms[..])
|
||||||
|
if (capture != null) ast.set_match_optional(state.builder, result)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
|
|||||||
@@ -4118,6 +4118,9 @@ collect_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
|
|||||||
state.cells[usize(capture)].local = true
|
state.cells[usize(capture)].local = true
|
||||||
binding :: sema.binding(state.resolution, capture)
|
binding :: sema.binding(state.resolution, capture)
|
||||||
if (binding.kind == .invalid) state.cells[usize(capture)].type = invalid_type(binding.data.diagnostic)
|
if (binding.kind == .invalid) state.cells[usize(capture)].type = invalid_type(binding.data.diagnostic)
|
||||||
|
if (ast.match_expr(state.tree, id).optional_capture and i == 0) {
|
||||||
|
state.cells[usize(subject)].capture = capture
|
||||||
|
}
|
||||||
try depend(state, capture, arm.body)
|
try depend(state, capture, arm.body)
|
||||||
}
|
}
|
||||||
saved_arm :: state.narrowing_arm
|
saved_arm :: state.narrowing_arm
|
||||||
@@ -4135,12 +4138,21 @@ collect_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
subject :: try infer_shape(state, ast.match_expr(state.tree, id).subject, defaults)
|
view :: ast.match_expr(state.tree, id)
|
||||||
|
subject :: try infer_shape(state, view.subject, defaults)
|
||||||
if (subject.kind == .invalid) {
|
if (subject.kind == .invalid) {
|
||||||
seed(state, id, subject)
|
seed(state, id, subject)
|
||||||
return subject
|
return subject
|
||||||
}
|
}
|
||||||
key := subject
|
key := subject
|
||||||
|
if (view.optional_capture) {
|
||||||
|
if (subject.kind != .none and subject.kind != .optional) {
|
||||||
|
try fail(state, id, .condition_not_bool, ast.node_span(state.tree, view.subject))
|
||||||
|
seed(state, id, state.types[usize(id)])
|
||||||
|
return state.types[usize(id)]
|
||||||
|
}
|
||||||
|
if (subject.kind == .optional) key = Type{ kind = .bool }
|
||||||
|
}
|
||||||
tagged := false
|
tagged := false
|
||||||
if (subject.kind == .record) {
|
if (subject.kind == .record) {
|
||||||
descriptor :: state.records.items[usize(subject.record_type)]
|
descriptor :: state.records.items[usize(subject.record_type)]
|
||||||
@@ -4151,7 +4163,7 @@ infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
|||||||
if (tagged) key = descriptor.tag_type
|
if (tagged) key = descriptor.tag_type
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
subject.kind != .none and !numeric_kind(subject.kind) and subject.kind != .bool
|
!view.optional_capture and subject.kind != .none and !numeric_kind(subject.kind) and subject.kind != .bool
|
||||||
and !(subject.kind == .enumeration and subject.nominal == ast.NodeId(maxval!(u32))) and !tagged
|
and !(subject.kind == .enumeration and subject.nominal == ast.NodeId(maxval!(u32))) and !tagged
|
||||||
) {
|
) {
|
||||||
try fail(state, id, .invalid_match_subject, ast.node_span(state.tree, id))
|
try fail(state, id, .invalid_match_subject, ast.node_span(state.tree, id))
|
||||||
@@ -4167,6 +4179,10 @@ infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
|||||||
arm :: ast.match_arm_view(state.tree, arm_id)
|
arm :: ast.match_arm_view(state.tree, arm_id)
|
||||||
payload := Type{}
|
payload := Type{}
|
||||||
capture_valid := tagged and !arm.catch_all
|
capture_valid := tagged and !arm.catch_all
|
||||||
|
if (view.optional_capture and subject.kind == .optional and i == 0) {
|
||||||
|
payload = state.arrays.items[usize(subject.array_type)].element
|
||||||
|
capture_valid = true
|
||||||
|
}
|
||||||
for 0..ast.match_pattern_count(state.tree, arm_id) |j| {
|
for 0..ast.match_pattern_count(state.tree, arm_id) |j| {
|
||||||
pattern :: ast.match_pattern(state.tree, arm_id, j)
|
pattern :: ast.match_pattern(state.tree, arm_id, j)
|
||||||
kind :: ast.expr_kind(state.tree, pattern)
|
kind :: ast.expr_kind(state.tree, pattern)
|
||||||
@@ -4191,7 +4207,7 @@ infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (arm.capture) |capture| {
|
if (arm.capture) |capture| {
|
||||||
if (key.kind != .none and (!capture_valid or payload.kind == .void or payload.kind == .none)) {
|
if (key.kind != .none and (!capture_valid or (!view.optional_capture and payload.kind == .void) or payload.kind == .none)) {
|
||||||
try fail(state, id, .invalid_match_capture, ast.node_span(state.tree, capture))
|
try fail(state, id, .invalid_match_capture, ast.node_span(state.tree, capture))
|
||||||
} else if (payload.kind != .none and state.cells[usize(capture)].type.kind != .invalid) {
|
} else if (payload.kind != .none and state.cells[usize(capture)].type.kind != .invalid) {
|
||||||
seed(state, capture, payload)
|
seed(state, capture, payload)
|
||||||
@@ -4317,9 +4333,17 @@ interval_overlap proc(left, right PatternInterval) bool {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
validate_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
validate_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
subject :: state.types[usize(ast.match_expr(state.tree, id).subject)]
|
view :: ast.match_expr(state.tree, id)
|
||||||
|
subject :: state.types[usize(view.subject)]
|
||||||
if (subject.kind == .invalid) return
|
if (subject.kind == .invalid) return
|
||||||
key := subject
|
key := subject
|
||||||
|
if (view.optional_capture) {
|
||||||
|
if (subject.kind != .optional) {
|
||||||
|
try fail(state, id, .condition_not_bool, ast.node_span(state.tree, view.subject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key = Type{ kind = .bool }
|
||||||
|
}
|
||||||
if (subject.kind == .record) key = state.records.items[usize(subject.record_type)].tag_type
|
if (subject.kind == .record) key = state.records.items[usize(subject.record_type)].tag_type
|
||||||
if (key.kind != .enumeration and !numeric_kind(key.kind) and key.kind != .bool) {
|
if (key.kind != .enumeration and !numeric_kind(key.kind) and key.kind != .bool) {
|
||||||
try fail(state, id, .invalid_match_subject, ast.node_span(state.tree, id))
|
try fail(state, id, .invalid_match_subject, ast.node_span(state.tree, id))
|
||||||
@@ -4724,9 +4748,11 @@ matching_narrowing_arm proc(state @State, id ast.NodeId) ast.NodeId {
|
|||||||
arm := state.cells[usize(id)].narrowing_arm
|
arm := state.cells[usize(id)].narrowing_arm
|
||||||
while (arm != ast.NodeId(maxval!(u32))) {
|
while (arm != ast.NodeId(maxval!(u32))) {
|
||||||
owner :: state.cells[usize(arm)].match_owner
|
owner :: state.cells[usize(arm)].match_owner
|
||||||
subject :: ast.match_expr(state.tree, owner).subject
|
view :: ast.match_expr(state.tree, owner)
|
||||||
original :: sema.binding(state.resolution, subject)
|
if (!view.optional_capture) {
|
||||||
|
original :: sema.binding(state.resolution, view.subject)
|
||||||
if (original.kind == .declaration and original.data.declaration == binding.data.declaration) return arm
|
if (original.kind == .declaration and original.data.declaration == binding.data.declaration) return arm
|
||||||
|
}
|
||||||
arm = state.cells[usize(arm)].enclosing_arm
|
arm = state.cells[usize(arm)].enclosing_arm
|
||||||
}
|
}
|
||||||
return ast.NodeId(maxval!(u32))
|
return ast.NodeId(maxval!(u32))
|
||||||
|
|||||||
@@ -1335,14 +1335,22 @@ specialize_match proc(state @mut State, id, statement ast.NodeId) bool ! Error {
|
|||||||
annotation = try expression_type(state, subject, 0)
|
annotation = try expression_type(state, subject, 0)
|
||||||
if (annotation != no_node) annotation = try inline_match_shape(state, annotation)
|
if (annotation != no_node) annotation = try inline_match_shape(state, annotation)
|
||||||
}
|
}
|
||||||
if (annotation == no_node or ast.expr_kind(&state.tree, annotation) != .union_type) break
|
if (annotation == no_node) break
|
||||||
|
payload := no_node
|
||||||
|
if (ast.match_expr(&state.tree, id).optional_capture) {
|
||||||
|
if (ast.expr_kind(&state.tree, annotation) != .optional_type) break
|
||||||
|
payload = ast.type_operand(&state.tree, annotation)
|
||||||
|
} else {
|
||||||
|
if (ast.expr_kind(&state.tree, annotation) != .union_type) break
|
||||||
pattern :: ast.match_pattern(&state.tree, arm_id, 0)
|
pattern :: ast.match_pattern(&state.tree, arm_id, 0)
|
||||||
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) continue
|
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) continue
|
||||||
slot :: type_record_slot(state, annotation, ast.enum_literal(&state.tree, pattern))
|
slot :: type_record_slot(state, annotation, ast.enum_literal(&state.tree, pattern))
|
||||||
if (slot == no_index) continue
|
if (slot == no_index) continue
|
||||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot))
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot))
|
||||||
if (field.type) |type| {
|
if (field.type) |type| payload = type
|
||||||
hashmap.put(&state.capture_types, arm.capture?, type) catch |err| {
|
}
|
||||||
|
if (payload != no_node) {
|
||||||
|
hashmap.put(&state.capture_types, arm.capture?, payload) catch |err| {
|
||||||
match err {
|
match err {
|
||||||
.key_exists: unreachable
|
.key_exists: unreachable
|
||||||
else: return err
|
else: return err
|
||||||
@@ -2508,6 +2516,7 @@ expand_inline_for proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||||
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 no_index
|
if (ast.expr_kind(&state.tree, id) != .match_expr) return no_index
|
||||||
|
optional_capture :: ast.match_expr(&state.tree, id).optional_capture
|
||||||
if (!try expand_type_matches(state, ast.match_expr(&state.tree, id).subject)) return no_index
|
if (!try expand_type_matches(state, ast.match_expr(&state.tree, id).subject)) return no_index
|
||||||
try expand(state, ast.match_expr(&state.tree, id).subject)
|
try expand(state, ast.match_expr(&state.tree, id).subject)
|
||||||
arms := arraylist.init(ast.NodeId, state.allocator)
|
arms := arraylist.init(ast.NodeId, state.allocator)
|
||||||
@@ -2581,6 +2590,8 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
|||||||
}
|
}
|
||||||
selector :: try ast.add_match(state.builder, ast.node_span(&state.tree, id),
|
selector :: try ast.add_match(state.builder, ast.node_span(&state.tree, id),
|
||||||
dispatch, arms.items)
|
dispatch, arms.items)
|
||||||
|
if (optional_capture)
|
||||||
|
ast.set_match_optional(state.builder, selector)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
# selector arms contain only indices. do not specialize this synthetic match
|
# selector arms contain only indices. do not specialize this synthetic match
|
||||||
# again while its checked evaluation expands the wrapper.
|
# again while its checked evaluation expands the wrapper.
|
||||||
@@ -2595,6 +2606,13 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
|||||||
@hide:file
|
@hide:file
|
||||||
type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
|
type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
|
||||||
subject :: ast.match_expr(&state.tree, id).subject
|
subject :: ast.match_expr(&state.tree, id).subject
|
||||||
|
if (ast.match_expr(&state.tree, id).optional_capture) {
|
||||||
|
unwrapped :: try ast.add_unary(state.builder,
|
||||||
|
ast.node_span(&state.tree, subject), .unwrap, subject)
|
||||||
|
try refresh(state)
|
||||||
|
child :: try expression_type(state, unwrapped, 0)
|
||||||
|
return try type_execution_value(state, unwrapped, child)
|
||||||
|
}
|
||||||
annotation :: try expression_type(state, subject, 0)
|
annotation :: try expression_type(state, subject, 0)
|
||||||
value :: if (type_record_storage(state, annotation, 0))
|
value :: if (type_record_storage(state, annotation, 0))
|
||||||
try evaluate_expected(state, subject, annotation) else try evaluate_ir(state, subject, annotation)
|
try evaluate_expected(state, subject, annotation) else try evaluate_ir(state, subject, annotation)
|
||||||
|
|||||||
+186
@@ -110,6 +110,192 @@ CASES = {
|
|||||||
"main proc() i16 { return if (1) 42 else 0 }",
|
"main proc() i16 { return if (1) 42 else 0 }",
|
||||||
b"", b"incompatible value type",
|
b"", b"incompatible value type",
|
||||||
),
|
),
|
||||||
|
"optional_conditional_expressions_evaluate_once_and_keep_branches_lazy": (
|
||||||
|
"""
|
||||||
|
next proc(calls @mut i16, value ?i16) ?i16 {
|
||||||
|
calls^ = calls^ + 1
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
main proc() [3]i16 {
|
||||||
|
calls i16 := 0
|
||||||
|
first i16 :: if next(&calls, 42) |payload| payload else divtrunc!(1, 0)
|
||||||
|
second i16 :: if next(&calls, null) |payload| divtrunc!(payload, 0) else 7
|
||||||
|
return [first, second, calls]
|
||||||
|
}
|
||||||
|
""", b"[42, 7, 2]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_keep_nested_presence_and_else_binding": (
|
||||||
|
"""
|
||||||
|
identity proc($T type, value T) T { return value }
|
||||||
|
choose proc(value ??i16) i16 {
|
||||||
|
return if value |inner| if identity(inner) |payload| payload else 7 else 3
|
||||||
|
}
|
||||||
|
main proc() [3]i16 {
|
||||||
|
return [choose(some!(null)), choose(null), choose(some!(some!(42)))]
|
||||||
|
}
|
||||||
|
""", b"[7, 3, 42]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_snapshot_aggregate_payloads": (
|
||||||
|
"""
|
||||||
|
Item :: struct { values [1]mut i16 }
|
||||||
|
replace proc(value @mut ?Item) i16 {
|
||||||
|
value^ = Item { values = [0] }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
main proc() [2]i16 {
|
||||||
|
original ?Item := Item { values = [42] }
|
||||||
|
saved i16 :: if original |payload| replace(&original) + payload.values[0] else -1
|
||||||
|
return [saved, (original?).values[0]]
|
||||||
|
}
|
||||||
|
""", b"[42, 0]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_preserve_narrow_payload_types": (
|
||||||
|
"""
|
||||||
|
identity proc($T type, value T) T { return value }
|
||||||
|
exact proc(value i8) i16 { return i16(value) }
|
||||||
|
choose proc(value ?i8) i16 { return if value |payload| exact(identity(payload)) else 7 }
|
||||||
|
main proc() [2]i16 { return [choose(42), choose(null)] }
|
||||||
|
""", b"[42, 7]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_capture_pointer_callback_slice_and_zero_size": (
|
||||||
|
"""
|
||||||
|
increment proc(value i16) i16 { return value + 2 }
|
||||||
|
read_pointer proc(value ?@i16) i16 { return if value |pointer| pointer^ else 7 }
|
||||||
|
invoke proc(value ?@proc(value i16) i16) i16 {
|
||||||
|
return if value |callback| callback(40) else 8
|
||||||
|
}
|
||||||
|
sum proc(value ?[]i16) i16 {
|
||||||
|
return if value |items| items[0] + items[1] else 9
|
||||||
|
}
|
||||||
|
empty proc(value ?[0]i16) i16 { return if value |items| i16(items.len) else 10 }
|
||||||
|
main proc() [8]i16 {
|
||||||
|
value i16 :: 42
|
||||||
|
items [2]i16 :: [19, 23]
|
||||||
|
return [read_pointer(&value), read_pointer(null), invoke(increment), invoke(null),
|
||||||
|
sum(items[..]), sum(null), empty([]), empty(null)]
|
||||||
|
}
|
||||||
|
""", b"[42, 7, 42, 8, 42, 9, 0, 10]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_preserve_optional_and_fallible_context": (
|
||||||
|
"""
|
||||||
|
Error :: enum { failed }
|
||||||
|
optional proc(value ?i16) ?i16 { return if value |payload| payload + 2 else null }
|
||||||
|
fallible proc(value ?i16) i16 ! Error {
|
||||||
|
return if value |payload| payload + 2 else .failed
|
||||||
|
}
|
||||||
|
main proc() [3]i16 {
|
||||||
|
if (optional(null) != null) return [0, 0, 0]
|
||||||
|
return [optional(40)?, fallible(40) catch 0, fallible(null) catch 7]
|
||||||
|
}
|
||||||
|
""", b"[42, 42, 7]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_materialize_compile_time_scalars": (
|
||||||
|
"""
|
||||||
|
choose proc(value ?i8) i16 { return if value |payload| i16(payload) + 2 else 7 }
|
||||||
|
nested proc(value ??i16) i16 {
|
||||||
|
return if value |inner| if inner |payload| payload else 11 else 13
|
||||||
|
}
|
||||||
|
main proc() [4]i16 {
|
||||||
|
return [$(choose(40)), $(choose(null)), $(nested(some!(null))), $(nested(null))]
|
||||||
|
}
|
||||||
|
""", b"[42, 7, 11, 13]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_generate_types_and_captured_defaults": (
|
||||||
|
"""
|
||||||
|
Choose proc(value ?usize) type { return if value |_| i16 else i8 }
|
||||||
|
Box proc(count ?usize) type {
|
||||||
|
return if count |length| struct { values [length]i16, bias i16 = i16(length) }
|
||||||
|
else struct { value i16 = 7 }
|
||||||
|
}
|
||||||
|
main proc() [3]i16 {
|
||||||
|
first Box(2) :: { values = [19, 21] }
|
||||||
|
same Box(usize(2)) :: first
|
||||||
|
absent Box(null) :: {}
|
||||||
|
narrow Choose(null) :: 5
|
||||||
|
wide Choose(2) :: 40
|
||||||
|
return [same.values[0] + same.values[1] + same.bias, absent.value,
|
||||||
|
wide + i16(narrow)]
|
||||||
|
}
|
||||||
|
""", b"[42, 7, 45]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_own_compile_time_aggregate_captures": (
|
||||||
|
"""
|
||||||
|
Config :: struct { name string, counts [2]usize }
|
||||||
|
Storage proc(config ?Config) type {
|
||||||
|
return if config |payload| struct {
|
||||||
|
items [payload.counts[0]]i16
|
||||||
|
name string = payload.name
|
||||||
|
} else struct { value i16 = 7 }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
value Storage(Config { name = "abc", counts = [2, 1] }) :: { items = [19, 20] }
|
||||||
|
return value.items[0] + value.items[1] + i16(value.name.len)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_specialize_selected_capture_only": (
|
||||||
|
"""
|
||||||
|
present proc($value ?i8) i16 {
|
||||||
|
return if $(value) |payload| i16(payload) + 2 else compile_error!("absent")
|
||||||
|
}
|
||||||
|
absent proc($value ?i8) i16 {
|
||||||
|
return if $(value) |_| compile_error!("present") else 7
|
||||||
|
}
|
||||||
|
select proc($value ?i8) i16 {
|
||||||
|
return if $(value) |payload| i16(payload) + 2 else 7
|
||||||
|
}
|
||||||
|
main proc() [6]i16 {
|
||||||
|
return [present(40), absent(null), select(40), select(null), select(1), select(40)]
|
||||||
|
}
|
||||||
|
""", b"[42, 7, 42, 7, 3, 42]\n", None,
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_keep_compile_time_narrow_overflow": (
|
||||||
|
"""
|
||||||
|
narrow proc(value ?i8) i8 { return if value |payload| payload + 1 else 0 }
|
||||||
|
main proc() i8 { return $(narrow(127)) }
|
||||||
|
""", b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_reject_runtime_explicit_comptime_subject": (
|
||||||
|
"""
|
||||||
|
choose proc(value ?i16) i16 { return if $(value) |payload| payload else 0 }
|
||||||
|
main proc() i16 { return choose(42) }
|
||||||
|
""", b"", b"generic argument must be a compile-time value",
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_reject_nonoptional_subject": (
|
||||||
|
"main proc() i16 { return if true |payload| 42 else 0 }",
|
||||||
|
b"", b"condition",
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_capture_does_not_reach_else": (
|
||||||
|
"main proc() i16 { value ?i16 :: null\nreturn if value |payload| 42 else payload }",
|
||||||
|
b"", b"unknown name",
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_capture_does_not_escape": (
|
||||||
|
"""
|
||||||
|
main proc() i16 {
|
||||||
|
value ?i16 :: 42
|
||||||
|
saved i16 :: if value |payload| payload else 0
|
||||||
|
return saved + payload
|
||||||
|
}
|
||||||
|
""", b"", b"unknown name",
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_reject_capture_shadowing": (
|
||||||
|
"main proc() i16 { value ?i16 :: 42\nreturn if value |value| value else 0 }",
|
||||||
|
b"", b"duplicate name or forbidden shadowing",
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_check_unselected_result_types": (
|
||||||
|
"""
|
||||||
|
choose proc(value ?i16) i16 { return if value |payload| payload else u8(7) }
|
||||||
|
main proc() i16 { return choose(42) }
|
||||||
|
""", b"42\n", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_require_else": (
|
||||||
|
"main proc() i16 { value ?i16 :: 42\nreturn if value |payload| payload }",
|
||||||
|
b"", b"unexpected token",
|
||||||
|
),
|
||||||
|
"optional_conditional_expressions_reject_pointer_capture_syntax": (
|
||||||
|
"main proc() i16 { value ?i16 :: 42\nreturn if value |@payload| payload^ else 0 }",
|
||||||
|
b"", b"unexpected token",
|
||||||
|
),
|
||||||
"integer_divtrunc_preserves_sign_and_single_evaluation": (
|
"integer_divtrunc_preserves_sign_and_single_evaluation": (
|
||||||
"""
|
"""
|
||||||
next proc(calls @mut i16) i16 { calls^ = calls^ + 1
|
next proc(calls @mut i16) i16 { calls^ = calls^ + 1
|
||||||
|
|||||||
Reference in New Issue
Block a user