feat: support boolean conditional expressions
This commit is contained in:
@@ -200,6 +200,8 @@ 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.
|
||||||
|
|
||||||
`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.
|
||||||
|
|
||||||
a final `inline |tag|:` arm on an enum, or `inline |payload[, tag]|:` on a tagged union, expands into one ordinary arm for each variant not covered earlier. dispatch remains runtime and evaluates the subject once. enum captures and optional union tags are immutable compile-time enum values; payloads are immutable by-value captures with each variant's exact type, including a `void` value for empty variants. either capture may be discarded with `_`. static tag matches/conditions select variant-specific operations before checking, including generic calls, nested inline arms and type-producing procedure bodies. ordinary payloads remain runtime values, and return/break/continue/deferred cleanup retain ordinary match semantics. redundant/nonfinal inline arms, invalid captures and scalar/untagged subjects are diagnosed. expansion shares the 4096-step type-control bound.
|
a final `inline |tag|:` arm on an enum, or `inline |payload[, tag]|:` on a tagged union, expands into one ordinary arm for each variant not covered earlier. dispatch remains runtime and evaluates the subject once. enum captures and optional union tags are immutable compile-time enum values; payloads are immutable by-value captures with each variant's exact type, including a `void` value for empty variants. either capture may be discarded with `_`. static tag matches/conditions select variant-specific operations before checking, including generic calls, nested inline arms and type-producing procedure bodies. ordinary payloads remain runtime values, and return/break/continue/deferred cleanup retain ordinary match semantics. redundant/nonfinal inline arms, invalid captures and scalar/untagged subjects are diagnosed. expansion shares the 4096-step type-control bound.
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ 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,
|
||||||
|
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
|
||||||
@@ -122,8 +124,9 @@ 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.
|
||||||
- [ ] 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: expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] 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`.
|
||||||
- [ ] 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.
|
||||||
|
|||||||
+2
-1
@@ -7,12 +7,13 @@
|
|||||||
#! * 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.
|
||||||
#! * 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.
|
||||||
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
|
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
|
||||||
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
#! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||||
#! braceless guards/iterables require parentheses unless they are calls or optional captures; else binds to the nearest if.
|
#! braceless guards/iterables require parentheses unless they are calls, explicit comptime expressions or optional captures; else binds to the nearest if.
|
||||||
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -/!/&/try higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges/catch. type composition | binds tighter than fallible !; pointer/optional/array prefixes bind to type atoms. compound range endpoints require their own parentheses.
|
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -/!/&/try higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges/catch. type composition | binds tighter than fallible !; pointer/optional/array prefixes bind to type atoms. compound range endpoints require their own parentheses.
|
||||||
#! binary operators associate left; unary operators associate right.
|
#! binary operators associate left; unary operators associate right.
|
||||||
|
|
||||||
|
|||||||
@@ -461,6 +461,35 @@ parse_if proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_if_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
keyword :: try consume(state, .if)
|
||||||
|
condition :: try parse_guard(state)
|
||||||
|
then_value :: try parse_expr(state)
|
||||||
|
skip_newlines(state)
|
||||||
|
if (!next_is(state, .else) or next_n_is(state, 1, .colon)) return .unexpected_token
|
||||||
|
state.next_token += 1
|
||||||
|
skip_newlines(state)
|
||||||
|
else_value :: try parse_expr(state)
|
||||||
|
span :: ast.Span{
|
||||||
|
start = keyword.span.start,
|
||||||
|
end = ast.builder_span(state.builder, else_value).end,
|
||||||
|
source = keyword.span.source,
|
||||||
|
}
|
||||||
|
# canonical boolean matches share inference, specialization and branch execution.
|
||||||
|
patterns [1]ast.NodeId :: [try ast.add_boolean(state.builder, keyword.span, true)]
|
||||||
|
then_arm :: try ast.add_match_arm(
|
||||||
|
state.builder, ast.builder_span(state.builder, then_value),
|
||||||
|
patterns[..], null, then_value, true, false,
|
||||||
|
)
|
||||||
|
else_arm :: try ast.add_match_arm(
|
||||||
|
state.builder, ast.builder_span(state.builder, else_value),
|
||||||
|
&[], null, else_value, true, true,
|
||||||
|
)
|
||||||
|
arms [2]ast.NodeId :: [then_arm, else_arm]
|
||||||
|
return try ast.add_match(state.builder, span, condition, arms[..])
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_while proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
parse_while proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||||
keyword :: try consume(state, .while)
|
keyword :: try consume(state, .while)
|
||||||
@@ -640,6 +669,7 @@ parse_guard proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
and !next_is(state, .pipe)
|
and !next_is(state, .pipe)
|
||||||
and !parenthesized_guard(state, condition_start)
|
and !parenthesized_guard(state, condition_start)
|
||||||
and ast.builder_expr_kind(state.builder, condition) != .call
|
and ast.builder_expr_kind(state.builder, condition) != .call
|
||||||
|
and ast.builder_expr_kind(state.builder, condition) != .comptime_expr
|
||||||
and ast.builder_expr_kind(state.builder, condition) != .invalid
|
and ast.builder_expr_kind(state.builder, condition) != .invalid
|
||||||
) {
|
) {
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
||||||
@@ -828,6 +858,7 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
if next_is(state, .enum) return try parse_enum_type(state)
|
if next_is(state, .enum) return try parse_enum_type(state)
|
||||||
if next_is(state, .union) return try parse_union_type(state)
|
if next_is(state, .union) return try parse_union_type(state)
|
||||||
if next_is(state, .match) return try parse_match(state)
|
if next_is(state, .match) return try parse_match(state)
|
||||||
|
if next_is(state, .if) return try parse_if_expr(state)
|
||||||
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
|
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
|
||||||
token :: try consume(state, .ident)
|
token :: try consume(state, .ident)
|
||||||
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||||
@@ -883,6 +914,7 @@ starts_type proc(kind lexer.TokenKind) bool {
|
|||||||
return (
|
return (
|
||||||
kind == .ident or kind == .open_bracket or kind == .struct or kind == .c_struct or kind == .opaque
|
kind == .ident or kind == .open_bracket or kind == .struct or kind == .c_struct or kind == .opaque
|
||||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias or kind == .match
|
or kind == .enum or kind == .union or kind == .distinct or kind == .alias or kind == .match
|
||||||
|
or kind == .if
|
||||||
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc or kind == .c_func
|
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc or kind == .c_func
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1658,6 +1690,7 @@ named_binding_starts proc(state @State) bool {
|
|||||||
next_is(state, .ident)
|
next_is(state, .ident)
|
||||||
and (next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
|
and (next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
|
||||||
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc) or next_n_is(state, 1, .match)
|
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc) or next_n_is(state, 1, .match)
|
||||||
|
or next_n_is(state, 1, .if)
|
||||||
or next_n_is(state, 1, .at)
|
or next_n_is(state, 1, .at)
|
||||||
or next_n_is(state, 1, .question)
|
or next_n_is(state, 1, .question)
|
||||||
or (next_n_is(state, 1, .open_paren) and pointer_binding_starts(state))
|
or (next_n_is(state, 1, .open_paren) and pointer_binding_starts(state))
|
||||||
@@ -1675,6 +1708,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
.union: return try parse_union_type(state)
|
.union: return try parse_union_type(state)
|
||||||
.distinct, .alias: return try parse_type_wrapper(state)
|
.distinct, .alias: return try parse_type_wrapper(state)
|
||||||
.match: return try parse_match(state)
|
.match: return try parse_match(state)
|
||||||
|
.if: return try parse_if_expr(state)
|
||||||
.dollar: {
|
.dollar: {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
_ = try consume(state, .open_paren)
|
_ = try consume(state, .open_paren)
|
||||||
|
|||||||
@@ -1615,6 +1615,14 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
|
||||||
|
state.cells[index].expected = expected
|
||||||
|
for 0..ast.match_arm_count(state.tree, id) |i| {
|
||||||
|
arm :: ast.match_arm_view(state.tree, ast.match_arm(state.tree, id, i))
|
||||||
|
if (arm.expression) try demand(state, arm.body, expected, code)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if (expected.kind == .fallible) {
|
if (expected.kind == .fallible) {
|
||||||
descriptor :: state.arrays.items[usize(expected.array_type)]
|
descriptor :: state.arrays.items[usize(expected.array_type)]
|
||||||
actual := group_type(state, id)
|
actual := group_type(state, id)
|
||||||
@@ -1691,14 +1699,6 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
|
|||||||
state.pointers.items[usize(expected.pointer_type)].element, code)
|
state.pointers.items[usize(expected.pointer_type)].element, code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
|
|
||||||
state.cells[index].expected = expected
|
|
||||||
for 0..ast.match_arm_count(state.tree, id) |i| {
|
|
||||||
arm :: ast.match_arm_view(state.tree, ast.match_arm(state.tree, id, i))
|
|
||||||
if (arm.expression) try demand(state, arm.body, expected, code)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
literal_node :: state.cells[index].literal
|
literal_node :: state.cells[index].literal
|
||||||
if (
|
if (
|
||||||
expected.nominal != ast.NodeId(maxval!(u32))
|
expected.nominal != ast.NodeId(maxval!(u32))
|
||||||
|
|||||||
@@ -4349,6 +4349,18 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
|||||||
.literal_bool: return try builtin(state, id, .bool)
|
.literal_bool: return try builtin(state, id, .bool)
|
||||||
.literal_string: return try builtin(state, id, .string)
|
.literal_string: return try builtin(state, id, .string)
|
||||||
.literal_void: return try builtin(state, id, .void)
|
.literal_void: return try builtin(state, id, .void)
|
||||||
|
.match_expr: {
|
||||||
|
result := no_node
|
||||||
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
||||||
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, i))
|
||||||
|
if (!arm.expression) return no_node
|
||||||
|
annotation :: try expression_type(state, arm.body, depth + 1)
|
||||||
|
if (annotation == no_node) continue
|
||||||
|
result = annotation
|
||||||
|
if (!flexible(state, arm.body)) return result
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
.builtin_call: {
|
.builtin_call: {
|
||||||
name :: ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name)
|
name :: ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name)
|
||||||
count :: ast.call_arg_count(&state.tree, id)
|
count :: ast.call_arg_count(&state.tree, id)
|
||||||
|
|||||||
@@ -11,6 +11,105 @@ HONEY = ROOT / "build/honey"
|
|||||||
|
|
||||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||||
CASES = {
|
CASES = {
|
||||||
|
"conditional_expressions_evaluate_guard_once_and_only_selected_arm": (
|
||||||
|
"""
|
||||||
|
next proc(calls @mut i16, value bool) bool { calls^ = calls^ + 1
|
||||||
|
return value }
|
||||||
|
main proc() [3]i16 {
|
||||||
|
calls i16 := 0
|
||||||
|
first i16 :: if next(&calls, true) 42 else divtrunc!(1, 0)
|
||||||
|
second i16 :: if next(&calls, false) divtrunc!(1, 0) else 7
|
||||||
|
return [first, second, calls]
|
||||||
|
}
|
||||||
|
""", b"[42, 7, 2]\n", None,
|
||||||
|
),
|
||||||
|
"conditional_expressions_nest_bind_else_and_preserve_precedence": (
|
||||||
|
"""
|
||||||
|
choose proc(a, b bool) i16 { return if (a) if (b) 1 else 40 else 3 + 4 }
|
||||||
|
main proc() [3]i16 {
|
||||||
|
return [choose(true, false) + 2, choose(false, true),
|
||||||
|
2 * (if (false) 10 else 21)]
|
||||||
|
}
|
||||||
|
""", b"[42, 7, 42]\n", None,
|
||||||
|
),
|
||||||
|
"conditional_expressions_preserve_aggregate_copy_isolation": (
|
||||||
|
"""
|
||||||
|
Item :: struct { values [1]mut i16 }
|
||||||
|
choose proc(flag bool, left, right Item) Item { return if (flag) left else right }
|
||||||
|
main proc() i16 {
|
||||||
|
left Item := { values = [42] }
|
||||||
|
right Item :: { values = [7] }
|
||||||
|
saved :: choose(true, left, right)
|
||||||
|
left.values[0] = 0
|
||||||
|
return saved.values[0]
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"conditional_expressions_select_generated_types_and_specialized_bodies": (
|
||||||
|
"""
|
||||||
|
Choose proc(wide bool) type { return if (wide) i16 else i8 }
|
||||||
|
length proc(flag bool) usize { return if (flag) 2 else 1 }
|
||||||
|
Box proc($wide bool) type {
|
||||||
|
return struct {
|
||||||
|
value Choose(wide)
|
||||||
|
items [length(wide)]i16
|
||||||
|
other (if (wide) i16 else i8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select proc($flag bool, value i16) i16 {
|
||||||
|
return if $(flag) value else compile_error!("unselected")
|
||||||
|
}
|
||||||
|
main proc() [3]i16 {
|
||||||
|
value Box(true) :: { value = 40, items = [1, 2], other = 42 }
|
||||||
|
return [value.value, i16(value.items.len), select(true, value.other)]
|
||||||
|
}
|
||||||
|
""", b"[40, 2, 42]\n", None,
|
||||||
|
),
|
||||||
|
"conditional_expressions_keep_outer_match_default": (
|
||||||
|
"""
|
||||||
|
choose proc(flag bool) i16 {
|
||||||
|
return match flag {
|
||||||
|
true: (if (flag) 42
|
||||||
|
else 1)
|
||||||
|
else: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() i16 { return choose(false) }
|
||||||
|
""", b"7\n", None,
|
||||||
|
),
|
||||||
|
"conditional_expressions_check_unselected_runtime_result_types": (
|
||||||
|
"""
|
||||||
|
choose proc(flag bool) i16 { return if (flag) i16(42) else u8(1) }
|
||||||
|
main proc() i16 { return choose(true) }
|
||||||
|
""", b"42\n", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"conditional_expressions_require_else": (
|
||||||
|
"main proc() i16 { return if (true) 42 }",
|
||||||
|
b"", b"unexpected token",
|
||||||
|
),
|
||||||
|
"conditional_expressions_preserve_optional_and_fallible_result_context": (
|
||||||
|
"""
|
||||||
|
Error :: enum { failed }
|
||||||
|
optional proc(flag bool) ?i16 { return if (flag) 42 else null }
|
||||||
|
fallible proc(flag bool) i16 ! Error { return if (flag) 42 else .failed }
|
||||||
|
main proc() [3]i16 {
|
||||||
|
if (optional(false) != null) return [0, 0, 0]
|
||||||
|
return [optional(true)?, fallible(true) catch 0, fallible(false) catch 7]
|
||||||
|
}
|
||||||
|
""", b"[42, 42, 7]\n", None,
|
||||||
|
),
|
||||||
|
"conditional_expressions_materialize_direct_constants": (
|
||||||
|
"""
|
||||||
|
main proc() [2]i16 {
|
||||||
|
return [i16($(if (true) 42 else divtrunc!(1, 0))),
|
||||||
|
$(if (false) 0 else i16(7))]
|
||||||
|
}
|
||||||
|
""", b"[42, 7]\n", None,
|
||||||
|
),
|
||||||
|
"conditional_expressions_reject_nonboolean_guards": (
|
||||||
|
"main proc() i16 { return if (1) 42 else 0 }",
|
||||||
|
b"", b"incompatible value type",
|
||||||
|
),
|
||||||
"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