rename expand to inline

This commit is contained in:
2026-08-02 20:31:05 +02:00
parent c92723bc14
commit 9e79d6692b
13 changed files with 123 additions and 82 deletions
+3 -3
View File
@@ -117,13 +117,13 @@ fields. `_` is not a keyword member name.
- field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support - field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
- boolean `if` / `else if` / `else` and `for` loops with braceless single-statement bodies when the preceding expression is parenthesized or a function call - boolean `if` / `else if` / `else` and `for` loops with braceless single-statement bodies when the preceding expression is parenthesized or a function call
- `while` loops with optional post-iteration update clauses - `while` loops with optional post-iteration update clauses
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures; `expand for` specializes a comptime aggregate into one checked body per element - `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures; `inline for` specializes a comptime aggregate into one checked body per element
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks; `break :label` can cross nested scopes to exit a labeled block - `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks; `break :label` can cross nested scopes to exit a labeled block
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO - bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
- always-trapping `unreachable`, a `noreturn` expression that diagnoses use during comptime evaluation and terminates the current runtime path - always-trapping `unreachable`, a `noreturn` expression that diagnoses use during comptime evaluation and terminates the current runtime path
- bare void `return`, same-line `return value`, value blocks, value `if` with implicit single-expression branches, value loops, value `match`, and strictly value-producing `yield value` / `yield :label value` - bare void `return`, same-line `return value`, value blocks, value `if` with implicit single-expression branches, value loops, value `match`, and strictly value-producing `yield value` / `yield :label value`
- `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns - `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns
- a final `expand |value|:` enum arm or `expand |payload[, tag]|:` tagged-union arm generates one specialized arm for each variant not covered earlier; enum values and optional tags are comptime-known, while union payloads keep their concrete variant type - a final `inline |value|:` enum arm or `inline |payload[, tag]|:` tagged-union arm generates one specialized arm for each variant not covered earlier; enum values and optional tags are comptime-known, while union payloads keep their concrete variant type
- fallible `try` and uniform `catch [|e|] value_source` fallbacks; captures work with ordinary expressions, value blocks, and value-producing `if` / loops / `match` - fallible `try` and uniform `catch [|e|] value_source` fallbacks; captures work with ordinary expressions, value blocks, and value-producing `if` / loops / `match`
- direct `return match ...` and `yield match ...` value-control-flow operands - direct `return match ...` and `yield match ...` value-control-flow operands
@@ -238,7 +238,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
- `some!(value)` explicitly constructs the present branch of an expected optional, including nested optionals where `some!(null)` differs from outer `null` - `some!(value)` explicitly constructs the present branch of an expected optional, including nested optionals where `some!(null)` differs from outer `null`
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0` - tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
- anonymous keyed records use `{x = 1, name = "bro"}`; without context their declaration-ordered names and inferred value types form a structurally interned record type, while a record context applies that type's coercions and field defaults; `{}` remains an empty tuple without context and constructs an empty contextual record when a record is expected - anonymous keyed records use `{x = 1, name = "bro"}`; without context their declaration-ordered names and inferred value types form a structurally interned record type, while a record context applies that type's coercions and field defaults; `{}` remains an empty tuple without context and constructs an empty contextual record when a record is expected
- `typeinfo!`, `field!`, `compile_error!`, and semantic `expand for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and expand-loop `break` / `continue` must be selected entirely at comptime - `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
- `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name - `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name
- bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names - bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions - concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
+5 -6
View File
@@ -856,8 +856,8 @@
- tuples are unnamed-field structs with structural anonymous values, nominal named declarations, - tuples are unnamed-field structs with structural anonymous values, nominal named declarations,
brace literals, numeric fields, and no runtime metadata brace literals, numeric fields, and no runtime metadata
- `@std/meta`, `typeinfo!`, `field!`, `compile_error!`, specialization-time branches, and semantic - `@std/meta`, `typeinfo!`, `field!`, `compile_error!`, specialization-time branches, and semantic
`expand for` use checker-owned persistent compile-time values for aggregate-first reflection and `inline for` use checker-owned persistent compile-time values for aggregate-first reflection and
heterogeneous static expansion; expand-loop control is recursively resolved at comptime heterogeneous static expansion; inline-loop control is recursively resolved at comptime
- interleaved comptime parameters use semantic candidate resolution, immutable byte values specialize - interleaved comptime parameters use semantic candidate resolution, immutable byte values specialize
by contents, and all comptime parameters remain erased from the runtime ABI by contents, and all comptime parameters remain erased from the runtime ABI
- `io.print(writer, format, args)` validates and expands `{s}` / `{d}` formatting at comptime, - `io.print(writer, format, args)` validates and expands `{s}` / `{d}` formatting at comptime,
@@ -890,10 +890,9 @@
- integer output uses one base-aware 65-byte stack buffer; float output uses fixed-buffer - integer output uses one base-aware 65-byte stack buffer; float output uses fixed-buffer
libc `snprintf` with 32-bit and 64-bit general/scientific precision and propagates failure libc `snprintf` with 32-bit and 64-bit general/scientific precision and propagates failure
40. compile-time `expand` (implemented) 40. compile-time `inline` (implemented)
- expansion-oriented `inline for` is strictly renamed to `expand for`; `inline` remains available - expansion-oriented `expand for` is strictly renamed to `inline for`; `expand` is no longer a keyword
for future function-inlining syntax - final inlined enum and tagged-union match arms generate checker-local specialized arms only
- final expanded enum and tagged-union match arms generate checker-local specialized arms only
for variants not covered by preceding explicit arms for variants not covered by preceding explicit arms
- generated enum values and union tags are static bindings, heterogeneous payloads retain their - generated enum values and union tags are static bindings, heterogeneous payloads retain their
concrete types, and `void` payloads support value and pointer captures without runtime storage concrete types, and `void` payloads support value and pointer captures without runtime storage
+1 -1
View File
@@ -221,7 +221,7 @@ Stmt :: struct {
// list (empty marks the `else` arm; more than one is a multi-pattern arm), // list (empty marks the `else` arm; more than one is a multi-pattern arm),
// `captures` for the optional payload capture (0 or 1 name, tagged-union variants // `captures` for the optional payload capture (0 or 1 name, tagged-union variants
// only) with `pointer_capture` distinguishing `|@cap|` from `|cap|`, and `body` // only) with `pointer_capture` distinguishing `|@cap|` from `|cap|`, and `body`
// as the arm body. An expanded arm has `expand` set, no patterns, and one or two // as the arm body. An `inline` arm has `expand` set, no patterns, and one or two
// captures for its specialized value/payload and optional tagged-union tag. // captures for its specialized value/payload and optional tagged-union tag.
captures: []symbol.Id, captures: []symbol.Id,
// `Match_Arm` pattern list; empty the `else` arm. // `Match_Arm` pattern list; empty the `else` arm.
+14 -14
View File
@@ -10574,7 +10574,7 @@ expand_field_bindings :: proc(
value := state.values[value_id] value := state.values[value_id]
if ct_value_contains_undefined(&state, value_id) { if ct_value_contains_undefined(&state, value_id) {
if diagnose { if diagnose {
_ = ct_fail(&state, .Not_Comptime, checker.ast_module.exprs[expr].span, "expand for cannot expand an undefined comptime value") _ = ct_fail(&state, .Not_Comptime, checker.ast_module.exprs[expr].span, "inline for cannot iterate an undefined comptime value")
} }
return nil, .Diagnosed if state.diagnostic != source.INVALID_DIAGNOSTIC else .Invalid return nil, .Diagnosed if state.diagnostic != source.INVALID_DIAGNOSTIC else .Invalid
} }
@@ -10712,7 +10712,7 @@ contains_expand_control :: proc(
} }
case .For, .While: case .For, .While:
// Unlabelled control belongs to the nested loop. A labelled jump can still // Unlabelled control belongs to the nested loop. A labelled jump can still
// name the surrounding expand loop and is therefore relevant here. // name the surrounding inline loop and is therefore relevant here.
if contains_expand_control(checker, statement.body, target_label, false) { if contains_expand_control(checker, statement.body, target_label, false) {
return true return true
} }
@@ -10817,7 +10817,7 @@ flatten_expand_iteration :: proc(
if diagnostic != nil { if diagnostic != nil {
diagnostic^ = source.add( diagnostic^ = source.add(
checker.diagnostics, statement.span, checker.diagnostics, statement.span,
"break or continue targeting an expand loop must be compile-time-resolvable", "break or continue targeting an inline loop must be compile-time-resolvable",
) )
} }
return .Invalid return .Invalid
@@ -10856,7 +10856,7 @@ flatten_expand_iteration :: proc(
if diagnostic != nil { if diagnostic != nil {
diagnostic^ = source.add( diagnostic^ = source.add(
checker.diagnostics, statement.span, checker.diagnostics, statement.span,
"break or continue targeting an expand loop must be compile-time-resolvable", "break or continue targeting an inline loop must be compile-time-resolvable",
) )
} }
return .Invalid return .Invalid
@@ -10882,7 +10882,7 @@ flatten_expand_iteration :: proc(
if diagnostic != nil { if diagnostic != nil {
diagnostic^ = source.add( diagnostic^ = source.add(
checker.diagnostics, statement.span, checker.diagnostics, statement.span,
"break or continue targeting an expand loop must be compile-time-resolvable", "break or continue targeting an inline loop must be compile-time-resolvable",
) )
} }
return .Invalid return .Invalid
@@ -11814,7 +11814,7 @@ build_block :: proc(
case .For: case .For:
if statement.expand { if statement.expand {
if statement.pointer_capture { if statement.pointer_capture {
id := source.add(checker.diagnostics, statement.span, "expand for does not support pointer captures") id := source.add(checker.diagnostics, statement.span, "inline for does not support pointer captures")
append(&body, hir.stmt_id(len(checker.module.statements))) append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{kind=.Trap, span=statement.span, diagnostic=id}) append(&checker.module.statements, hir.Stmt{kind=.Trap, span=statement.span, diagnostic=id})
ctx.problematic^ = true ctx.problematic^ = true
@@ -11822,9 +11822,9 @@ build_block :: proc(
} }
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, ctx.pkg, ctx.file, true) bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, ctx.pkg, ctx.file, true)
if expand_error != .None && expand_error != .Diagnosed { if expand_error != .None && expand_error != .Diagnosed {
message := "expand for requires a comptime tuple, fixed array, range, slice, or reflection value" message := "inline for requires a comptime tuple, fixed array, range, slice, or reflection value"
if expand_error == .Quota { if expand_error == .Quota {
message = "expand for expansion exceeds the compile-time evaluation quota" message = "inline for expansion exceeds the compile-time evaluation quota"
} }
id := source.add( id := source.add(
checker.diagnostics, statement.span, checker.diagnostics, statement.span,
@@ -12869,28 +12869,28 @@ emit_match :: proc(
continue continue
} }
if has_else || has_expand { if has_else || has_expand {
message := "arms after 'else' are unreachable" if has_else else "arms after 'expand' are unreachable" message := "arms after 'else' are unreachable" if has_else else "arms after 'inline' are unreachable"
source.add(checker.diagnostics, arm.span, message) source.add(checker.diagnostics, arm.span, message)
ok = false ok = false
} }
if arm.expand { if arm.expand {
if !is_tagged && !is_enum_subject { if !is_tagged && !is_enum_subject {
source.add(checker.diagnostics, arm.span, "'expand' requires an enum or tagged-union match subject") source.add(checker.diagnostics, arm.span, "'inline' requires an enum or tagged-union match subject")
ok = false ok = false
continue continue
} }
expected_captures := 1 if is_enum_subject else 2 expected_captures := 1 if is_enum_subject else 2
if len(arm.captures) == 0 || len(arm.captures) > expected_captures { if len(arm.captures) == 0 || len(arm.captures) > expected_captures {
description := "exactly one capture" if is_enum_subject else "one or two captures" description := "exactly one capture" if is_enum_subject else "one or two captures"
source.addf(checker.diagnostics, arm.span, "expanded match on '%s' requires %s", type_label(checker, subject_type), description) source.addf(checker.diagnostics, arm.span, "inlined match on '%s' requires %s", type_label(checker, subject_type), description)
ok = false ok = false
} }
if is_enum_subject && arm.pointer_capture { if is_enum_subject && arm.pointer_capture {
source.add(checker.diagnostics, arm.span, "enum expansion does not support pointer captures") source.add(checker.diagnostics, arm.span, "enum inlining does not support pointer captures")
ok = false ok = false
} }
if len(arm.captures) > 1 && arm.captures[0] != checker.sink_symbol && arm.captures[0] == arm.captures[1] { if len(arm.captures) > 1 && arm.captures[0] != checker.sink_symbol && arm.captures[0] == arm.captures[1] {
source.add(checker.diagnostics, arm.span, "expand captures must have distinct names") source.add(checker.diagnostics, arm.span, "inline captures must have distinct names")
ok = false ok = false
} }
remaining := 0 remaining := 0
@@ -12963,7 +12963,7 @@ emit_match :: proc(
} }
} }
if remaining == 0 { if remaining == 0 {
source.add(checker.diagnostics, arm.span, "redundant 'expand': the 'match' already covers every variant") source.add(checker.diagnostics, arm.span, "redundant 'inline': the 'match' already covers every variant")
ok = false ok = false
} }
has_expand = true has_expand = true
+1 -1
View File
@@ -40,7 +40,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
case "if": return .Keyword_If case "if": return .Keyword_If
case "while": return .Keyword_While case "while": return .Keyword_While
case "for": return .Keyword_For case "for": return .Keyword_For
case "expand": return .Keyword_Expand case "inline": return .Keyword_Inline
case "break": return .Keyword_Break case "break": return .Keyword_Break
case "continue": return .Keyword_Continue case "continue": return .Keyword_Continue
case "defer": return .Keyword_Defer case "defer": return .Keyword_Defer
+8 -17
View File
@@ -1813,16 +1813,7 @@ parse_value_control_flow :: proc(parser: ^Parser) -> (ast.Stmt_Id, bool) {
} }
parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id { parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
if current(parser).kind == .Identifier && token_text(parser, current(parser)) == "inline" && if current(parser).kind == .Keyword_Inline && peek(parser).kind == .Keyword_For {
peek(parser).kind == .Keyword_For {
start := advance(parser)
id := parse_for(parser)
parser.module.statements[id].expand = true
parser.module.statements[id].span = span_from(start.span, parser.module.statements[id].span)
source.add(parser.diagnostics, start.span, "'inline for' was renamed to 'expand for'")
return id
}
if current(parser).kind == .Keyword_Expand && peek(parser).kind == .Keyword_For {
start := advance(parser) start := advance(parser)
id := parse_for(parser) id := parse_for(parser)
parser.module.statements[id].expand = true parser.module.statements[id].expand = true
@@ -2266,7 +2257,7 @@ parse_arm_body :: proc(parser: ^Parser) -> []ast.Stmt_Id {
} }
// parse_match_arm parses one `<pattern,...> [|[@]capture|]: <body>`, `else: <body>`, // parse_match_arm parses one `<pattern,...> [|[@]capture|]: <body>`, `else: <body>`,
// or `expand |[@]value[, tag]|: <body>` arm. // or `inline |[@]value[, tag]|: <body>` arm.
parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id { parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := current(parser).span start := current(parser).span
patterns: [dynamic]ast.Expr_Id patterns: [dynamic]ast.Expr_Id
@@ -2275,10 +2266,10 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
captures.allocator = parser.module.allocator captures.allocator = parser.module.allocator
pointer_capture := false pointer_capture := false
expand := false expand := false
if _, is_expand := allow(parser, .Keyword_Expand); is_expand { if _, is_expand := allow(parser, .Keyword_Inline); is_expand {
expand = true expand = true
if _, ok := allow(parser, .Pipe); !ok { if _, ok := allow(parser, .Pipe); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '|' before expand captures") source.add(parser.diagnostics, current(parser).span, "expected '|' before inline captures")
} else { } else {
if _, at_ok := allow(parser, .At); at_ok { if _, at_ok := allow(parser, .At); at_ok {
pointer_capture = true pointer_capture = true
@@ -2288,7 +2279,7 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
advance(parser) advance(parser)
append(&captures, name_tok.symbol) append(&captures, name_tok.symbol)
} else { } else {
source.add(parser.diagnostics, current(parser).span, "expected an expand value capture") source.add(parser.diagnostics, current(parser).span, "expected an inline value capture")
} }
if _, comma_ok := allow(parser, .Comma); comma_ok { if _, comma_ok := allow(parser, .Comma); comma_ok {
tag_tok := current(parser) tag_tok := current(parser)
@@ -2296,10 +2287,10 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
advance(parser) advance(parser)
append(&captures, tag_tok.symbol) append(&captures, tag_tok.symbol)
} else { } else {
source.add(parser.diagnostics, current(parser).span, "expected an expand tag capture") source.add(parser.diagnostics, current(parser).span, "expected an inline tag capture")
} }
if _, extra := allow(parser, .Comma); extra { if _, extra := allow(parser, .Comma); extra {
source.add(parser.diagnostics, current(parser).span, "'expand' accepts at most two captures") source.add(parser.diagnostics, current(parser).span, "'inline' accepts at most two captures")
for current(parser).kind != .Pipe && current(parser).kind != .Colon && for current(parser).kind != .Pipe && current(parser).kind != .Colon &&
current(parser).kind != .Newline && current(parser).kind != .Eof { current(parser).kind != .Newline && current(parser).kind != .Eof {
advance(parser) advance(parser)
@@ -2307,7 +2298,7 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
} }
} }
if _, close_ok := allow(parser, .Pipe); !close_ok { if _, close_ok := allow(parser, .Pipe); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected '|' to close expand captures") source.add(parser.diagnostics, current(parser).span, "expected '|' to close inline captures")
} }
} }
} else if _, is_else := allow(parser, .Keyword_Else); !is_else { } else if _, is_else := allow(parser, .Keyword_Else); !is_else {
+1 -1
View File
@@ -85,7 +85,7 @@ Kind :: enum u8 {
Keyword_If, Keyword_If,
Keyword_While, Keyword_While,
Keyword_For, Keyword_For,
Keyword_Expand, Keyword_Inline,
Keyword_Break, Keyword_Break,
Keyword_Continue, Keyword_Continue,
Keyword_Defer, Keyword_Defer,
+79 -28
View File
@@ -177,6 +177,21 @@ lexer_preserves_newlines_and_skips_comments :: proc(t: ^testing.T) {
testing.expect_value(t, stream.items[1].kind, token.Kind.Identifier) testing.expect_value(t, stream.items[1].kind, token.Kind.Identifier)
} }
@(test)
inline_is_keyword_and_expand_is_identifier :: proc(t: ^testing.T) {
source_file := source.Source{path="test.bro", text="inline expand"}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, stream.items[0].kind, token.Kind.Keyword_Inline)
testing.expect_value(t, stream.items[1].kind, token.Kind.Identifier)
}
@(test) @(test)
lexer_recognizes_bitwise_operators_with_longest_match :: proc(t: ^testing.T) { lexer_recognizes_bitwise_operators_with_longest_match :: proc(t: ^testing.T) {
source_file := source.Source{path="test.bro", text="~ & &= | |= xor xor= << <<= >> >>= <<| <<|= ^"} source_file := source.Source{path="test.bro", text="~ & &= | |= xor xor= << <<= >> >>= <<| <<|= ^"}
@@ -1253,6 +1268,45 @@ main func() void {
testing.expect(t, !found_old_function_error) testing.expect(t, !found_old_function_error)
} }
@(test)
function_signature_context_infers_enum_literals :: proc(t: ^testing.T) {
text := `TokenKind :: enum {
ident
double_colon
newline
}
ParseError :: enum {
unexpected_token
}
parse func() void ! ParseError {
try expect(.ident)
try expect(.double_colon)
try expect(.newline)
}
expect func(kind TokenKind) void ! ParseError {
if kind == .newline {
return .unexpected_token
}
}
main func() void {
parse() catch |_| {}
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 0)
}
@(test) @(test)
pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) { pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) {
text := `sum_c c_func(a, b int) int { text := `sum_c c_func(a, b int) int {
@@ -3236,10 +3290,10 @@ milestone_37_tuples_reflection_expand_for_and_debug_print_compile_and_run :: pro
@(test) @(test)
expanded_matches_and_tag_intrinsics_compile_and_run :: proc(t: ^testing.T) { expanded_matches_and_tag_intrinsics_compile_and_run :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-expand" output := "/tmp/brolang-test-inline"
defer _ = os.remove(output) defer _ = os.remove(output)
status := compiler_core.compile_package( status := compiler_core.compile_package(
"examples/programs/expand", output, nil, target.DEFAULT, cimport.Options{}, ".", "examples/programs/inline", output, nil, target.DEFAULT, cimport.Options{}, ".",
) )
testing.expect_value(t, status, 0) testing.expect_value(t, status, 0)
state, stdout, stderr, _ := os2.process_exec( state, stdout, stderr, _ := os2.process_exec(
@@ -3261,29 +3315,29 @@ main func() void {
inline for {1} |value| { _ = value } inline for {1} |value| { _ = value }
n i32 = 1 n i32 = 1
match n { expand |value|: _ = value } match n { inline |value|: _ = value }
e E = .a e E = .a
match e { expand |value, tag|: _ = value } match e { inline |value, tag|: _ = value }
match e { match e {
.a, .b: {} .a, .b: {}
expand |value|: _ = value inline |value|: _ = value
} }
match e { match e {
expand |value|: _ = value inline |value|: _ = value
.b: {} .b: {}
} }
match e { match e {
else: {} else: {}
expand |value|: _ = value inline |value|: _ = value
} }
u Plain = Plain{value = 1} u Plain = Plain{value = 1}
_ = tag!(u) _ = tag!(u)
_ = tagname!(e) _ = tagname!(e)
match e { expand ||: {} } match e { inline ||: {} }
match e { expand |a, b, c|: {} } match e { inline |a, b, c|: {} }
} }
` `
source_file := source.Source{path="expand_diagnostics.bro", text=text} source_file := source.Source{path="expand_diagnostics.bro", text=text}
@@ -3298,7 +3352,6 @@ main func() void {
hir_module := checker.check(&ast_module, &diagnostics, &symbols) hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module) defer hir.destroy_module(&hir_module)
found_old := false
found_subject := false found_subject := false
found_captures := false found_captures := false
found_redundant := false found_redundant := false
@@ -3309,17 +3362,15 @@ main func() void {
found_tagname := false found_tagname := false
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
message := diagnostic.message message := diagnostic.message
found_old = found_old || strings.contains(message, "'inline for' was renamed to 'expand for'") found_subject = found_subject || strings.contains(message, "'inline' requires an enum or tagged-union")
found_subject = found_subject || strings.contains(message, "'expand' requires an enum or tagged-union")
found_captures = found_captures || strings.contains(message, "requires exactly one capture") found_captures = found_captures || strings.contains(message, "requires exactly one capture")
found_redundant = found_redundant || strings.contains(message, "redundant 'expand'") found_redundant = found_redundant || strings.contains(message, "redundant 'inline'")
found_after = found_after || strings.contains(message, "arms after 'expand' are unreachable") || strings.contains(message, "arms after 'else' are unreachable") found_after = found_after || strings.contains(message, "arms after 'inline' are unreachable") || strings.contains(message, "arms after 'else' are unreachable")
found_missing = found_missing || strings.contains(message, "expected an expand value capture") found_missing = found_missing || strings.contains(message, "expected an inline value capture")
found_many = found_many || strings.contains(message, "at most two captures") found_many = found_many || strings.contains(message, "at most two captures")
found_tag = found_tag || strings.contains(message, "tag! requires a tagged-union value") found_tag = found_tag || strings.contains(message, "tag! requires a tagged-union value")
found_tagname = found_tagname || strings.contains(message, "tagname! requires a comptime-known enum value") found_tagname = found_tagname || strings.contains(message, "tagname! requires a comptime-known enum value")
} }
testing.expect(t, found_old)
testing.expect(t, found_subject) testing.expect(t, found_subject)
testing.expect(t, found_captures) testing.expect(t, found_captures)
testing.expect(t, found_redundant) testing.expect(t, found_redundant)
@@ -3751,7 +3802,7 @@ main func() i32 { return answer }
milestone_37_expand_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) { milestone_37_expand_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) {
text := `main func() void { text := `main func() void {
total i32 = 0 total i32 = 0
expand for {1, 2} |value| { inline for {1, 2} |value| {
if total == 0 { if total == 0 {
break break
} }
@@ -3775,7 +3826,7 @@ milestone_37_expand_loop_control_must_be_statically_resolvable :: proc(t: ^testi
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
found = found || strings.contains( found = found || strings.contains(
diagnostic.message, diagnostic.message,
"break or continue targeting an expand loop must be compile-time-resolvable", "break or continue targeting an inline loop must be compile-time-resolvable",
) )
} }
testing.expect(t, found) testing.expect(t, found)
@@ -3803,7 +3854,7 @@ read_initialized_sibling func() i32 {
answer :: $read_initialized_sibling() answer :: $read_initialized_sibling()
main func() i32 { main func() i32 {
total usize = 0 total usize = 0
expand for make_tokens() |token| { inline for make_tokens() |token| {
total += token.text.len + token.count total += token.text.len + token.count
} }
if answer != 42 or total != 8 { if answer != 42 or total != 8 {
@@ -3884,8 +3935,8 @@ milestone_37_expand_expansions_keep_distinct_call_resolutions :: proc(t: ^testin
} }
main func() i32 { main func() i32 {
total i64 = 0 total i64 = 0
expand for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| { inline for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| {
expand for row |value| { inline for row |value| {
total += i64(identity(value)) total += i64(identity(value))
} }
} }
@@ -3917,13 +3968,13 @@ main func() i32 {
milestone_37_expand_control_prunes_inference_after_static_exit :: proc(t: ^testing.T) { milestone_37_expand_control_prunes_inference_after_static_exit :: proc(t: ^testing.T) {
text := `take_i8 func(value i8) void { _ = value } text := `take_i8 func(value i8) void { _ = value }
main func() void { main func() void {
expand for {i8(1), "skip"} |value, index| { inline for {i8(1), "skip"} |value, index| {
if index == 1 { if index == 1 {
continue continue
} }
take_i8(value) take_i8(value)
} }
expand for {i8(1), "stop"} |value, index| { inline for {i8(1), "stop"} |value, index| {
if index == 1 { if index == 1 {
break break
} }
@@ -3954,7 +4005,7 @@ StringToken :: struct { kind Kind, value []u8 }
StopToken :: struct { kind Kind, value bool } StopToken :: struct { kind Kind, value bool }
take_i8 func(value i8) void { _ = value } take_i8 func(value i8) void { _ = value }
main func() void { main func() void {
expand for { inline for {
IntToken {kind = .integer, value = 1}, IntToken {kind = .integer, value = 1},
StringToken {kind = .string, value = "ok"}, StringToken {kind = .string, value = "ok"},
StopToken {kind = .stop, value = false}, StopToken {kind = .stop, value = false},
@@ -3968,7 +4019,7 @@ main func() void {
.stop: break .stop: break
} }
} }
expand for {i8(2), "skip", "stop"} |value, index| { inline for {i8(2), "skip", "stop"} |value, index| {
match index { match index {
0: {} 0: {}
1..=1, 7: continue 1..=1, 7: continue
@@ -6742,7 +6793,7 @@ via_nested func() i32 ! AError {
via_expand func() i32 ! AError { via_expand func() i32 ! AError {
abc(1) catch |err| { abc(1) catch |err| {
match err { match err {
expand |tag|: match tag { inline |tag|: match tag {
.a: return err .a: return err
else: unreachable else: unreachable
} }
@@ -15247,7 +15298,7 @@ Map func($E, $V type) type {
init func($E, $V type, values meta.EnumFieldStruct(E, ?V, some!(null))) Map(E, V) { init func($E, $V type, values meta.EnumFieldStruct(E, ?V, some!(null))) Map(E, V) {
map Map(E, V) = undefined map Map(E, V) = undefined
match typeinfo!(E) { match typeinfo!(E) {
.enum |info|: expand for info.fields |field, index| { .enum |info|: inline for info.fields |field, index| {
map.present[index] = false map.present[index] = false
if field!(values, field.name) |value| { if field!(values, field.name) |value| {
map.present[index] = true map.present[index] = true
@@ -15261,7 +15312,7 @@ init func($E, $V type, values meta.EnumFieldStruct(E, ?V, some!(null))) Map(E, V
get func($E, $V type, map @Map(E, V), key E) ?V { get func($E, $V type, map @Map(E, V), key E) ?V {
match typeinfo!(E) { match typeinfo!(E) {
.enum |info|: expand for info.fields |field, index| { .enum |info|: inline for info.fields |field, index| {
if key == field!(E, field.name) { if key == field!(E, field.name) {
if map.present[index] { return map.values[index] } if map.present[index] { return map.values[index] }
return null return null
@@ -18,7 +18,7 @@ Value :: union(enum) {
enum_score func(kind Kind) i32 { enum_score func(kind Kind) i32 {
result :: match kind { result :: match kind {
.first: 1 .first: 1
expand |value|: { inline |value|: {
yield match value { yield match value {
.second: 2 .second: 2
.third: 3 .third: 3
@@ -31,7 +31,7 @@ enum_score func(kind Kind) i32 {
equal_value func(a, b Value) bool { equal_value func(a, b Value) bool {
if (tag!(a) != tag!(b)) return false if (tag!(a) != tag!(b)) return false
result :: match a { result :: match a {
expand |value, tag|: { inline |value, tag|: {
yield match tag { yield match tag {
.number: value == field!(b, tagname!(tag)) .number: value == field!(b, tagname!(tag))
.pair: { .pair: {
@@ -49,7 +49,7 @@ equal_value func(a, b Value) bool {
increment func(value @mut Value) void { increment func(value @mut Value) void {
match value^ { match value^ {
expand |@payload, tag|: match tag { inline |@payload, tag|: match tag {
.number: payload^ += 1 .number: payload^ += 1
.pair: payload.left += 1 .pair: payload.left += 1
.empty: _ = payload .empty: _ = payload
+3 -3
View File
@@ -11,7 +11,7 @@ format func() []u8 {
sum func($T type, value T) i32 { sum func($T type, value T) i32 {
total i32 = 0 total i32 = 0
match typeinfo!(T) { match typeinfo!(T) {
.record |record|: expand for record.fields |field| { .record |record|: inline for record.fields |field| {
total += i32(field!(value, field.name)) total += i32(field!(value, field.name))
} }
else: compile_error!("sum requires a record") else: compile_error!("sum requires a record")
@@ -22,7 +22,7 @@ sum func($T type, value T) i32 {
static_control func($T type, value T) i32 { static_control func($T type, value T) i32 {
total i32 = 0 total i32 = 0
match typeinfo!(T) { match typeinfo!(T) {
.record |record|: expand for record.fields |field| { .record |record|: inline for record.fields |field| {
{ {
if field.index == 1 { if field.index == 1 {
continue continue
@@ -50,7 +50,7 @@ row_value func(row Row) i32 {
static_aggregates func() i32 { static_aggregates func() i32 {
total i32 = 0 total i32 = 0
expand for {Row {value = 2}, Row {value = 40}} |row| { inline for {Row {value = 2}, Row {value = 40}} |row| {
total += row_value(row) total += row_value(row)
} }
return total return total
+2 -2
View File
@@ -17,7 +17,7 @@ init func(
map EnumMap(E, V) = undefined map EnumMap(E, V) = undefined
match typeinfo!(E) { match typeinfo!(E) {
.enum |info|: expand for info.fields |field, i| { .enum |info|: inline for info.fields |field, i| {
map.present[i] = false map.present[i] = false
if field!(values, field.name) |value| { if field!(values, field.name) |value| {
@@ -34,7 +34,7 @@ init func(
get func($E, $V type, map @EnumMap(E, V), key E) ?V { get func($E, $V type, map @EnumMap(E, V), key E) ?V {
# fixme: linear lookup; implement an enum index/discriminant map for O(1) lookup # fixme: linear lookup; implement an enum index/discriminant map for O(1) lookup
match typeinfo!(E) { match typeinfo!(E) {
.enum |info|: expand for info.fields |field, i| { .enum |info|: inline for info.fields |field, i| {
if key == field!(E, field.name) { if key == field!(E, field.name) {
if (map.present[i]) return map.values[i] if (map.present[i]) return map.values[i]
return null return null
+2 -2
View File
@@ -111,7 +111,7 @@ stderr func(io Io) Writer {
} }
print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError { print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError {
expand for parse_format(format.len, format, Args) |token| { inline for parse_format(format.len, format, Args) |token| {
match token.kind { match token.kind {
.unused: break .unused: break
.literal: try write_all(output, format[token.start..token.end]) .literal: try write_all(output, format[token.start..token.end])
@@ -427,7 +427,7 @@ hide write_default func(output Writer, $T type, value T) void ! WriteError {
.pointer: try write_all(output, value) .pointer: try write_all(output, value)
.slice: try write_all(output, value) .slice: try write_all(output, value)
.enum |enum_info|: { .enum |enum_info|: {
expand for enum_info.fields |field| { inline for enum_info.fields |field| {
if value == field!(T, field.name) { if value == field!(T, field.name) {
try write_all(output, ".") try write_all(output, ".")
try write_all(output, field.name) try write_all(output, field.name)
+1 -1
View File
@@ -49,7 +49,7 @@ EnumFieldStruct func($E, $Field type, $default ?Field) type {
names [info.fields.len]mut []u8 = undefined names [info.fields.len]mut []u8 = undefined
field_types [info.fields.len]mut type = undefined field_types [info.fields.len]mut type = undefined
defaults [info.fields.len]mut ?Field = undefined defaults [info.fields.len]mut ?Field = undefined
expand for info.fields |field, index| { inline for info.fields |field, index| {
names[index] = field.name names[index] = field.name
field_types[index] = Field field_types[index] = Field
defaults[index] = default defaults[index] = default