comptime expandable match statements
This commit is contained in:
+4
-2
@@ -89,11 +89,12 @@ 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
|
- `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
|
||||||
- `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
|
||||||
- bare void `return`, same-line `return value`, value blocks, value `if`, value loops, value `match`, and strictly value-producing `yield value` / `yield :label value`
|
- bare void `return`, same-line `return value`, value blocks, value `if`, 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
|
||||||
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
||||||
- direct `return match ...` and `yield match ...` value-control-flow operands
|
- direct `return match ...` and `yield match ...` value-control-flow operands
|
||||||
|
|
||||||
@@ -156,7 +157,8 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
|
|||||||
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
|
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
|
||||||
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
|
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
|
||||||
- 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`
|
||||||
- `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
|
- `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
|
||||||
|
- `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
|
||||||
- native function pointer values and types with `@func(...) R`, fallible `@func(...) R ! E`, optional `?@func(...) R`, and non-variadic native indirect calls
|
- native function pointer values and types with `@func(...) R`, fallible `@func(...) R ! E`, optional `?@func(...) R`, and non-variadic native indirect calls
|
||||||
|
|||||||
@@ -851,8 +851,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
|
||||||
`inline for` use checker-owned persistent compile-time values for aggregate-first reflection and
|
`expand for` use checker-owned persistent compile-time values for aggregate-first reflection and
|
||||||
heterogeneous static expansion; inline-loop control is recursively resolved at comptime
|
heterogeneous static expansion; expand-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,
|
||||||
@@ -885,6 +885,16 @@
|
|||||||
- 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)
|
||||||
|
- expansion-oriented `inline for` is strictly renamed to `expand for`; `inline` remains available
|
||||||
|
for future function-inlining syntax
|
||||||
|
- final expanded enum and tagged-union match arms generate checker-local specialized arms only
|
||||||
|
for variants not covered by preceding explicit arms
|
||||||
|
- 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
|
||||||
|
- `tag!` reads or folds a tagged union discriminant, while `tagname!` turns a comptime-known enum
|
||||||
|
value into its immutable field-name string
|
||||||
|
|
||||||
## A word on unchecked casts
|
## A word on unchecked casts
|
||||||
|
|
||||||
For casts that bypass safety checks, Honey provides builtin functions:
|
For casts that bypass safety checks, Honey provides builtin functions:
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ Stmt :: struct {
|
|||||||
immutable: bool,
|
immutable: bool,
|
||||||
value_control_flow: bool,
|
value_control_flow: bool,
|
||||||
pointer_capture: bool,
|
pointer_capture: bool,
|
||||||
inline: bool,
|
expand: bool,
|
||||||
error_only: bool,
|
error_only: bool,
|
||||||
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
|
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
|
||||||
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
|
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
|
||||||
@@ -207,7 +207,8 @@ 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.
|
// as the arm body. An expanded arm has `expand` set, no patterns, and one or two
|
||||||
|
// 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.
|
||||||
patterns: []Expr_Id,
|
patterns: []Expr_Id,
|
||||||
|
|||||||
+403
-69
@@ -59,7 +59,7 @@ Static_Binding :: struct {
|
|||||||
value: Ct_Value_Id,
|
value: Ct_Value_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
Inline_Expansion :: struct {
|
Expand_Expansion :: struct {
|
||||||
statement: ast.Stmt_Id,
|
statement: ast.Stmt_Id,
|
||||||
index: u32,
|
index: u32,
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,7 @@ Type_Factory_Origin :: struct {
|
|||||||
Call_Resolution :: struct {
|
Call_Resolution :: struct {
|
||||||
expr: ast.Expr_Id,
|
expr: ast.Expr_Id,
|
||||||
ctx: []Comptime_Value,
|
ctx: []Comptime_Value,
|
||||||
inline_ctx: []Inline_Expansion,
|
expand_ctx: []Expand_Expansion,
|
||||||
mapping: []int,
|
mapping: []int,
|
||||||
comptime_values: []Comptime_Value,
|
comptime_values: []Comptime_Value,
|
||||||
runtime_types: []types.Type,
|
runtime_types: []types.Type,
|
||||||
@@ -235,7 +235,7 @@ Checker :: struct {
|
|||||||
static_bindings: [dynamic]Static_Binding,
|
static_bindings: [dynamic]Static_Binding,
|
||||||
comptime_keys: [dynamic]string,
|
comptime_keys: [dynamic]string,
|
||||||
comptime_static_values: [dynamic]Ct_Value_Id,
|
comptime_static_values: [dynamic]Ct_Value_Id,
|
||||||
inline_context: [dynamic]Inline_Expansion,
|
expand_context: [dynamic]Expand_Expansion,
|
||||||
type_factories: [dynamic]Type_Factory_Entry,
|
type_factories: [dynamic]Type_Factory_Entry,
|
||||||
generated_types: [dynamic]Generated_Type_Entry,
|
generated_types: [dynamic]Generated_Type_Entry,
|
||||||
type_factory_origins: [dynamic]Type_Factory_Origin,
|
type_factory_origins: [dynamic]Type_Factory_Origin,
|
||||||
@@ -549,7 +549,21 @@ persistent_field_value :: proc(checker: ^Checker, root: Ct_Value_Id, name: symbo
|
|||||||
}
|
}
|
||||||
|
|
||||||
build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span, expected: types.Type) -> hir.Expr_Id {
|
build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span, expected: types.Type) -> hir.Expr_Id {
|
||||||
|
if value.kind == .Void {
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Void, span=span, type=types.VOID,
|
||||||
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
if value.kind == .Integer {
|
if value.kind == .Integer {
|
||||||
|
if types.is_enum(value.type, &checker.module.types) {
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Integer, span=span, type=value.type, integer=i64(value.integer),
|
||||||
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
expr := ast.Expr{
|
expr := ast.Expr{
|
||||||
kind=.Integer, span=span, integer=u64(value.integer),
|
kind=.Integer, span=span, integer=u64(value.integer),
|
||||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
@@ -628,6 +642,28 @@ build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span
|
|||||||
return invalid_hir_expr(checker, span, id, expected)
|
return invalid_hir_expr(checker, span, id, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
push_static_integer_binding :: proc(checker: ^Checker, name: symbol.Id, value_type: types.Type, value: i128) -> int {
|
||||||
|
start := len(checker.static_bindings)
|
||||||
|
if symbol.is_valid(name) && name != checker.sink_symbol {
|
||||||
|
id := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=value_type, integer=value})
|
||||||
|
append(&checker.static_bindings, Static_Binding{name=name, type=value_type, value=id})
|
||||||
|
}
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
push_static_void_binding :: proc(checker: ^Checker, name: symbol.Id) -> int {
|
||||||
|
start := len(checker.static_bindings)
|
||||||
|
if symbol.is_valid(name) && name != checker.sink_symbol {
|
||||||
|
id := ct_add_value(&checker.static_state, Ct_Value{kind=.Void, type=types.VOID})
|
||||||
|
append(&checker.static_bindings, Static_Binding{name=name, type=types.VOID, value=id})
|
||||||
|
}
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
pop_static_bindings :: proc(checker: ^Checker, start: int) {
|
||||||
|
resize(&checker.static_bindings, start)
|
||||||
|
}
|
||||||
|
|
||||||
comptime_string_argument :: proc(
|
comptime_string_argument :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
id: ast.Expr_Id,
|
id: ast.Expr_Id,
|
||||||
@@ -856,6 +892,104 @@ build_type_builtin :: proc(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tag_result_type :: proc(checker: ^Checker, value: types.Type) -> (types.Type, bool) {
|
||||||
|
if !types.is_tagged_union(value, &checker.module.types) {
|
||||||
|
return types.INVALID, false
|
||||||
|
}
|
||||||
|
return types.union_tag_enum(value, &checker.module.types), true
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_member_name_from_value :: proc(checker: ^Checker, enum_type: types.Type, value: i128) -> (string, bool) {
|
||||||
|
if !types.is_enum(enum_type, &checker.module.types) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for member in types.enum_members_for(&checker.module.types, enum_type) {
|
||||||
|
if member.value == value {
|
||||||
|
return symbol_text(checker, symbol.Id(member.name)), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
build_tag_intrinsic :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
expr: ast.Expr,
|
||||||
|
locals: []Build_Local,
|
||||||
|
global_reads: ^[dynamic]hir.Global_Id,
|
||||||
|
calls: ^[dynamic]hir.Function_Id,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
) -> hir.Expr_Id {
|
||||||
|
if len(expr.args) != 1 {
|
||||||
|
id := source.addf(checker.diagnostics, expr.span, "tag! expects 1 argument, got %d", len(expr.args))
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
||||||
|
value_id, flow, comptime_ok := ct_eval_expr(&state, expr.args[0], types.INVALID, 0)
|
||||||
|
if comptime_ok && flow.kind == .Normal && value_id != INVALID_CT_VALUE && int(value_id) < len(state.values) {
|
||||||
|
value := state.values[value_id]
|
||||||
|
if tag_type, tagged := tag_result_type(checker, value.type); tagged && value.kind == .Struct &&
|
||||||
|
value.active >= 0 {
|
||||||
|
fields := types.fields_for(&checker.module.types, value.type)
|
||||||
|
if int(value.active) < len(fields) {
|
||||||
|
if member, found := find_enum_member(checker, tag_type, symbol.Id(fields[value.active].name)); found {
|
||||||
|
ct_state_destroy(&state)
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Integer, span=expr.span, type=tag_type, integer=i64(member.value),
|
||||||
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ct_state_destroy(&state)
|
||||||
|
value := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
if checker.module.exprs[value].kind == .Invalid {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
tag_type, ok := tag_result_type(checker, checker.module.exprs[value].type)
|
||||||
|
if !ok {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "tag! requires a tagged-union value")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Union_Tag, span=expr.span, type=tag_type, left=value,
|
||||||
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
build_tagname_intrinsic :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
expr: ast.Expr,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
) -> hir.Expr_Id {
|
||||||
|
if len(expr.args) != 1 {
|
||||||
|
id := source.addf(checker.diagnostics, expr.span, "tagname! expects 1 argument, got %d", len(expr.args))
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
||||||
|
defer ct_state_destroy(&state)
|
||||||
|
value_id, flow, ok := ct_eval_expr(&state, expr.args[0], types.INVALID, 0)
|
||||||
|
if !ok || flow.kind != .Normal || value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "tagname! requires a comptime-known enum value")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
value := state.values[value_id]
|
||||||
|
name, name_ok := enum_member_name_from_value(checker, value.type, value.integer)
|
||||||
|
if value.kind != .Integer || !name_ok {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "tagname! requires a comptime-known enum value")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
string_id := intern_comptime_string(checker, name)
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.String, span=expr.span, type=string_literal_type(checker, string_id), integer=i64(string_id),
|
||||||
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool {
|
is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool {
|
||||||
item, ok := types.node(&checker.module.types, value)
|
item, ok := types.node(&checker.module.types, value)
|
||||||
return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0
|
return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0
|
||||||
@@ -2554,7 +2688,7 @@ clone_comptime_values :: proc(values: []Comptime_Value, allocator: mem.Allocator
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
inline_expansions_equal :: proc(left, right: []Inline_Expansion) -> bool {
|
expand_expansions_equal :: proc(left, right: []Expand_Expansion) -> bool {
|
||||||
if len(left) != len(right) {
|
if len(left) != len(right) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -2574,7 +2708,7 @@ find_call_resolution :: proc(
|
|||||||
entry := checker.call_resolutions[index]
|
entry := checker.call_resolutions[index]
|
||||||
if entry.expr == expr &&
|
if entry.expr == expr &&
|
||||||
comptime_values_equal(entry.ctx, checker.current_comptime_values) &&
|
comptime_values_equal(entry.ctx, checker.current_comptime_values) &&
|
||||||
inline_expansions_equal(entry.inline_ctx, checker.inline_context[:]) {
|
expand_expansions_equal(entry.expand_ctx, checker.expand_context[:]) {
|
||||||
return index, true
|
return index, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2591,7 +2725,7 @@ store_call_resolution :: proc(
|
|||||||
entry := Call_Resolution{
|
entry := Call_Resolution{
|
||||||
expr=expr,
|
expr=expr,
|
||||||
ctx=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
ctx=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
||||||
inline_ctx=slice.clone(checker.inline_context[:], checker.allocator),
|
expand_ctx=slice.clone(checker.expand_context[:], checker.allocator),
|
||||||
mapping=slice.clone(mapping, checker.allocator),
|
mapping=slice.clone(mapping, checker.allocator),
|
||||||
comptime_values=clone_comptime_values(comptime_values, checker.allocator),
|
comptime_values=clone_comptime_values(comptime_values, checker.allocator),
|
||||||
runtime_types=slice.clone(runtime_types, checker.allocator),
|
runtime_types=slice.clone(runtime_types, checker.allocator),
|
||||||
@@ -2599,7 +2733,7 @@ store_call_resolution :: proc(
|
|||||||
if index, ok := find_call_resolution(checker, expr); ok {
|
if index, ok := find_call_resolution(checker, expr); ok {
|
||||||
previous := checker.call_resolutions[index]
|
previous := checker.call_resolutions[index]
|
||||||
delete(previous.ctx, checker.allocator)
|
delete(previous.ctx, checker.allocator)
|
||||||
delete(previous.inline_ctx, checker.allocator)
|
delete(previous.expand_ctx, checker.allocator)
|
||||||
delete(previous.mapping, checker.allocator)
|
delete(previous.mapping, checker.allocator)
|
||||||
delete(previous.comptime_values, checker.allocator)
|
delete(previous.comptime_values, checker.allocator)
|
||||||
delete(previous.runtime_types, checker.allocator)
|
delete(previous.runtime_types, checker.allocator)
|
||||||
@@ -4276,7 +4410,11 @@ infer_expr :: proc(
|
|||||||
}
|
}
|
||||||
expr := checker.ast_module.exprs[frame.expr]
|
expr := checker.ast_module.exprs[frame.expr]
|
||||||
if frame.stage == 0 {
|
if frame.stage == 0 {
|
||||||
constant := eval_constant(checker, frame.expr)
|
constant := Constant{}
|
||||||
|
_, static_name := current_static_binding(checker, expr.name)
|
||||||
|
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
||||||
|
constant = eval_constant(checker, frame.expr)
|
||||||
|
}
|
||||||
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
|
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
|
||||||
(constant.kind == .Value && !fits_i64(constant.value)) {
|
(constant.kind == .Value && !fits_i64(constant.value)) {
|
||||||
last = types.I64
|
last = types.I64
|
||||||
@@ -4502,6 +4640,21 @@ infer_expr :: proc(
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if is_intrinsic_call(checker, expr, "tag") {
|
||||||
|
if len(expr.args) == 1 {
|
||||||
|
value_type := infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types)
|
||||||
|
last, _ = tag_result_type(checker, value_type)
|
||||||
|
} else {
|
||||||
|
last = types.INVALID
|
||||||
|
}
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if is_intrinsic_call(checker, expr, "tagname") {
|
||||||
|
last = types.slice(&checker.module.types, types.U8, false)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if is_ptrcast_call(checker, expr) {
|
if is_ptrcast_call(checker, expr) {
|
||||||
if len(expr.args) != 2 {
|
if len(expr.args) != 2 {
|
||||||
last = types.INVALID
|
last = types.INVALID
|
||||||
@@ -5087,21 +5240,21 @@ infer_statements :: proc(
|
|||||||
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
}
|
}
|
||||||
case .For:
|
case .For:
|
||||||
if statement.inline {
|
if statement.expand {
|
||||||
bindings, inline_error := inline_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
||||||
if inline_error == .None {
|
if expand_error == .None {
|
||||||
for binding, inline_index in bindings {
|
for binding, expand_index in bindings {
|
||||||
binding_start := push_inline_binding(checker, binding, statement.index_name, inline_index, statement_id)
|
binding_start := push_expand_binding(checker, binding, statement.index_name, expand_index, statement_id)
|
||||||
iteration: [dynamic]ast.Stmt_Id
|
iteration: [dynamic]ast.Stmt_Id
|
||||||
iteration.allocator = checker.allocator
|
iteration.allocator = checker.allocator
|
||||||
control := flatten_inline_iteration(
|
control := flatten_expand_iteration(
|
||||||
checker, statement.body, pkg, file, &iteration, statement.label, nil,
|
checker, statement.body, pkg, file, &iteration, statement.label, nil,
|
||||||
)
|
)
|
||||||
if control != .Invalid {
|
if control != .Invalid {
|
||||||
infer_statements(checker, iteration[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, iteration[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
}
|
}
|
||||||
delete(iteration)
|
delete(iteration)
|
||||||
pop_inline_binding(checker, binding_start)
|
pop_expand_binding(checker, binding_start)
|
||||||
if control == .Break || control == .Invalid {
|
if control == .Break || control == .Invalid {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -5164,13 +5317,65 @@ infer_statements :: proc(
|
|||||||
// (e.g. `match get()`). Mirror the `.For`/unwrap-`.If` capture handling.
|
// (e.g. `match get()`). Mirror the `.For`/unwrap-`.If` capture handling.
|
||||||
subject_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
subject_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||||
is_tagged := types.is_tagged_union(subject_type, &checker.module.types)
|
is_tagged := types.is_tagged_union(subject_type, &checker.module.types)
|
||||||
|
is_enum_subject := types.is_enum(subject_type, &checker.module.types)
|
||||||
|
covered: [dynamic]symbol.Id
|
||||||
|
covered.allocator = checker.allocator
|
||||||
for arm_id in statement.body {
|
for arm_id in statement.body {
|
||||||
arm := checker.ast_module.statements[arm_id]
|
arm := checker.ast_module.statements[arm_id]
|
||||||
if arm.kind != .Match_Arm {
|
if arm.kind != .Match_Arm {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if arm.expand && (is_tagged || is_enum_subject) {
|
||||||
|
tag_type := types.union_tag_enum(subject_type, &checker.module.types) if is_tagged else subject_type
|
||||||
|
if is_tagged {
|
||||||
|
for field in types.fields_for(&checker.module.types, subject_type) {
|
||||||
|
name := symbol.Id(field.name)
|
||||||
|
if contains_name(covered[:], name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
member, found := find_enum_member(checker, tag_type, name)
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
capture_start := len(locals^)
|
||||||
|
static_start := len(checker.static_bindings)
|
||||||
|
if len(arm.captures) > 1 {
|
||||||
|
_ = push_static_integer_binding(checker, arm.captures[1], tag_type, member.value)
|
||||||
|
}
|
||||||
|
if len(arm.captures) > 0 && arm.captures[0] != checker.sink_symbol {
|
||||||
|
if types.is_void(field.type) && !arm.pointer_capture {
|
||||||
|
_ = push_static_void_binding(checker, arm.captures[0])
|
||||||
|
} else {
|
||||||
|
capture_type := field.type
|
||||||
|
if arm.pointer_capture {
|
||||||
|
capture_type = types.pointer(&checker.module.types, field.type, true, false)
|
||||||
|
}
|
||||||
|
append(locals, Infer_Local{name=arm.captures[0], type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
|
resize(locals, capture_start)
|
||||||
|
pop_static_bindings(checker, static_start)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for member in types.enum_members_for(&checker.module.types, subject_type) {
|
||||||
|
name := symbol.Id(member.name)
|
||||||
|
if contains_name(covered[:], name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
static_start := push_static_integer_binding(checker, arm.captures[0] if len(arm.captures) > 0 else symbol.INVALID, tag_type, member.value)
|
||||||
|
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
|
pop_static_bindings(checker, static_start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
for pattern in arm.patterns {
|
for pattern in arm.patterns {
|
||||||
_ = infer_expr(checker, pattern, locals^[:], pkg, file, demanded, local_types)
|
_ = infer_expr(checker, pattern, locals^[:], pkg, file, demanded, local_types)
|
||||||
|
pattern_expr := checker.ast_module.exprs[pattern]
|
||||||
|
if pattern_expr.kind == .Enum_Literal {
|
||||||
|
append(&covered, pattern_expr.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
capture_start := len(locals^)
|
capture_start := len(locals^)
|
||||||
if len(arm.captures) > 0 && is_tagged && len(arm.patterns) > 0 {
|
if len(arm.captures) > 0 && is_tagged && len(arm.patterns) > 0 {
|
||||||
@@ -5194,6 +5399,7 @@ infer_statements :: proc(
|
|||||||
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
resize(locals, capture_start)
|
resize(locals, capture_start)
|
||||||
}
|
}
|
||||||
|
delete(covered)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resize(locals, scope_start)
|
resize(locals, scope_start)
|
||||||
@@ -7592,7 +7798,11 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
expr := checker.ast_module.exprs[frame.expr]
|
expr := checker.ast_module.exprs[frame.expr]
|
||||||
if frame.stage == 0 {
|
if frame.stage == 0 {
|
||||||
constant := eval_constant(checker, frame.expr)
|
constant := Constant{}
|
||||||
|
_, static_name := current_static_binding(checker, expr.name)
|
||||||
|
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
||||||
|
constant = eval_constant(checker, frame.expr)
|
||||||
|
}
|
||||||
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero || constant.kind == .Non_Exact {
|
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero || constant.kind == .Non_Exact {
|
||||||
last = build_constant_expr(checker, expr, constant, frame.expected)
|
last = build_constant_expr(checker, expr, constant, frame.expected)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
@@ -7846,6 +8056,16 @@ build_expr :: proc(
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if is_intrinsic_call(checker, expr, "tag") {
|
||||||
|
last = build_tag_intrinsic(checker, expr, locals, global_reads, calls, pkg, file)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if is_intrinsic_call(checker, expr, "tagname") {
|
||||||
|
last = build_tagname_intrinsic(checker, expr, pkg, file)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if is_ptrcast_call(checker, expr) {
|
if is_ptrcast_call(checker, expr) {
|
||||||
if len(expr.args) != 2 {
|
if len(expr.args) != 2 {
|
||||||
id := source.addf(checker.diagnostics, expr.span, "ptrcast! expects 2 arguments, got %d", len(expr.args))
|
id := source.addf(checker.diagnostics, expr.span, "ptrcast! expects 2 arguments, got %d", len(expr.args))
|
||||||
@@ -8642,6 +8862,9 @@ specialization_match_body :: proc(
|
|||||||
return nil, {}, false, false
|
return nil, {}, false, false
|
||||||
}
|
}
|
||||||
arm := checker.ast_module.statements[selection.arm]
|
arm := checker.ast_module.statements[selection.arm]
|
||||||
|
if arm.expand {
|
||||||
|
return nil, {}, false, false
|
||||||
|
}
|
||||||
if arm.pointer_capture {
|
if arm.pointer_capture {
|
||||||
return nil, {}, false, false
|
return nil, {}, false, false
|
||||||
}
|
}
|
||||||
@@ -8656,21 +8879,21 @@ specialization_match_body :: proc(
|
|||||||
return arm.body, {}, false, true
|
return arm.body, {}, false, true
|
||||||
}
|
}
|
||||||
|
|
||||||
Inline_Binding_Error :: enum u8 {
|
Expand_Binding_Error :: enum u8 {
|
||||||
None,
|
None,
|
||||||
Invalid,
|
Invalid,
|
||||||
Quota,
|
Quota,
|
||||||
Diagnosed,
|
Diagnosed,
|
||||||
}
|
}
|
||||||
|
|
||||||
inline_field_bindings :: proc(
|
expand_field_bindings :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
expr: ast.Expr_Id,
|
expr: ast.Expr_Id,
|
||||||
capture: symbol.Id,
|
capture: symbol.Id,
|
||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
file: ast.File_Id,
|
file: ast.File_Id,
|
||||||
diagnose := false,
|
diagnose := false,
|
||||||
) -> ([]Static_Binding, Inline_Binding_Error) {
|
) -> ([]Static_Binding, Expand_Binding_Error) {
|
||||||
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=diagnose)
|
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=diagnose)
|
||||||
defer ct_state_destroy(&state)
|
defer ct_state_destroy(&state)
|
||||||
value_id, flow, ok := ct_eval_expr(&state, expr, types.INVALID, 0)
|
value_id, flow, ok := ct_eval_expr(&state, expr, types.INVALID, 0)
|
||||||
@@ -8680,7 +8903,7 @@ inline_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, "inline for cannot expand an undefined comptime value")
|
_ = ct_fail(&state, .Not_Comptime, checker.ast_module.exprs[expr].span, "expand for cannot expand 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
|
||||||
}
|
}
|
||||||
@@ -8748,7 +8971,7 @@ inline_field_bindings :: proc(
|
|||||||
return bindings, .None
|
return bindings, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
push_inline_binding :: proc(
|
push_expand_binding :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
binding: Static_Binding,
|
binding: Static_Binding,
|
||||||
index_name: symbol.Id,
|
index_name: symbol.Id,
|
||||||
@@ -8757,7 +8980,7 @@ push_inline_binding :: proc(
|
|||||||
) -> int {
|
) -> int {
|
||||||
start := len(checker.static_bindings)
|
start := len(checker.static_bindings)
|
||||||
append(&checker.static_bindings, binding)
|
append(&checker.static_bindings, binding)
|
||||||
append(&checker.inline_context, Inline_Expansion{statement=statement, index=u32(index)})
|
append(&checker.expand_context, Expand_Expansion{statement=statement, index=u32(index)})
|
||||||
if symbol.is_valid(index_name) {
|
if symbol.is_valid(index_name) {
|
||||||
value := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
value := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
||||||
append(&checker.static_bindings, Static_Binding{name=index_name, type=types.USIZE, value=value})
|
append(&checker.static_bindings, Static_Binding{name=index_name, type=types.USIZE, value=value})
|
||||||
@@ -8765,19 +8988,19 @@ push_inline_binding :: proc(
|
|||||||
return start
|
return start
|
||||||
}
|
}
|
||||||
|
|
||||||
pop_inline_binding :: proc(checker: ^Checker, start: int) {
|
pop_expand_binding :: proc(checker: ^Checker, start: int) {
|
||||||
resize(&checker.static_bindings, start)
|
resize(&checker.static_bindings, start)
|
||||||
_ = pop(&checker.inline_context)
|
_ = pop(&checker.expand_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
Inline_Control :: enum u8 {
|
Expand_Control :: enum u8 {
|
||||||
Normal,
|
Normal,
|
||||||
Break,
|
Break,
|
||||||
Continue,
|
Continue,
|
||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
inline_control_target :: proc(statement: ast.Stmt, target_label: symbol.Id, allow_unlabeled: bool) -> Inline_Control {
|
expand_control_target :: proc(statement: ast.Stmt, target_label: symbol.Id, allow_unlabeled: bool) -> Expand_Control {
|
||||||
if statement.kind != .Break && statement.kind != .Continue {
|
if statement.kind != .Break && statement.kind != .Continue {
|
||||||
return .Normal
|
return .Normal
|
||||||
}
|
}
|
||||||
@@ -8791,7 +9014,7 @@ inline_control_target :: proc(statement: ast.Stmt, target_label: symbol.Id, allo
|
|||||||
return .Break if statement.kind == .Break else .Continue
|
return .Break if statement.kind == .Break else .Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
contains_inline_control :: proc(
|
contains_expand_control :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
statements: []ast.Stmt_Id,
|
statements: []ast.Stmt_Id,
|
||||||
target_label: symbol.Id,
|
target_label: symbol.Id,
|
||||||
@@ -8799,32 +9022,32 @@ contains_inline_control :: proc(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
for statement_id in statements {
|
for statement_id in statements {
|
||||||
statement := checker.ast_module.statements[statement_id]
|
statement := checker.ast_module.statements[statement_id]
|
||||||
if inline_control_target(statement, target_label, allow_unlabeled) != .Normal {
|
if expand_control_target(statement, target_label, allow_unlabeled) != .Normal {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
#partial switch statement.kind {
|
#partial switch statement.kind {
|
||||||
case .Block:
|
case .Block:
|
||||||
if contains_inline_control(checker, statement.body, target_label, allow_unlabeled) {
|
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case .If:
|
case .If:
|
||||||
if contains_inline_control(checker, statement.body, target_label, allow_unlabeled) ||
|
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) ||
|
||||||
contains_inline_control(checker, statement.else_body, target_label, allow_unlabeled) {
|
contains_expand_control(checker, statement.else_body, target_label, allow_unlabeled) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case .Match, .Match_Arm:
|
case .Match, .Match_Arm:
|
||||||
if contains_inline_control(checker, statement.body, target_label, allow_unlabeled) {
|
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
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 inline loop and is therefore relevant here.
|
// name the surrounding expand loop and is therefore relevant here.
|
||||||
if contains_inline_control(checker, statement.body, target_label, false) {
|
if contains_expand_control(checker, statement.body, target_label, false) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case .Defer:
|
case .Defer:
|
||||||
if statement.update != ast.INVALID_STMT &&
|
if statement.update != ast.INVALID_STMT &&
|
||||||
contains_inline_control(checker, []ast.Stmt_Id{statement.update}, target_label, false) {
|
contains_expand_control(checker, []ast.Stmt_Id{statement.update}, target_label, false) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8861,7 +9084,7 @@ clone_statement_body :: proc(checker: ^Checker, statement: ast.Stmt, body: []ast
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
append_inline_block :: proc(checker: ^Checker, span: source.Span, body: []ast.Stmt_Id, out: ^[dynamic]ast.Stmt_Id) {
|
append_expand_block :: proc(checker: ^Checker, span: source.Span, body: []ast.Stmt_Id, out: ^[dynamic]ast.Stmt_Id) {
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -8891,7 +9114,7 @@ same_stmt_ids :: proc(left, right: []ast.Stmt_Id) -> bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
flatten_inline_iteration :: proc(
|
flatten_expand_iteration :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
statements: []ast.Stmt_Id,
|
statements: []ast.Stmt_Id,
|
||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
@@ -8899,10 +9122,10 @@ flatten_inline_iteration :: proc(
|
|||||||
out: ^[dynamic]ast.Stmt_Id,
|
out: ^[dynamic]ast.Stmt_Id,
|
||||||
target_label: symbol.Id,
|
target_label: symbol.Id,
|
||||||
diagnostic: ^source.Diagnostic_Id,
|
diagnostic: ^source.Diagnostic_Id,
|
||||||
) -> Inline_Control {
|
) -> Expand_Control {
|
||||||
for statement_id in statements {
|
for statement_id in statements {
|
||||||
statement := checker.ast_module.statements[statement_id]
|
statement := checker.ast_module.statements[statement_id]
|
||||||
if control := inline_control_target(statement, target_label, true); control != .Normal {
|
if control := expand_control_target(statement, target_label, true); control != .Normal {
|
||||||
return control
|
return control
|
||||||
}
|
}
|
||||||
if statement.kind == .If && len(statement.captures) == 0 {
|
if statement.kind == .If && len(statement.captures) == 0 {
|
||||||
@@ -8910,20 +9133,20 @@ flatten_inline_iteration :: proc(
|
|||||||
selected_body := statement.body if selected else statement.else_body
|
selected_body := statement.body if selected else statement.else_body
|
||||||
branch: [dynamic]ast.Stmt_Id
|
branch: [dynamic]ast.Stmt_Id
|
||||||
branch.allocator = checker.allocator
|
branch.allocator = checker.allocator
|
||||||
flow := flatten_inline_iteration(checker, selected_body, pkg, file, &branch, target_label, diagnostic)
|
flow := flatten_expand_iteration(checker, selected_body, pkg, file, &branch, target_label, diagnostic)
|
||||||
append_inline_block(checker, statement.span, branch[:], out)
|
append_expand_block(checker, statement.span, branch[:], out)
|
||||||
delete(branch)
|
delete(branch)
|
||||||
if flow != .Normal {
|
if flow != .Normal {
|
||||||
return flow
|
return flow
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if contains_inline_control(checker, statement.body, target_label, true) ||
|
if contains_expand_control(checker, statement.body, target_label, true) ||
|
||||||
contains_inline_control(checker, statement.else_body, target_label, true) {
|
contains_expand_control(checker, statement.else_body, target_label, true) {
|
||||||
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 inline loop must be compile-time-resolvable",
|
"break or continue targeting an expand loop must be compile-time-resolvable",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return .Invalid
|
return .Invalid
|
||||||
@@ -8933,7 +9156,7 @@ flatten_inline_iteration :: proc(
|
|||||||
if selected_body, _, _, comptime_ok := specialization_match_body(checker, statement, pkg, file); comptime_ok {
|
if selected_body, _, _, comptime_ok := specialization_match_body(checker, statement, pkg, file); comptime_ok {
|
||||||
selected: [dynamic]ast.Stmt_Id
|
selected: [dynamic]ast.Stmt_Id
|
||||||
selected.allocator = checker.allocator
|
selected.allocator = checker.allocator
|
||||||
flow := flatten_inline_iteration(checker, selected_body, pkg, file, &selected, target_label, diagnostic)
|
flow := flatten_expand_iteration(checker, selected_body, pkg, file, &selected, target_label, diagnostic)
|
||||||
if flow != .Normal {
|
if flow != .Normal {
|
||||||
arm_index := -1
|
arm_index := -1
|
||||||
for arm_id, index in statement.body {
|
for arm_id, index in statement.body {
|
||||||
@@ -8958,11 +9181,11 @@ flatten_inline_iteration :: proc(
|
|||||||
append(out, statement_id)
|
append(out, statement_id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if contains_inline_control(checker, statement.body, target_label, true) {
|
if contains_expand_control(checker, statement.body, target_label, true) {
|
||||||
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 inline loop must be compile-time-resolvable",
|
"break or continue targeting an expand loop must be compile-time-resolvable",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return .Invalid
|
return .Invalid
|
||||||
@@ -8971,11 +9194,11 @@ flatten_inline_iteration :: proc(
|
|||||||
if statement.kind == .Block {
|
if statement.kind == .Block {
|
||||||
block: [dynamic]ast.Stmt_Id
|
block: [dynamic]ast.Stmt_Id
|
||||||
block.allocator = checker.allocator
|
block.allocator = checker.allocator
|
||||||
flow := flatten_inline_iteration(checker, statement.body, pkg, file, &block, target_label, diagnostic)
|
flow := flatten_expand_iteration(checker, statement.body, pkg, file, &block, target_label, diagnostic)
|
||||||
if flow == .Normal {
|
if flow == .Normal {
|
||||||
append(out, statement_id)
|
append(out, statement_id)
|
||||||
} else {
|
} else {
|
||||||
append_inline_block(checker, statement.span, block[:], out)
|
append_expand_block(checker, statement.span, block[:], out)
|
||||||
}
|
}
|
||||||
delete(block)
|
delete(block)
|
||||||
if flow != .Normal {
|
if flow != .Normal {
|
||||||
@@ -8984,11 +9207,11 @@ flatten_inline_iteration :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (statement.kind == .For || statement.kind == .While || statement.kind == .Defer) &&
|
if (statement.kind == .For || statement.kind == .While || statement.kind == .Defer) &&
|
||||||
contains_inline_control(checker, []ast.Stmt_Id{statement_id}, target_label, false) {
|
contains_expand_control(checker, []ast.Stmt_Id{statement_id}, target_label, false) {
|
||||||
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 inline loop must be compile-time-resolvable",
|
"break or continue targeting an expand loop must be compile-time-resolvable",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return .Invalid
|
return .Invalid
|
||||||
@@ -9821,19 +10044,19 @@ build_block :: proc(
|
|||||||
})
|
})
|
||||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||||
case .For:
|
case .For:
|
||||||
if statement.inline {
|
if statement.expand {
|
||||||
if statement.pointer_capture {
|
if statement.pointer_capture {
|
||||||
id := source.add(checker.diagnostics, statement.span, "inline for does not support pointer captures")
|
id := source.add(checker.diagnostics, statement.span, "expand 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
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bindings, inline_error := inline_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 inline_error != .None && inline_error != .Diagnosed {
|
if expand_error != .None && expand_error != .Diagnosed {
|
||||||
message := "inline for requires a comptime tuple, fixed array, range, slice, or reflection value"
|
message := "expand for requires a comptime tuple, fixed array, range, slice, or reflection value"
|
||||||
if inline_error == .Quota {
|
if expand_error == .Quota {
|
||||||
message = "inline for expansion exceeds the compile-time evaluation quota"
|
message = "expand for expansion exceeds the compile-time evaluation quota"
|
||||||
}
|
}
|
||||||
id := source.add(
|
id := source.add(
|
||||||
checker.diagnostics, statement.span,
|
checker.diagnostics, statement.span,
|
||||||
@@ -9842,13 +10065,13 @@ build_block :: proc(
|
|||||||
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
|
||||||
} else if inline_error == .None {
|
} else if expand_error == .None {
|
||||||
for binding, inline_index in bindings {
|
for binding, expand_index in bindings {
|
||||||
binding_start := push_inline_binding(checker, binding, statement.index_name, inline_index, statement_id)
|
binding_start := push_expand_binding(checker, binding, statement.index_name, expand_index, statement_id)
|
||||||
iteration: [dynamic]ast.Stmt_Id
|
iteration: [dynamic]ast.Stmt_Id
|
||||||
iteration.allocator = checker.allocator
|
iteration.allocator = checker.allocator
|
||||||
diagnostic := source.INVALID_DIAGNOSTIC
|
diagnostic := source.INVALID_DIAGNOSTIC
|
||||||
control := flatten_inline_iteration(
|
control := flatten_expand_iteration(
|
||||||
checker, statement.body, ctx.pkg, ctx.file, &iteration, statement.label, &diagnostic,
|
checker, statement.body, ctx.pkg, ctx.file, &iteration, statement.label, &diagnostic,
|
||||||
)
|
)
|
||||||
if control == .Invalid {
|
if control == .Invalid {
|
||||||
@@ -9863,7 +10086,7 @@ build_block :: proc(
|
|||||||
delete(expanded, checker.allocator)
|
delete(expanded, checker.allocator)
|
||||||
}
|
}
|
||||||
delete(iteration)
|
delete(iteration)
|
||||||
pop_inline_binding(checker, binding_start)
|
pop_expand_binding(checker, binding_start)
|
||||||
if control == .Break || control == .Invalid {
|
if control == .Break || control == .Invalid {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -10846,6 +11069,7 @@ emit_match :: proc(
|
|||||||
covered.allocator = checker.allocator
|
covered.allocator = checker.allocator
|
||||||
defer delete(covered)
|
defer delete(covered)
|
||||||
has_else := false
|
has_else := false
|
||||||
|
has_expand := false
|
||||||
|
|
||||||
for arm_id in statement.body {
|
for arm_id in statement.body {
|
||||||
arm := checker.ast_module.statements[arm_id]
|
arm := checker.ast_module.statements[arm_id]
|
||||||
@@ -10853,10 +11077,107 @@ emit_match :: proc(
|
|||||||
ok = false
|
ok = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if has_else {
|
if has_else || has_expand {
|
||||||
source.add(checker.diagnostics, arm.span, "arms after 'else' are unreachable")
|
message := "arms after 'else' are unreachable" if has_else else "arms after 'expand' are unreachable"
|
||||||
|
source.add(checker.diagnostics, arm.span, message)
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
|
if arm.expand {
|
||||||
|
if !is_tagged && !is_enum_subject {
|
||||||
|
source.add(checker.diagnostics, arm.span, "'expand' requires an enum or tagged-union match subject")
|
||||||
|
ok = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expected_captures := 1 if is_enum_subject else 2
|
||||||
|
if len(arm.captures) == 0 || len(arm.captures) > expected_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)
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
if is_enum_subject && arm.pointer_capture {
|
||||||
|
source.add(checker.diagnostics, arm.span, "enum expansion does not support pointer captures")
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
remaining := 0
|
||||||
|
member_enum := tag_enum if is_tagged else subject_type
|
||||||
|
if is_tagged {
|
||||||
|
for field, field_index in types.fields_for(store, subject_type) {
|
||||||
|
name := symbol.Id(field.name)
|
||||||
|
if contains_name(covered[:], name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
member, found := find_enum_member(checker, member_enum, name)
|
||||||
|
if !found {
|
||||||
|
ok = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remaining += 1
|
||||||
|
append(&covered, name)
|
||||||
|
member_expr := enum_member_hir(checker, member_enum, name, arm.span)
|
||||||
|
condition := add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Eq, span=arm.span, type=types.BOOL,
|
||||||
|
left=slot_read(checker, key_local, key_type, arm.span), right=member_expr,
|
||||||
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
static_start := len(checker.static_bindings)
|
||||||
|
if len(arm.captures) > 1 {
|
||||||
|
_ = push_static_integer_binding(checker, arm.captures[1], member_enum, member.value)
|
||||||
|
}
|
||||||
|
body_arm := arm
|
||||||
|
if types.is_void(field.type) && !arm.pointer_capture {
|
||||||
|
if len(arm.captures) > 0 {
|
||||||
|
_ = push_static_void_binding(checker, arm.captures[0])
|
||||||
|
}
|
||||||
|
body_arm.captures = nil
|
||||||
|
}
|
||||||
|
arm_body, body_ok := build_match_arm_body(
|
||||||
|
ctx, body_arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable,
|
||||||
|
field_index, field.type, as_value, slot, slot_type, span,
|
||||||
|
)
|
||||||
|
pop_static_bindings(checker, static_start)
|
||||||
|
ok = body_ok && ok
|
||||||
|
append(&built, Match_Built_Arm{condition=condition, body=arm_body})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for member in types.enum_members_for(store, subject_type) {
|
||||||
|
name := symbol.Id(member.name)
|
||||||
|
if contains_name(covered[:], name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remaining += 1
|
||||||
|
append(&covered, name)
|
||||||
|
member_expr := enum_member_hir(checker, subject_type, name, arm.span)
|
||||||
|
condition := add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Eq, span=arm.span, type=types.BOOL,
|
||||||
|
left=slot_read(checker, key_local, key_type, arm.span), right=member_expr,
|
||||||
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
static_start := push_static_integer_binding(
|
||||||
|
checker, arm.captures[0] if len(arm.captures) > 0 else symbol.INVALID,
|
||||||
|
subject_type, member.value,
|
||||||
|
)
|
||||||
|
body_arm := arm
|
||||||
|
body_arm.captures = nil
|
||||||
|
arm_body, body_ok := build_match_arm_body(
|
||||||
|
ctx, body_arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable,
|
||||||
|
-1, types.INVALID, as_value, slot, slot_type, span,
|
||||||
|
)
|
||||||
|
pop_static_bindings(checker, static_start)
|
||||||
|
ok = body_ok && ok
|
||||||
|
append(&built, Match_Built_Arm{condition=condition, body=arm_body})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if remaining == 0 {
|
||||||
|
source.add(checker.diagnostics, arm.span, "redundant 'expand': the 'match' already covers every variant")
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
has_expand = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
is_else := len(arm.patterns) == 0
|
is_else := len(arm.patterns) == 0
|
||||||
condition := hir.INVALID_EXPR
|
condition := hir.INVALID_EXPR
|
||||||
field_index := -1
|
field_index := -1
|
||||||
@@ -11214,7 +11535,20 @@ build_value_match :: proc(
|
|||||||
}
|
}
|
||||||
subtree: [dynamic]hir.Stmt_Id
|
subtree: [dynamic]hir.Stmt_Id
|
||||||
subtree.allocator = checker.allocator
|
subtree.allocator = checker.allocator
|
||||||
ok := emit_match(ctx, &subtree, statement, true, &slot, &slot_type)
|
ok := false
|
||||||
|
if selected_body, capture, has_capture, comptime_ok := specialization_match_body(
|
||||||
|
checker, statement, ctx.pkg, ctx.file,
|
||||||
|
); comptime_ok {
|
||||||
|
if has_capture {
|
||||||
|
append(&checker.static_bindings, capture)
|
||||||
|
}
|
||||||
|
ok = build_value_arm(ctx, &subtree, selected_body, &slot, &slot_type, span)
|
||||||
|
if has_capture {
|
||||||
|
_ = pop(&checker.static_bindings)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = emit_match(ctx, &subtree, statement, true, &slot, &slot_type)
|
||||||
|
}
|
||||||
if !ok || slot == hir.INVALID_LOCAL {
|
if !ok || slot == hir.INVALID_LOCAL {
|
||||||
for s in subtree {
|
for s in subtree {
|
||||||
append(body, s)
|
append(body, s)
|
||||||
@@ -12391,7 +12725,7 @@ check :: proc(
|
|||||||
checker.static_bindings.allocator = allocator
|
checker.static_bindings.allocator = allocator
|
||||||
checker.comptime_keys.allocator = allocator
|
checker.comptime_keys.allocator = allocator
|
||||||
checker.comptime_static_values.allocator = allocator
|
checker.comptime_static_values.allocator = allocator
|
||||||
checker.inline_context.allocator = allocator
|
checker.expand_context.allocator = allocator
|
||||||
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
|
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
|
||||||
build_symbol_indexes(&checker)
|
build_symbol_indexes(&checker)
|
||||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
@@ -12467,7 +12801,7 @@ check :: proc(
|
|||||||
}
|
}
|
||||||
for resolution in checker.call_resolutions {
|
for resolution in checker.call_resolutions {
|
||||||
delete(resolution.ctx, allocator)
|
delete(resolution.ctx, allocator)
|
||||||
delete(resolution.inline_ctx, allocator)
|
delete(resolution.expand_ctx, allocator)
|
||||||
delete(resolution.mapping, allocator)
|
delete(resolution.mapping, allocator)
|
||||||
delete(resolution.comptime_values, allocator)
|
delete(resolution.comptime_values, allocator)
|
||||||
delete(resolution.runtime_types, allocator)
|
delete(resolution.runtime_types, allocator)
|
||||||
@@ -12483,7 +12817,7 @@ check :: proc(
|
|||||||
}
|
}
|
||||||
delete(checker.comptime_keys)
|
delete(checker.comptime_keys)
|
||||||
delete(checker.comptime_static_values)
|
delete(checker.comptime_static_values)
|
||||||
delete(checker.inline_context)
|
delete(checker.expand_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
for function, index in ast_module.functions {
|
||||||
|
|||||||
@@ -2515,6 +2515,50 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
|||||||
}
|
}
|
||||||
return ct_typeinfo_value(state, target, expr.span)
|
return ct_typeinfo_value(state, target, expr.span)
|
||||||
}
|
}
|
||||||
|
if is_intrinsic_call(checker, expr, "tag") {
|
||||||
|
if len(expr.args) != 1 {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "tag! expects 1 argument, got %d", len(expr.args))
|
||||||
|
}
|
||||||
|
value_id, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
||||||
|
if !ok || flow.kind != .Normal {
|
||||||
|
return INVALID_CT_VALUE, flow, ok
|
||||||
|
}
|
||||||
|
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||||
|
}
|
||||||
|
value := state.values[value_id]
|
||||||
|
tag_type, tag_ok := tag_result_type(checker, value.type)
|
||||||
|
if !tag_ok || value.kind != .Struct || value.active < 0 {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "tag! requires a tagged-union value")
|
||||||
|
}
|
||||||
|
fields := types.fields_for(&checker.module.types, value.type)
|
||||||
|
if int(value.active) >= len(fields) {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||||
|
}
|
||||||
|
member, found := find_enum_member(checker, tag_type, symbol.Id(fields[value.active].name))
|
||||||
|
if !found {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||||
|
}
|
||||||
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=tag_type, integer=member.value}), ct_flow(.Normal), true
|
||||||
|
}
|
||||||
|
if is_intrinsic_call(checker, expr, "tagname") {
|
||||||
|
if len(expr.args) != 1 {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "tagname! expects 1 argument, got %d", len(expr.args))
|
||||||
|
}
|
||||||
|
value_id, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
||||||
|
if !ok || flow.kind != .Normal {
|
||||||
|
return INVALID_CT_VALUE, flow, ok
|
||||||
|
}
|
||||||
|
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||||
|
}
|
||||||
|
value := state.values[value_id]
|
||||||
|
name, found := enum_member_name_from_value(checker, value.type, value.integer)
|
||||||
|
if value.kind != .Integer || !found {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "tagname! requires a comptime-known enum value")
|
||||||
|
}
|
||||||
|
return ct_reflection_string(state, name), ct_flow(.Normal), true
|
||||||
|
}
|
||||||
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
||||||
if len(expr.args) != 1 {
|
if len(expr.args) != 1 {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
||||||
@@ -3660,6 +3704,7 @@ ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool,
|
|||||||
Ct_Match_Selection :: struct {
|
Ct_Match_Selection :: struct {
|
||||||
arm: ast.Stmt_Id,
|
arm: ast.Stmt_Id,
|
||||||
payload: Ct_Value_Id,
|
payload: Ct_Value_Id,
|
||||||
|
tag: Ct_Value_Id,
|
||||||
payload_field: int,
|
payload_field: int,
|
||||||
payload_type: types.Type,
|
payload_type: types.Type,
|
||||||
}
|
}
|
||||||
@@ -3684,9 +3729,40 @@ ct_select_match_arm :: proc(
|
|||||||
selection := Ct_Match_Selection{
|
selection := Ct_Match_Selection{
|
||||||
arm=arm_id,
|
arm=arm_id,
|
||||||
payload=INVALID_CT_VALUE,
|
payload=INVALID_CT_VALUE,
|
||||||
|
tag=INVALID_CT_VALUE,
|
||||||
payload_field=-1,
|
payload_field=-1,
|
||||||
payload_type=types.INVALID,
|
payload_type=types.INVALID,
|
||||||
}
|
}
|
||||||
|
if arm.expand {
|
||||||
|
if subject_value.kind == .Struct && types.is_tagged_union(subject_value.type, &checker.module.types) {
|
||||||
|
fields := types.fields_for(&checker.module.types, subject_value.type)
|
||||||
|
if subject_value.active < 0 || int(subject_value.active) >= len(fields) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
field_index := int(subject_value.active)
|
||||||
|
field := fields[field_index]
|
||||||
|
selection.payload_field = field_index
|
||||||
|
selection.payload_type = field.type
|
||||||
|
children := ct_child_slice(state, subject_value)
|
||||||
|
if len(children) > 0 {
|
||||||
|
selection.payload = children[0]
|
||||||
|
} else if types.is_void(field.type) {
|
||||||
|
selection.payload = ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
||||||
|
}
|
||||||
|
tag_type := types.union_tag_enum(subject_value.type, &checker.module.types)
|
||||||
|
member, found := find_enum_member(checker, tag_type, symbol.Id(field.name))
|
||||||
|
if !found {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
selection.tag = ct_add_value(state, Ct_Value{kind=.Integer, type=tag_type, integer=member.value})
|
||||||
|
return selection, true
|
||||||
|
}
|
||||||
|
if subject_value.kind == .Integer && types.is_enum(subject_value.type, &checker.module.types) {
|
||||||
|
selection.tag = subject
|
||||||
|
return selection, true
|
||||||
|
}
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
if !matched {
|
if !matched {
|
||||||
for pattern_id in arm.patterns {
|
for pattern_id in arm.patterns {
|
||||||
pattern := checker.ast_module.exprs[pattern_id]
|
pattern := checker.ast_module.exprs[pattern_id]
|
||||||
@@ -3765,7 +3841,11 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
|||||||
if selected {
|
if selected {
|
||||||
arm := checker.ast_module.statements[selection.arm]
|
arm := checker.ast_module.statements[selection.arm]
|
||||||
scope_start := len(state.bindings)
|
scope_start := len(state.bindings)
|
||||||
if len(arm.captures) > 0 && selection.payload != INVALID_CT_VALUE {
|
if arm.expand && types.is_enum(state.values[subject].type, &checker.module.types) {
|
||||||
|
if len(arm.captures) > 0 && arm.captures[0] != checker.sink_symbol {
|
||||||
|
ct_bind_value(state, arm.captures[0], state.values[subject].type, subject, false)
|
||||||
|
}
|
||||||
|
} else if len(arm.captures) > 0 && selection.payload != INVALID_CT_VALUE {
|
||||||
capture := arm.captures[0]
|
capture := arm.captures[0]
|
||||||
if arm.pointer_capture {
|
if arm.pointer_capture {
|
||||||
if subject_place == INVALID_CT_PLACE || selection.payload_field < 0 || !types.is_valid(selection.payload_type) {
|
if subject_place == INVALID_CT_PLACE || selection.payload_field < 0 || !types.is_valid(selection.payload_type) {
|
||||||
@@ -3785,6 +3865,10 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
|||||||
ct_bind_value(state, capture, state.values[selection.payload].type, selection.payload, false)
|
ct_bind_value(state, capture, state.values[selection.payload].type, selection.payload, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if arm.expand && len(arm.captures) > 1 && arm.captures[1] != checker.sink_symbol &&
|
||||||
|
selection.tag != INVALID_CT_VALUE {
|
||||||
|
ct_bind_value(state, arm.captures[1], state.values[selection.tag].type, selection.tag, false)
|
||||||
|
}
|
||||||
if yield_returns && len(arm.body) == 1 && checker.ast_module.statements[arm.body[0]].kind == .Expression {
|
if yield_returns && len(arm.body) == 1 && checker.ast_module.statements[arm.body[0]].kind == .Expression {
|
||||||
expr_stmt := checker.ast_module.statements[arm.body[0]]
|
expr_stmt := checker.ast_module.statements[arm.body[0]]
|
||||||
value, expr_flow, expr_ok := ct_eval_expr(state, expr_stmt.expr, types.INVALID, depth+1)
|
value, expr_flow, expr_ok := ct_eval_expr(state, expr_stmt.expr, types.INVALID, depth+1)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ Linkage :: enum u8 {
|
|||||||
|
|
||||||
Expr_Kind :: enum u8 {
|
Expr_Kind :: enum u8 {
|
||||||
Invalid,
|
Invalid,
|
||||||
|
Void,
|
||||||
Integer,
|
Integer,
|
||||||
Float,
|
Float,
|
||||||
String,
|
String,
|
||||||
|
|||||||
@@ -37,6 +37,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 "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
|
||||||
|
|||||||
@@ -1103,7 +1103,9 @@ emit_instruction_stream :: proc(
|
|||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
" %%v%d = getelementptr %s, ptr %%v%d, i64 0\n",
|
" %%v%d = getelementptr %s, ptr %%v%d, i64 0\n",
|
||||||
instruction_index, llvm_type(child, &emitter.module.types), instruction.a,
|
instruction_index,
|
||||||
|
"i8" if types.is_void(child) else llvm_type(child, &emitter.module.types),
|
||||||
|
instruction.a,
|
||||||
)
|
)
|
||||||
case .Alloca:
|
case .Alloca:
|
||||||
if !types.is_runtime_value(instruction.type, &emitter.module.types) {
|
if !types.is_runtime_value(instruction.type, &emitter.module.types) {
|
||||||
|
|||||||
@@ -659,6 +659,13 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
case .Invalid:
|
case .Invalid:
|
||||||
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
|
case .Void:
|
||||||
|
last = append_instruction(state, ir.Instruction{
|
||||||
|
op=.Const, span=expr.span, type=types.VOID,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
_ = pop(&stack)
|
||||||
case .Integer, .Float, .Bool:
|
case .Integer, .Float, .Bool:
|
||||||
last = append_instruction(state, ir.Instruction{
|
last = append_instruction(state, ir.Instruction{
|
||||||
op=.Const, span=expr.span, type=expr.type, integer=expr.integer,
|
op=.Const, span=expr.span, type=expr.type, integer=expr.integer,
|
||||||
|
|||||||
@@ -1680,7 +1680,15 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
peek(parser).kind == .Keyword_For {
|
peek(parser).kind == .Keyword_For {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
id := parse_for(parser)
|
id := parse_for(parser)
|
||||||
parser.module.statements[id].inline = true
|
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)
|
||||||
|
id := parse_for(parser)
|
||||||
|
parser.module.statements[id].expand = true
|
||||||
parser.module.statements[id].span = span_from(start.span, parser.module.statements[id].span)
|
parser.module.statements[id].span = span_from(start.span, parser.module.statements[id].span)
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
@@ -2104,10 +2112,8 @@ parse_arm_body :: proc(parser: ^Parser) -> []ast.Stmt_Id {
|
|||||||
return single
|
return single
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse_match_arm parses one `<pattern,...> [|[@]capture|]: <body>` arm (or
|
// parse_match_arm parses one `<pattern,...> [|[@]capture|]: <body>`, `else: <body>`,
|
||||||
// `else: <body>`). `patterns` is empty for `else`, one expr for a single pattern, or
|
// or `expand |[@]value[, tag]|: <body>` arm.
|
||||||
// several for a multi-pattern arm; `captures` holds the optional 0-or-1 payload capture
|
|
||||||
// name with `pointer_capture` set for the `|@cap|` form (validated in the checker).
|
|
||||||
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
|
||||||
@@ -2115,7 +2121,43 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
captures: [dynamic]symbol.Id
|
captures: [dynamic]symbol.Id
|
||||||
captures.allocator = parser.module.allocator
|
captures.allocator = parser.module.allocator
|
||||||
pointer_capture := false
|
pointer_capture := false
|
||||||
if _, is_else := allow(parser, .Keyword_Else); !is_else {
|
expand := false
|
||||||
|
if _, is_expand := allow(parser, .Keyword_Expand); is_expand {
|
||||||
|
expand = true
|
||||||
|
if _, ok := allow(parser, .Pipe); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '|' before expand captures")
|
||||||
|
} else {
|
||||||
|
if _, at_ok := allow(parser, .At); at_ok {
|
||||||
|
pointer_capture = true
|
||||||
|
}
|
||||||
|
name_tok := current(parser)
|
||||||
|
if name_tok.kind == .Identifier || name_tok.kind == .Underscore {
|
||||||
|
advance(parser)
|
||||||
|
append(&captures, name_tok.symbol)
|
||||||
|
} else {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected an expand value capture")
|
||||||
|
}
|
||||||
|
if _, comma_ok := allow(parser, .Comma); comma_ok {
|
||||||
|
tag_tok := current(parser)
|
||||||
|
if tag_tok.kind == .Identifier || tag_tok.kind == .Underscore {
|
||||||
|
advance(parser)
|
||||||
|
append(&captures, tag_tok.symbol)
|
||||||
|
} else {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected an expand tag capture")
|
||||||
|
}
|
||||||
|
if _, extra := allow(parser, .Comma); extra {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "'expand' accepts at most two captures")
|
||||||
|
for current(parser).kind != .Pipe && current(parser).kind != .Colon &&
|
||||||
|
current(parser).kind != .Newline && current(parser).kind != .Eof {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, close_ok := allow(parser, .Pipe); !close_ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '|' to close expand captures")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if _, is_else := allow(parser, .Keyword_Else); !is_else {
|
||||||
saved := parser.no_struct_literal
|
saved := parser.no_struct_literal
|
||||||
parser.no_struct_literal = true
|
parser.no_struct_literal = true
|
||||||
append(&patterns, parse_expression(parser))
|
append(&patterns, parse_expression(parser))
|
||||||
@@ -2155,6 +2197,7 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
patterns=patterns[:],
|
patterns=patterns[:],
|
||||||
captures=captures[:],
|
captures=captures[:],
|
||||||
pointer_capture=pointer_capture,
|
pointer_capture=pointer_capture,
|
||||||
|
expand=expand,
|
||||||
body=body,
|
body=body,
|
||||||
target=ast.INVALID_EXPR,
|
target=ast.INVALID_EXPR,
|
||||||
update=ast.INVALID_STMT,
|
update=ast.INVALID_STMT,
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_If,
|
Keyword_If,
|
||||||
Keyword_While,
|
Keyword_While,
|
||||||
Keyword_For,
|
Keyword_For,
|
||||||
|
Keyword_Expand,
|
||||||
Keyword_Break,
|
Keyword_Break,
|
||||||
Keyword_Continue,
|
Keyword_Continue,
|
||||||
Keyword_Defer,
|
Keyword_Defer,
|
||||||
|
|||||||
+111
-15
@@ -2404,7 +2404,7 @@ main func(init process.Init) void { _ = init }
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: proc(t: ^testing.T) {
|
milestone_37_tuples_reflection_expand_for_and_debug_print_compile_and_run :: proc(t: ^testing.T) {
|
||||||
sources := source.init_store()
|
sources := source.init_store()
|
||||||
defer source.destroy_store(&sources)
|
defer source.destroy_store(&sources)
|
||||||
diagnostics := source.init_store_diagnostics(&sources)
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
@@ -2445,6 +2445,102 @@ milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: pro
|
|||||||
testing.expect_value(t, string(stderr), "hello!\ntuple=40/bro, limits=-9223372036854775808/18446744073709551615")
|
testing.expect_value(t, string(stderr), "hello!\ntuple=40/bro, limits=-9223372036854775808/18446744073709551615")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
expanded_matches_and_tag_intrinsics_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-expand"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package(
|
||||||
|
"examples/programs/expand", output, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||||
|
)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state, stdout, stderr, _ := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{output}}, context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
testing.expect_value(t, string(stdout), "")
|
||||||
|
testing.expect_value(t, string(stderr), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
expanded_match_and_tag_diagnostics :: proc(t: ^testing.T) {
|
||||||
|
text := `E :: enum { a, b }
|
||||||
|
Plain :: union { value i32 }
|
||||||
|
|
||||||
|
main func() void {
|
||||||
|
inline for {1} |value| { _ = value }
|
||||||
|
|
||||||
|
n i32 = 1
|
||||||
|
match n { expand |value|: _ = value }
|
||||||
|
|
||||||
|
e E = .a
|
||||||
|
match e { expand |value, tag|: _ = value }
|
||||||
|
match e {
|
||||||
|
.a, .b: {}
|
||||||
|
expand |value|: _ = value
|
||||||
|
}
|
||||||
|
match e {
|
||||||
|
expand |value|: _ = value
|
||||||
|
.b: {}
|
||||||
|
}
|
||||||
|
match e {
|
||||||
|
else: {}
|
||||||
|
expand |value|: _ = value
|
||||||
|
}
|
||||||
|
|
||||||
|
u Plain = Plain{value = 1}
|
||||||
|
_ = tag!(u)
|
||||||
|
_ = tagname!(e)
|
||||||
|
|
||||||
|
match e { expand ||: {} }
|
||||||
|
match e { expand |a, b, c|: {} }
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="expand_diagnostics.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)
|
||||||
|
|
||||||
|
found_old := false
|
||||||
|
found_subject := false
|
||||||
|
found_captures := false
|
||||||
|
found_redundant := false
|
||||||
|
found_after := false
|
||||||
|
found_missing := false
|
||||||
|
found_many := false
|
||||||
|
found_tag := false
|
||||||
|
found_tagname := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
message := diagnostic.message
|
||||||
|
found_old = found_old || strings.contains(message, "'inline for' was renamed to 'expand for'")
|
||||||
|
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_redundant = found_redundant || strings.contains(message, "redundant 'expand'")
|
||||||
|
found_after = found_after || strings.contains(message, "arms after 'expand' are unreachable") || strings.contains(message, "arms after 'else' are unreachable")
|
||||||
|
found_missing = found_missing || strings.contains(message, "expected an expand value capture")
|
||||||
|
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_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_captures)
|
||||||
|
testing.expect(t, found_redundant)
|
||||||
|
testing.expect(t, found_after)
|
||||||
|
testing.expect(t, found_missing)
|
||||||
|
testing.expect(t, found_many)
|
||||||
|
testing.expect(t, found_tag)
|
||||||
|
testing.expect(t, found_tagname)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
milestone_37_format_errors_are_reported_at_comptime :: proc(t: ^testing.T) {
|
milestone_37_format_errors_are_reported_at_comptime :: proc(t: ^testing.T) {
|
||||||
directory := "/tmp/brolang-test-format-errors"
|
directory := "/tmp/brolang-test-format-errors"
|
||||||
@@ -2620,10 +2716,10 @@ main func() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
milestone_37_inline_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
|
||||||
inline for {1, 2} |value| {
|
expand for {1, 2} |value| {
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -2647,7 +2743,7 @@ milestone_37_inline_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 inline loop must be compile-time-resolvable",
|
"break or continue targeting an expand loop must be compile-time-resolvable",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
@@ -2675,7 +2771,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
|
||||||
inline for make_tokens() |token| {
|
expand 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 {
|
||||||
@@ -2750,14 +2846,14 @@ main func() void {}
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
milestone_37_inline_expansions_keep_distinct_call_resolutions :: proc(t: ^testing.T) {
|
milestone_37_expand_expansions_keep_distinct_call_resolutions :: proc(t: ^testing.T) {
|
||||||
text := `identity func($T type, value T) T {
|
text := `identity func($T type, value T) T {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
total i64 = 0
|
total i64 = 0
|
||||||
inline for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| {
|
expand for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| {
|
||||||
inline for row |value| {
|
expand for row |value| {
|
||||||
total += i64(identity(value))
|
total += i64(identity(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2786,16 +2882,16 @@ main func() i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
milestone_37_inline_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 {
|
||||||
inline for {i8(1), "skip"} |value, index| {
|
expand for {i8(1), "skip"} |value, index| {
|
||||||
if index == 1 {
|
if index == 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
take_i8(value)
|
take_i8(value)
|
||||||
}
|
}
|
||||||
inline for {i8(1), "stop"} |value, index| {
|
expand for {i8(1), "stop"} |value, index| {
|
||||||
if index == 1 {
|
if index == 1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -2819,14 +2915,14 @@ main func() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
milestone_37_inline_match_specialization_prunes_unselected_arms :: proc(t: ^testing.T) {
|
milestone_37_expand_match_specialization_prunes_unselected_arms :: proc(t: ^testing.T) {
|
||||||
text := `Kind :: enum { integer, string, stop }
|
text := `Kind :: enum { integer, string, stop }
|
||||||
IntToken :: struct { kind Kind, value i8 }
|
IntToken :: struct { kind Kind, value i8 }
|
||||||
StringToken :: struct { kind Kind, value []u8 }
|
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 {
|
||||||
inline for {
|
expand 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},
|
||||||
@@ -2840,7 +2936,7 @@ main func() void {
|
|||||||
.stop: break
|
.stop: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inline for {i8(2), "skip", "stop"} |value, index| {
|
expand for {i8(2), "skip", "stop"} |value, index| {
|
||||||
match index {
|
match index {
|
||||||
0: {}
|
0: {}
|
||||||
1..=1, 7: continue
|
1..=1, 7: continue
|
||||||
@@ -5416,7 +5512,7 @@ main func() i32 {
|
|||||||
return a + b - 16
|
return a + b - 16
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
source_file := source.Source{path="inline_errors.bro", text=text}
|
source_file := source.Source{path="expand_errors.bro", text=text}
|
||||||
diagnostics := source.init_diagnostics(&source_file)
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
defer source.destroy_diagnostics(&diagnostics)
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
symbols := symbol.init_table()
|
symbols := symbol.init_table()
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
Kind :: enum {
|
||||||
|
first
|
||||||
|
second
|
||||||
|
third
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair :: struct {
|
||||||
|
left i32
|
||||||
|
right i32
|
||||||
|
}
|
||||||
|
|
||||||
|
Value :: union(enum) {
|
||||||
|
number i32
|
||||||
|
pair Pair
|
||||||
|
empty void
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_score func(kind Kind) i32 {
|
||||||
|
result :: match kind {
|
||||||
|
.first: 1
|
||||||
|
expand |value|: {
|
||||||
|
yield match value {
|
||||||
|
.second: 2
|
||||||
|
.third: 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
equal_value func(a, b Value) bool {
|
||||||
|
if (tag!(a) != tag!(b)) return false
|
||||||
|
result :: match a {
|
||||||
|
expand |value, tag|: {
|
||||||
|
yield match tag {
|
||||||
|
.number: value == field!(b, tagname!(tag))
|
||||||
|
.pair: {
|
||||||
|
other :: field!(b, tagname!(tag))
|
||||||
|
yield value.left == other.left and value.right == other.right
|
||||||
|
}
|
||||||
|
.empty: {
|
||||||
|
yield true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
increment func(value @mut Value) void {
|
||||||
|
match value^ {
|
||||||
|
expand |@payload, tag|: match tag {
|
||||||
|
.number: payload^ += 1
|
||||||
|
.pair: payload.left += 1
|
||||||
|
.empty: _ = payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
if enum_score(.first) != 1 or enum_score(.second) != 2 or enum_score(.third) != 3 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
known :: $tag!(Value{number = 1})
|
||||||
|
known_direct :: tag!(Value{pair = Pair{left = 0, right = 0}})
|
||||||
|
if (known != .number) or (known_direct != .pair) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
a Value = .number{41}
|
||||||
|
b Value = .number{41}
|
||||||
|
if !equal_value(a, b) or equal_value(a, .pair{left = 41, right = 0}) {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
increment(&a)
|
||||||
|
if a.number != 42 {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
pair_a Value = .pair{left = 2, right = 3}
|
||||||
|
pair_b Value = .pair{left = 2, right = 3}
|
||||||
|
if !equal_value(pair_a, pair_b) or !equal_value(.empty, .empty) {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
increment(&pair_a)
|
||||||
|
if pair_a.pair.left != 3 {
|
||||||
|
return 6
|
||||||
|
}
|
||||||
|
|
||||||
|
empty Value = .empty
|
||||||
|
increment(&empty)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -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|: inline for record.fields |field| {
|
.record |record|: expand 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|: inline for record.fields |field| {
|
.record |record|: expand 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
|
||||||
inline for {Row {value = 2}, Row {value = 40}} |row| {
|
expand for {Row {value = 2}, Row {value = 40}} |row| {
|
||||||
total += row_value(row)
|
total += row_value(row)
|
||||||
}
|
}
|
||||||
return total
|
return total
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"if"
|
"if"
|
||||||
"while"
|
"while"
|
||||||
"for"
|
"for"
|
||||||
"inline"
|
"expand"
|
||||||
"break"
|
"break"
|
||||||
"continue"
|
"continue"
|
||||||
"defer"
|
"defer"
|
||||||
|
|||||||
+2
-2
@@ -339,7 +339,7 @@ hide write_default func(writer Writer, $T type, value T) void ! WriteError {
|
|||||||
.pointer: try write_all(writer, value)
|
.pointer: try write_all(writer, value)
|
||||||
.slice: try write_all(writer, value)
|
.slice: try write_all(writer, value)
|
||||||
.enum |enum_info|: {
|
.enum |enum_info|: {
|
||||||
inline for enum_info.fields |field| {
|
expand for enum_info.fields |field| {
|
||||||
if value == field!(T, field.name) {
|
if value == field!(T, field.name) {
|
||||||
try write_all(writer, ".")
|
try write_all(writer, ".")
|
||||||
try write_all(writer, field.name)
|
try write_all(writer, field.name)
|
||||||
@@ -354,7 +354,7 @@ hide write_default func(writer Writer, $T type, value T) void ! WriteError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print func(writer Writer, $format []u8, $Args type, args Args) void ! WriteError {
|
print func(writer Writer, $format []u8, $Args type, args Args) void ! WriteError {
|
||||||
inline for parse_format(format.len, format, Args) |token| {
|
expand for parse_format(format.len, format, Args) |token| {
|
||||||
match token.kind {
|
match token.kind {
|
||||||
.unused: break
|
.unused: break
|
||||||
.literal: try write_all(writer, format[token.start..token.end])
|
.literal: try write_all(writer, format[token.start..token.end])
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ module.exports = grammar({
|
|||||||
),
|
),
|
||||||
|
|
||||||
for_statement: $ => seq(
|
for_statement: $ => seq(
|
||||||
optional('inline'),
|
optional('expand'),
|
||||||
'for',
|
'for',
|
||||||
repeat($._newline),
|
repeat($._newline),
|
||||||
field('iterable', $.expression),
|
field('iterable', $.expression),
|
||||||
@@ -364,16 +364,30 @@ module.exports = grammar({
|
|||||||
'}',
|
'}',
|
||||||
),
|
),
|
||||||
|
|
||||||
match_arm: $ => seq(
|
match_arm: $ => choice(seq(
|
||||||
field('pattern', choice('else', commaSep1($, $.expression))),
|
field('pattern', choice('else', commaSep1($, $.expression))),
|
||||||
optional($.match_capture),
|
optional($.match_capture),
|
||||||
':',
|
':',
|
||||||
repeat($._newline),
|
repeat($._newline),
|
||||||
field('body', $._branch_body),
|
field('body', $._branch_body),
|
||||||
),
|
), seq(
|
||||||
|
'expand',
|
||||||
|
$.expand_match_capture,
|
||||||
|
':',
|
||||||
|
repeat($._newline),
|
||||||
|
field('body', $._branch_body),
|
||||||
|
)),
|
||||||
|
|
||||||
match_capture: $ => seq('|', optional('@'), field('name', choice($.identifier, $.sink)), '|'),
|
match_capture: $ => seq('|', optional('@'), field('name', choice($.identifier, $.sink)), '|'),
|
||||||
|
|
||||||
|
expand_match_capture: $ => seq(
|
||||||
|
'|',
|
||||||
|
optional('@'),
|
||||||
|
field('value', choice($.identifier, $.sink)),
|
||||||
|
optional(seq(',', field('tag', choice($.identifier, $.sink)))),
|
||||||
|
'|',
|
||||||
|
),
|
||||||
|
|
||||||
_branch_body: $ => $.statement,
|
_branch_body: $ => $.statement,
|
||||||
|
|
||||||
_value: $ => choice(
|
_value: $ => choice(
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"if"
|
"if"
|
||||||
"while"
|
"while"
|
||||||
"for"
|
"for"
|
||||||
"inline"
|
"expand"
|
||||||
"break"
|
"break"
|
||||||
"continue"
|
"continue"
|
||||||
"defer"
|
"defer"
|
||||||
|
|||||||
@@ -2402,7 +2402,7 @@
|
|||||||
"members": [
|
"members": [
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "inline"
|
"value": "expand"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "BLANK"
|
"type": "BLANK"
|
||||||
@@ -2593,6 +2593,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"match_arm": {
|
"match_arm": {
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
{
|
{
|
||||||
@@ -2680,6 +2683,40 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "expand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "expand_match_capture"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": ":"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REPEAT",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "body",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_branch_body"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"match_capture": {
|
"match_capture": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -2722,6 +2759,82 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"expand_match_capture": {
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "|"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "@"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BLANK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "value",
|
||||||
|
"content": {
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "sink"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": ","
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "tag",
|
||||||
|
"content": {
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "sink"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BLANK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "|"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"_branch_body": {
|
"_branch_body": {
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "statement"
|
"name": "statement"
|
||||||
|
|||||||
@@ -658,6 +658,40 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "expand_match_capture",
|
||||||
|
"named": true,
|
||||||
|
"fields": {
|
||||||
|
"tag": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "sink",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "sink",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "expression",
|
"type": "expression",
|
||||||
"named": true,
|
"named": true,
|
||||||
@@ -1429,7 +1463,7 @@
|
|||||||
},
|
},
|
||||||
"pattern": {
|
"pattern": {
|
||||||
"multiple": true,
|
"multiple": true,
|
||||||
"required": true,
|
"required": false,
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"type": ",",
|
"type": ",",
|
||||||
@@ -1450,6 +1484,10 @@
|
|||||||
"multiple": false,
|
"multiple": false,
|
||||||
"required": false,
|
"required": false,
|
||||||
"types": [
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "expand_match_capture",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "match_capture",
|
"type": "match_capture",
|
||||||
"named": true
|
"named": true
|
||||||
@@ -2575,6 +2613,10 @@
|
|||||||
"type": "escape_sequence",
|
"type": "escape_sequence",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "expand",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "f32",
|
"type": "f32",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -2635,10 +2677,6 @@
|
|||||||
"type": "import",
|
"type": "import",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "inline",
|
|
||||||
"named": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"named": false
|
"named": false
|
||||||
|
|||||||
+119691
-119134
File diff suppressed because it is too large
Load Diff
@@ -279,7 +279,7 @@ choose func() i32 {
|
|||||||
(identifier)))))))
|
(identifier)))))))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Tuples and inline for
|
Tuples and expand for
|
||||||
==================
|
==================
|
||||||
|
|
||||||
Pair :: struct { i32, []u8 }
|
Pair :: struct { i32, []u8 }
|
||||||
@@ -289,7 +289,7 @@ main func() void {
|
|||||||
singleton :: {1,}
|
singleton :: {1,}
|
||||||
empty :: {}
|
empty :: {}
|
||||||
_ = pair.0
|
_ = pair.0
|
||||||
inline for singleton |value| {
|
expand for singleton |value| {
|
||||||
_ = value
|
_ = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,3 +365,108 @@ main func() void {
|
|||||||
(sink))
|
(sink))
|
||||||
(expression
|
(expression
|
||||||
(identifier))))))))))
|
(identifier))))))))))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Expanded match arms
|
||||||
|
==================
|
||||||
|
|
||||||
|
Kind :: enum { one, two }
|
||||||
|
Value :: union(enum) { number i32, empty void }
|
||||||
|
|
||||||
|
test func(kind Kind, value Value) void {
|
||||||
|
match kind {
|
||||||
|
.one: {}
|
||||||
|
expand |tag|: _ = tag
|
||||||
|
}
|
||||||
|
match value {
|
||||||
|
expand |@payload, tag|: {
|
||||||
|
_ = payload
|
||||||
|
_ = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(type_declaration
|
||||||
|
(identifier)
|
||||||
|
(enum_type
|
||||||
|
(enum_body
|
||||||
|
(enum_member
|
||||||
|
(identifier))
|
||||||
|
(enum_member
|
||||||
|
(identifier)))))
|
||||||
|
(type_declaration
|
||||||
|
(identifier)
|
||||||
|
(union_type
|
||||||
|
(record_body
|
||||||
|
(record_field
|
||||||
|
(identifier)
|
||||||
|
(type
|
||||||
|
(builtin_type)))
|
||||||
|
(record_field
|
||||||
|
(identifier)
|
||||||
|
(type
|
||||||
|
(builtin_type))))))
|
||||||
|
(function_declaration
|
||||||
|
(identifier)
|
||||||
|
(parameter_list
|
||||||
|
(parameter
|
||||||
|
(identifier)
|
||||||
|
(type
|
||||||
|
(named_type
|
||||||
|
(qualified_identifier
|
||||||
|
(identifier)))))
|
||||||
|
(parameter
|
||||||
|
(identifier)
|
||||||
|
(type
|
||||||
|
(named_type
|
||||||
|
(qualified_identifier
|
||||||
|
(identifier))))))
|
||||||
|
(type
|
||||||
|
(builtin_type))
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(match_statement
|
||||||
|
(expression
|
||||||
|
(identifier))
|
||||||
|
(match_arm
|
||||||
|
(expression
|
||||||
|
(enum_literal
|
||||||
|
(identifier)))
|
||||||
|
(statement
|
||||||
|
(expression_statement
|
||||||
|
(expression
|
||||||
|
(tuple_literal)))))
|
||||||
|
(match_arm
|
||||||
|
(expand_match_capture
|
||||||
|
(identifier))
|
||||||
|
(statement
|
||||||
|
(assignment_statement
|
||||||
|
(expression
|
||||||
|
(sink))
|
||||||
|
(expression
|
||||||
|
(identifier)))))))
|
||||||
|
(statement
|
||||||
|
(match_statement
|
||||||
|
(expression
|
||||||
|
(identifier))
|
||||||
|
(match_arm
|
||||||
|
(expand_match_capture
|
||||||
|
(identifier)
|
||||||
|
(identifier))
|
||||||
|
(statement
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(assignment_statement
|
||||||
|
(expression
|
||||||
|
(sink))
|
||||||
|
(expression
|
||||||
|
(identifier))))
|
||||||
|
(statement
|
||||||
|
(assignment_statement
|
||||||
|
(expression
|
||||||
|
(sink))
|
||||||
|
(expression
|
||||||
|
(identifier))))))))))))
|
||||||
|
|||||||
Reference in New Issue
Block a user