Compare commits
2 Commits
9e75549d02
...
0eeacc2e37
| Author | SHA1 | Date | |
|---|---|---|---|
| 0eeacc2e37 | |||
| 6de4d9f9f3 |
+15
-1
@@ -39,6 +39,20 @@ roadmap and milestone history.
|
|||||||
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
|
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
|
||||||
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition
|
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition
|
||||||
|
|
||||||
|
#### native record constraint fields
|
||||||
|
|
||||||
|
A direct `int`, `float`, or `range` field in a named native struct or union is a
|
||||||
|
program-wide constraint, not per-value polymorphism. Before record layout, all reachable keyed
|
||||||
|
constructors, field assignments, and concrete uses of field reads contribute demands and the field
|
||||||
|
resolves once to one concrete runtime type. Compatible scalar demands widen normally. Integer
|
||||||
|
literals remain provisional until inference settles, so a later `usize` use can resolve an `int`
|
||||||
|
field to `usize`; otherwise literal-only `int` fields use the widest smallest-signed type required,
|
||||||
|
and literal-only `float` fields use `f64`.
|
||||||
|
|
||||||
|
An undemanded field or incompatible demands are errors. This inference applies only to direct
|
||||||
|
fields of named native records. `c_struct` fields, nested constraints such as `[]int`, and fields in
|
||||||
|
anonymous generated records still require concrete runtime types.
|
||||||
|
|
||||||
#### keyword member names
|
#### keyword member names
|
||||||
|
|
||||||
Reserved keywords are valid native enum members and tagged-union variants when used in an
|
Reserved keywords are valid native enum members and tagged-union variants when used in an
|
||||||
@@ -76,7 +90,7 @@ fields. `_` is not a keyword member name.
|
|||||||
- `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
|
||||||
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks
|
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks
|
||||||
- bare block scopes and `defer`, including LIFO flushing on fall-through, `return`, `break`, and `continue`
|
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
|
||||||
- value blocks, value `if`, value loops, value `match`, `yield`, and labeled `yield :label value`
|
- value blocks, value `if`, value loops, value `match`, `yield`, and labeled `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
|
||||||
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ Current prototype features:
|
|||||||
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by explicit or uniquely inferred leading comptime arguments
|
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by explicit or uniquely inferred leading comptime arguments
|
||||||
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
|
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
|
||||||
- Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`)
|
- Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`)
|
||||||
- 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
|
- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`/`errdefer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
||||||
- Native function pointer values and types (`*func(...) R`, `*func(...) R ! E`, `?*func(...) R`)
|
- Native function pointer values and types (`*func(...) R`, `*func(...) R ! E`, `?*func(...) R`)
|
||||||
- Typed allocation/reallocation through `std/mem` and generic dynamic arrays through `std/arraylist`
|
- Typed allocation/reallocation through `std/mem` and generic dynamic arrays through `std/arraylist`
|
||||||
- Bodyless concrete C function declarations with exact external symbol names
|
- Bodyless concrete C function declarations with exact external symbol names
|
||||||
|
|||||||
@@ -300,6 +300,9 @@
|
|||||||
- `defer <stmt>` runs the statement when the enclosing block scope exits, in reverse
|
- `defer <stmt>` runs the statement when the enclosing block scope exits, in reverse
|
||||||
(LIFO) order, on every exit path: fall-through, `return`, `break`, `continue`. The
|
(LIFO) order, on every exit path: fall-through, `return`, `break`, `continue`. The
|
||||||
deferred statement may be a block (`defer { ... }`)
|
deferred statement may be a block (`defer { ... }`)
|
||||||
|
- `errdefer [|error|] <stmt>` is the fallible-function counterpart: it runs only when
|
||||||
|
an explicit or `try`-propagated error exits its active block scope, can capture the
|
||||||
|
widened enclosing error, and stays interleaved with ordinary defers in LIFO order
|
||||||
- bare block statements `{ ... }` were added as the enabling feature: a `{ ... }`
|
- bare block statements `{ ... }` were added as the enabling feature: a `{ ... }`
|
||||||
introduces a nested scope (locals are name-scoped to it; defers inside it fire at the
|
introduces a nested scope (locals are name-scoped to it; defers inside it fire at the
|
||||||
closing brace). A leading `{` is unambiguous since struct literals are postfix only
|
closing brace). A leading `{` is unambiguous since struct literals are postfix only
|
||||||
@@ -310,10 +313,8 @@
|
|||||||
the innermost loop body; fall-through flushes the current block's own defers. Deferring
|
the innermost loop body; fall-through flushes the current block's own defers. Deferring
|
||||||
a `return`/`break`/`continue`/`defer`, a `return` inside a `defer`, or a `break`/
|
a `return`/`break`/`continue`/`defer`, a `return` inside a `defer`, or a `break`/
|
||||||
`continue` that would escape a `defer` are all rejected
|
`continue` that would escape a `defer` are all rejected
|
||||||
- implemented entirely in lexer/parser/checker (new `Keyword_Defer`; `Block`/`Defer` AST
|
- cleanup is statically expanded with no runtime registration stack; `try` carries its
|
||||||
kinds reusing `body`/`update`; `parse_block_statement`/`parse_defer`). No HIR opcode:
|
active error-exit cleanup into lowering so propagation cannot bypass either defer form
|
||||||
a bare block is built and spliced inline, and a deferred statement is built once at the
|
|
||||||
`defer` site and its hir replayed at each exit, so lowering/codegen are unchanged
|
|
||||||
|
|
||||||
20. add `yield` statement (implemented; first pass — value blocks only; see below)
|
20. add `yield` statement (implemented; first pass — value blocks only; see below)
|
||||||
- a `{ ... }` on the right of a declaration or assignment is a *value block*: its final
|
- a `{ ... }` on the right of a declaration or assignment is a *value block*: its final
|
||||||
@@ -823,6 +824,10 @@
|
|||||||
conflicting targets
|
conflicting targets
|
||||||
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
|
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
|
||||||
|
|
||||||
|
35. syntax highlighting (tree-sitter) updates (implemented)
|
||||||
|
- pointer sigils are highlighted as operators
|
||||||
|
- type-factory calls in type positions and struct literals are highlighted as functions
|
||||||
|
|
||||||
## 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:
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ Stmt :: struct {
|
|||||||
immutable: bool,
|
immutable: bool,
|
||||||
value_control_flow: bool,
|
value_control_flow: bool,
|
||||||
pointer_capture: bool,
|
pointer_capture: 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 `=`;
|
||||||
// the arithmetic variants are `+=`, `-=`, `*=`, and `/=`.
|
// the arithmetic variants are `+=`, `-=`, `*=`, and `/=`.
|
||||||
@@ -187,7 +188,8 @@ Stmt :: struct {
|
|||||||
// distinguish `|@item|` from copy capture.
|
// distinguish `|@item|` from copy capture.
|
||||||
// `Block` statements (a bare `{ ... }` scope) use `body` as their statements.
|
// `Block` statements (a bare `{ ... }` scope) use `body` as their statements.
|
||||||
// `Defer` statements use `update` as the deferred statement (which may itself
|
// `Defer` statements use `update` as the deferred statement (which may itself
|
||||||
// be a `Block`).
|
// be a `Block`), `error_only` for `errdefer`, and `captures` for its optional
|
||||||
|
// error capture.
|
||||||
// `Match` statements use `expr` as the subject and `body` as the list of arm
|
// `Match` statements use `expr` as the subject and `body` as the list of arm
|
||||||
// statements (each a `Match_Arm`). A `Match_Arm` uses `patterns` as its pattern
|
// statements (each a `Match_Arm`). A `Match_Arm` uses `patterns` as its pattern
|
||||||
// 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),
|
||||||
|
|||||||
+476
-34
@@ -72,6 +72,12 @@ Yield_Target :: struct {
|
|||||||
defer_floor: int,
|
defer_floor: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Defer_Entry :: struct {
|
||||||
|
body: []hir.Stmt_Id,
|
||||||
|
error_only: bool,
|
||||||
|
capture: hir.Local_Id,
|
||||||
|
}
|
||||||
|
|
||||||
Build_Ctx :: struct {
|
Build_Ctx :: struct {
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
@@ -96,7 +102,7 @@ Build_Ctx :: struct {
|
|||||||
// to be valid). `defer_depth`/`loop_floor` guard control flow inside a deferred
|
// to be valid). `defer_depth`/`loop_floor` guard control flow inside a deferred
|
||||||
// statement: `return` is rejected while `defer_depth > 0`, and `break`/`continue`
|
// statement: `return` is rejected while `defer_depth > 0`, and `break`/`continue`
|
||||||
// only see loops opened within the defer (those past `loop_floor`).
|
// only see loops opened within the defer (those past `loop_floor`).
|
||||||
defers: ^[dynamic][]hir.Stmt_Id,
|
defers: ^[dynamic]Defer_Entry,
|
||||||
loop_defer_starts: ^[dynamic]int,
|
loop_defer_starts: ^[dynamic]int,
|
||||||
// Parallel to `loop_defer_starts`: the label of each enclosing break target (INVALID
|
// Parallel to `loop_defer_starts`: the label of each enclosing break target (INVALID
|
||||||
// when unlabeled), so a `break :L` / `continue :L` can target an outer one. A labeled
|
// when unlabeled), so a `break :L` / `continue :L` can target an outer one. A labeled
|
||||||
@@ -168,6 +174,14 @@ Checker :: struct {
|
|||||||
global_open_float: []bool,
|
global_open_float: []bool,
|
||||||
global_const_value: []i128,
|
global_const_value: []i128,
|
||||||
global_demands_dirty: bool,
|
global_demands_dirty: bool,
|
||||||
|
// Program-wide inference for direct constraint fields in named native records.
|
||||||
|
// The arrays use Store.fields' existing dense indices; resolved concrete types
|
||||||
|
// live directly in module.types.fields so layout and lowering need no side table.
|
||||||
|
record_field_constraints: []types.Type,
|
||||||
|
record_field_defaults: []types.Type,
|
||||||
|
record_field_conflicts: []types.Type,
|
||||||
|
record_field_conflict_spans: []source.Span,
|
||||||
|
record_field_demands_dirty: bool,
|
||||||
external_global_canonical: []ast.Global_Id,
|
external_global_canonical: []ast.Global_Id,
|
||||||
external_global_diagnostics: []source.Diagnostic_Id,
|
external_global_diagnostics: []source.Diagnostic_Id,
|
||||||
constants: []Constant,
|
constants: []Constant,
|
||||||
@@ -2393,6 +2407,211 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_record_field_inference :: proc(checker: ^Checker) {
|
||||||
|
store := &checker.module.types
|
||||||
|
for item in store.nodes {
|
||||||
|
if !(item.declared && (item.kind == .Struct || item.kind == .Union) &&
|
||||||
|
!item.c_layout && symbol.is_valid(symbol.Id(item.name))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
start := int(item.field_start)
|
||||||
|
end := start+int(item.field_count)
|
||||||
|
if start < 0 || end > len(store.fields) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for slot in start..<end {
|
||||||
|
if types.is_constraint(store.fields[slot].type) {
|
||||||
|
checker.record_field_constraints[slot] = store.fields[slot].type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_inferred_record_field :: proc(checker: ^Checker, slot: int) -> bool {
|
||||||
|
return slot >= 0 && slot < len(checker.record_field_constraints) &&
|
||||||
|
types.is_constraint(checker.record_field_constraints[slot])
|
||||||
|
}
|
||||||
|
|
||||||
|
record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.Id, bool) {
|
||||||
|
for item in checker.module.types.nodes {
|
||||||
|
if !(item.declared && (item.kind == .Struct || item.kind == .Union) &&
|
||||||
|
!item.c_layout && symbol.is_valid(symbol.Id(item.name))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
start := int(item.field_start)
|
||||||
|
if slot >= start && slot < start+int(item.field_count) &&
|
||||||
|
slot >= 0 && slot < len(checker.module.types.fields) {
|
||||||
|
return symbol.Id(item.name), symbol.Id(checker.module.types.fields[slot].name), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return symbol.INVALID, symbol.INVALID, false
|
||||||
|
}
|
||||||
|
|
||||||
|
record_field_conflict :: proc(checker: ^Checker, slot: int, actual: types.Type, span: source.Span) {
|
||||||
|
if !is_inferred_record_field(checker, slot) ||
|
||||||
|
types.is_valid(checker.record_field_conflicts[slot]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checker.record_field_conflicts[slot] = actual
|
||||||
|
checker.record_field_conflict_spans[slot] = span
|
||||||
|
}
|
||||||
|
|
||||||
|
merge_record_field_default :: proc(checker: ^Checker, slot: int, candidate: types.Type, span: source.Span) -> bool {
|
||||||
|
if !is_inferred_record_field(checker, slot) || !is_runtime_type(checker, candidate) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
constraint := checker.record_field_constraints[slot]
|
||||||
|
if !types.constraint_accepts(constraint, candidate, &checker.module.types) {
|
||||||
|
record_field_conflict(checker, slot, candidate, span)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
current := checker.record_field_defaults[slot]
|
||||||
|
if !is_runtime_type(checker, current) {
|
||||||
|
checker.record_field_defaults[slot] = candidate
|
||||||
|
checker.record_field_demands_dirty = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if types.equal(current, candidate) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
merged := types.widest(current, candidate)
|
||||||
|
if types.is_concrete_scalar(merged) {
|
||||||
|
if !types.equal(current, merged) {
|
||||||
|
checker.record_field_defaults[slot] = merged
|
||||||
|
checker.record_field_demands_dirty = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
record_field_conflict(checker, slot, candidate, span)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
merge_record_field_demand :: proc(checker: ^Checker, slot: int, demand: types.Type, span: source.Span) -> bool {
|
||||||
|
if !is_inferred_record_field(checker, slot) || !is_runtime_type(checker, demand) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
constraint := checker.record_field_constraints[slot]
|
||||||
|
if !types.constraint_accepts(constraint, demand, &checker.module.types) {
|
||||||
|
record_field_conflict(checker, slot, demand, span)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
current := checker.module.types.fields[slot].type
|
||||||
|
if types.is_constraint(current) {
|
||||||
|
checker.module.types.fields[slot].type = demand
|
||||||
|
checker.record_field_demands_dirty = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if types.equal(current, demand) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
merged := types.widest(current, demand)
|
||||||
|
if types.is_concrete_scalar(merged) {
|
||||||
|
if !types.equal(current, merged) {
|
||||||
|
checker.module.types.fields[slot].type = merged
|
||||||
|
checker.record_field_demands_dirty = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
record_field_conflict(checker, slot, demand, span)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
record_field_expr_candidate :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
slot: int,
|
||||||
|
expr_id: ast.Expr_Id,
|
||||||
|
inferred: types.Type,
|
||||||
|
locals: []Infer_Local,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
) -> bool {
|
||||||
|
if !is_inferred_record_field(checker, slot) || expr_id == ast.INVALID_EXPR {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
expr := checker.ast_module.exprs[expr_id]
|
||||||
|
constraint := checker.record_field_constraints[slot]
|
||||||
|
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file);
|
||||||
|
constant.kind == .Value && fits_i64(constant.value) {
|
||||||
|
candidate := types.smallest_signed_for_literal(i64(constant.value))
|
||||||
|
if constraint == types.FLOAT {
|
||||||
|
candidate = types.F64
|
||||||
|
}
|
||||||
|
return merge_record_field_default(checker, slot, candidate, expr.span)
|
||||||
|
}
|
||||||
|
if is_float_constant_expr(checker, expr_id) {
|
||||||
|
return merge_record_field_default(checker, slot, types.F64, expr.span)
|
||||||
|
}
|
||||||
|
if numeric_operand_is_open(checker, expr_id, locals, pkg, file) {
|
||||||
|
candidate := inferred
|
||||||
|
if constraint == types.FLOAT && types.is_concrete_integer(candidate) {
|
||||||
|
candidate = types.F64
|
||||||
|
}
|
||||||
|
return merge_record_field_default(checker, slot, candidate, expr.span)
|
||||||
|
}
|
||||||
|
concrete := types.constraint_target(constraint, inferred, &checker.module.types)
|
||||||
|
if !is_runtime_type(checker, concrete) {
|
||||||
|
if is_runtime_type(checker, inferred) {
|
||||||
|
record_field_conflict(checker, slot, inferred, expr.span)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return merge_record_field_demand(checker, slot, concrete, expr.span)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize_record_field_inference :: proc(checker: ^Checker) {
|
||||||
|
for constraint, slot in checker.record_field_constraints {
|
||||||
|
if !types.is_constraint(constraint) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
record_name, field_name, ok := record_field_owner(checker, slot)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
current := checker.module.types.fields[slot].type
|
||||||
|
conflict := checker.record_field_conflicts[slot]
|
||||||
|
if types.is_valid(conflict) {
|
||||||
|
if is_runtime_type(checker, current) {
|
||||||
|
source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
checker.record_field_conflict_spans[slot],
|
||||||
|
"conflicting types %s and %s for field '%s.%s' declared as '%s'",
|
||||||
|
types.name(current), types.name(conflict),
|
||||||
|
symbol_text(checker, record_name), symbol_text(checker, field_name),
|
||||||
|
types.name(constraint),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
checker.record_field_conflict_spans[slot],
|
||||||
|
"type %s does not satisfy the '%s' constraint for field '%s.%s'",
|
||||||
|
types.name(conflict), types.name(constraint),
|
||||||
|
symbol_text(checker, record_name), symbol_text(checker, field_name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if types.is_constraint(current) {
|
||||||
|
source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
source.Span{},
|
||||||
|
"could not resolve the '%s' constraint for field '%s.%s'",
|
||||||
|
types.name(constraint),
|
||||||
|
symbol_text(checker, record_name), symbol_text(checker, field_name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if types.is_constraint(current) {
|
||||||
|
switch constraint {
|
||||||
|
case types.INT:
|
||||||
|
checker.module.types.fields[slot].type = types.I64
|
||||||
|
case types.FLOAT:
|
||||||
|
checker.module.types.fields[slot].type = types.F64
|
||||||
|
case types.RANGE:
|
||||||
|
checker.module.types.fields[slot].type = types.range(&checker.module.types, types.I64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
validate_type_nodes :: proc(checker: ^Checker) {
|
validate_type_nodes :: proc(checker: ^Checker) {
|
||||||
for item, index in checker.module.types.nodes {
|
for item, index in checker.module.types.nodes {
|
||||||
id := types.DYNAMIC_START+types.Type(index)
|
id := types.DYNAMIC_START+types.Type(index)
|
||||||
@@ -2453,9 +2672,14 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
|||||||
// variant has no runtime value, only a tag. Allowed only here, not for
|
// variant has no runtime value, only a tag. Allowed only here, not for
|
||||||
// structs, untagged unions, or c_structs.
|
// structs, untagged unions, or c_structs.
|
||||||
tagged_union := item.kind == .Union && types.is_enum(item.child, &checker.module.types)
|
tagged_union := item.kind == .Union && types.is_enum(item.child, &checker.module.types)
|
||||||
for field in types.fields_for(&checker.module.types, id) {
|
for field, field_index in types.fields_for(&checker.module.types, id) {
|
||||||
|
field_slot := int(item.field_start)+field_index
|
||||||
if tagged_union && types.is_void(field.type) {
|
if tagged_union && types.is_void(field.type) {
|
||||||
// void variant: no payload to validate.
|
// void variant: no payload to validate.
|
||||||
|
} else if is_inferred_record_field(checker, field_slot) {
|
||||||
|
// Direct constraints in named native records are validated after the
|
||||||
|
// program-wide inference fixpoint. C, nested, and anonymous fields do
|
||||||
|
// not enter this state and retain the existing validation below.
|
||||||
} else if !types.is_runtime_value(field.type, &checker.module.types) {
|
} else if !types.is_runtime_value(field.type, &checker.module.types) {
|
||||||
source.add(
|
source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -2844,7 +3068,11 @@ infer_compound_expr :: proc(
|
|||||||
if types.is_pointer(value, store) {
|
if types.is_pointer(value, store) {
|
||||||
value = types.child_type(value, store)
|
value = types.child_type(value, store)
|
||||||
}
|
}
|
||||||
_, field, ok := find_struct_field(checker, value, expr.name)
|
slot, field, ok := find_struct_field_slot(checker, value, expr.name)
|
||||||
|
if ok && is_inferred_record_field(checker, slot) && is_runtime_type(checker, expected) {
|
||||||
|
_ = merge_record_field_demand(checker, slot, expected, expr.span)
|
||||||
|
field = checker.module.types.fields[slot]
|
||||||
|
}
|
||||||
return field.type if ok else types.INVALID
|
return field.type if ok else types.INVALID
|
||||||
case .Unwrap:
|
case .Unwrap:
|
||||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
@@ -2881,15 +3109,37 @@ infer_compound_expr :: proc(
|
|||||||
resize(&block_locals, capture_start)
|
resize(&block_locals, capture_start)
|
||||||
return success
|
return success
|
||||||
case .Struct_Literal:
|
case .Struct_Literal:
|
||||||
for keyed in expr.args {
|
value := types.INVALID
|
||||||
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded, local_types)
|
if expr.left != ast.INVALID_EXPR {
|
||||||
}
|
value, _ = resolve_type_argument(checker, expr.left, pkg, file)
|
||||||
if !symbol.is_valid(expr.name) {
|
} else if symbol.is_valid(expr.name) {
|
||||||
return types.INVALID
|
|
||||||
}
|
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file)
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
||||||
value := types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) if available else types.INVALID
|
value = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) if available else types.INVALID
|
||||||
return types.resolve_alias(value, store)
|
} else {
|
||||||
|
value = expected
|
||||||
|
}
|
||||||
|
value = types.resolve_alias(value, store)
|
||||||
|
for keyed in expr.args {
|
||||||
|
keyed_expr := checker.ast_module.exprs[keyed]
|
||||||
|
if keyed_expr.left == ast.INVALID_EXPR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slot, field, ok := find_struct_field_slot(checker, value, keyed_expr.name)
|
||||||
|
if !ok || !is_inferred_record_field(checker, slot) {
|
||||||
|
_ = infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field_expected := field.type if is_runtime_type(checker, field.type) else types.INVALID
|
||||||
|
actual := infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types, field_expected)
|
||||||
|
_ = record_field_expr_candidate(checker, slot, keyed_expr.left, actual, locals, pkg, file)
|
||||||
|
if is_runtime_type(checker, checker.module.types.fields[slot].type) {
|
||||||
|
_ = record_demand(
|
||||||
|
checker, keyed_expr.left, checker.module.types.fields[slot].type,
|
||||||
|
locals, local_types, pkg, file,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
case .Keyed:
|
case .Keyed:
|
||||||
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
case:
|
case:
|
||||||
@@ -2990,8 +3240,12 @@ infer_expr :: proc(
|
|||||||
base_type = types.child_type(base_type, &checker.module.types)
|
base_type = types.child_type(base_type, &checker.module.types)
|
||||||
}
|
}
|
||||||
if !types.is_valid(last) {
|
if !types.is_valid(last) {
|
||||||
_, field, ok := find_struct_field(checker, base_type, expr.name)
|
slot, field, ok := find_struct_field_slot(checker, base_type, expr.name)
|
||||||
if ok {
|
if ok {
|
||||||
|
if is_inferred_record_field(checker, slot) && is_runtime_type(checker, frame.expected) {
|
||||||
|
_ = merge_record_field_demand(checker, slot, frame.expected, expr.span)
|
||||||
|
field = checker.module.types.fields[slot]
|
||||||
|
}
|
||||||
last = field.type
|
last = field.type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3014,8 +3268,12 @@ infer_expr :: proc(
|
|||||||
base_type = types.child_type(base_type, &checker.module.types)
|
base_type = types.child_type(base_type, &checker.module.types)
|
||||||
}
|
}
|
||||||
if !types.is_valid(last) {
|
if !types.is_valid(last) {
|
||||||
_, field, ok := find_struct_field(checker, base_type, expr.name)
|
slot, field, ok := find_struct_field_slot(checker, base_type, expr.name)
|
||||||
if ok {
|
if ok {
|
||||||
|
if is_inferred_record_field(checker, slot) && is_runtime_type(checker, frame.expected) {
|
||||||
|
_ = merge_record_field_demand(checker, slot, frame.expected, expr.span)
|
||||||
|
field = checker.module.types.fields[slot]
|
||||||
|
}
|
||||||
last = field.type
|
last = field.type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3569,6 +3827,16 @@ infer_statements :: proc(
|
|||||||
locals^[local_index].mutable {
|
locals^[local_index].mutable {
|
||||||
_ = merge_infer_local_type(checker, &locals^[local_index], value_type, local_types)
|
_ = merge_infer_local_type(checker, &locals^[local_index], value_type, local_types)
|
||||||
}
|
}
|
||||||
|
} else if target_expr.kind == .Field ||
|
||||||
|
target_expr.kind == .Name && symbol.is_valid(target_expr.qualifier) {
|
||||||
|
if slot, ok := inferred_record_field_slot_from_expr(
|
||||||
|
checker, target_expr, locals^[:], local_types, pkg, file,
|
||||||
|
); ok {
|
||||||
|
_ = record_field_expr_candidate(
|
||||||
|
checker, slot, statement.expr, value_type, locals^[:], pkg, file,
|
||||||
|
)
|
||||||
|
target_type = checker.module.types.fields[slot].type
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !rhs_is_arith {
|
if !rhs_is_arith {
|
||||||
_ = record_demand(checker, statement.expr, target_type, locals^[:], local_types, pkg, file)
|
_ = record_demand(checker, statement.expr, target_type, locals^[:], local_types, pkg, file)
|
||||||
@@ -3686,8 +3954,19 @@ infer_statements :: proc(
|
|||||||
case .Block:
|
case .Block:
|
||||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
case .Defer:
|
case .Defer:
|
||||||
|
capture_start := len(locals^)
|
||||||
|
if statement.error_only && len(statement.captures) > 0 &&
|
||||||
|
statement.captures[0] != checker.sink_symbol {
|
||||||
|
append(locals, Infer_Local{
|
||||||
|
name=statement.captures[0],
|
||||||
|
type=types.fallible_error(result^, &checker.module.types),
|
||||||
|
declared=types.fallible_error(result^, &checker.module.types),
|
||||||
|
statement=ast.INVALID_STMT,
|
||||||
|
})
|
||||||
|
}
|
||||||
deferred := [1]ast.Stmt_Id{statement.update}
|
deferred := [1]ast.Stmt_Id{statement.update}
|
||||||
infer_statements(checker, deferred[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, deferred[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
|
resize(locals, capture_start)
|
||||||
case .Match:
|
case .Match:
|
||||||
// The build pass desugars `match` to an if/else chain, but inference runs first
|
// The build pass desugars `match` to an if/else chain, but inference runs first
|
||||||
// and must still visit the subject and arm bodies so calls there get specialized
|
// and must still visit the subject and arm bodies so calls there get specialized
|
||||||
@@ -4080,8 +4359,39 @@ record_demand_shallow :: proc(
|
|||||||
return record_demand(checker, expr_id, demand, locals, local_types, pkg, file)
|
return record_demand(checker, expr_id, demand, locals, local_types, pkg, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inferred_record_field_slot_from_expr :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
expr: ast.Expr,
|
||||||
|
locals: []Infer_Local,
|
||||||
|
local_types: []types.Type,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
) -> (int, bool) {
|
||||||
|
base := types.INVALID
|
||||||
|
field_name := expr.name
|
||||||
|
if expr.kind == .Field {
|
||||||
|
base = infer_nested_expr(checker, expr.left, locals, pkg, file, nil, local_types)
|
||||||
|
} else if expr.kind == .Name && symbol.is_valid(expr.qualifier) &&
|
||||||
|
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
||||||
|
base = find_infer_local(locals, expr.qualifier)
|
||||||
|
if !types.is_valid(base) {
|
||||||
|
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
|
||||||
|
base = checker.global_types[global]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -1, false
|
||||||
|
}
|
||||||
|
if types.is_pointer(base, &checker.module.types) {
|
||||||
|
base = types.child_type(base, &checker.module.types)
|
||||||
|
}
|
||||||
|
slot, _, ok := find_struct_field_slot(checker, base, field_name)
|
||||||
|
return slot, ok && is_inferred_record_field(checker, slot)
|
||||||
|
}
|
||||||
|
|
||||||
// record_demand pushes a concrete type demand onto open numeric slots reachable
|
// record_demand pushes a concrete type demand onto open numeric slots reachable
|
||||||
// through bare names and numeric arithmetic. Calls remain a boundary (milestone 14.5).
|
// through bare names, inferred record fields, and numeric arithmetic. Calls remain
|
||||||
|
// a boundary (milestone 14.5).
|
||||||
record_demand :: proc(
|
record_demand :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
expr_id: ast.Expr_Id,
|
expr_id: ast.Expr_Id,
|
||||||
@@ -4098,6 +4408,13 @@ record_demand :: proc(
|
|||||||
expr := checker.ast_module.exprs[expr_id]
|
expr := checker.ast_module.exprs[expr_id]
|
||||||
#partial switch expr.kind {
|
#partial switch expr.kind {
|
||||||
case .Name:
|
case .Name:
|
||||||
|
if symbol.is_valid(expr.qualifier) {
|
||||||
|
if slot, ok := inferred_record_field_slot_from_expr(
|
||||||
|
checker, expr, locals, local_types, pkg, file,
|
||||||
|
); ok {
|
||||||
|
return merge_record_field_demand(checker, slot, demand, expr.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
if !symbol.is_valid(expr.qualifier) {
|
if !symbol.is_valid(expr.qualifier) {
|
||||||
if index, ok := find_infer_local_index(locals, expr.name); ok {
|
if index, ok := find_infer_local_index(locals, expr.name); ok {
|
||||||
return merge_local_demand(checker, &locals[index], demand, local_types)
|
return merge_local_demand(checker, &locals[index], demand, local_types)
|
||||||
@@ -4111,6 +4428,12 @@ record_demand :: proc(
|
|||||||
if global != ast.INVALID_GLOBAL {
|
if global != ast.INVALID_GLOBAL {
|
||||||
return merge_global_demand(checker, global, demand)
|
return merge_global_demand(checker, global, demand)
|
||||||
}
|
}
|
||||||
|
case .Field:
|
||||||
|
if slot, ok := inferred_record_field_slot_from_expr(
|
||||||
|
checker, expr, locals, local_types, pkg, file,
|
||||||
|
); ok {
|
||||||
|
return merge_record_field_demand(checker, slot, demand, expr.span)
|
||||||
|
}
|
||||||
case .Negate:
|
case .Negate:
|
||||||
if types.is_signed(demand, checker.target) || types.is_float(demand, checker.target) {
|
if types.is_signed(demand, checker.target) || types.is_float(demand, checker.target) {
|
||||||
return record_demand(checker, expr.left, demand, locals, local_types, pkg, file)
|
return record_demand(checker, expr.left, demand, locals, local_types, pkg, file)
|
||||||
@@ -4184,6 +4507,7 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
for {
|
for {
|
||||||
changed := false
|
changed := false
|
||||||
checker.global_demands_dirty = false
|
checker.global_demands_dirty = false
|
||||||
|
checker.record_field_demands_dirty = false
|
||||||
spec_count := len(checker.specs)
|
spec_count := len(checker.specs)
|
||||||
|
|
||||||
// Backward demands: a global pushes its own (declared or already-resolved) type
|
// Backward demands: a global pushes its own (declared or already-resolved) type
|
||||||
@@ -4269,6 +4593,9 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
if checker.global_demands_dirty {
|
if checker.global_demands_dirty {
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
if checker.record_field_demands_dirty {
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
if !changed {
|
if !changed {
|
||||||
if !defaults_applied {
|
if !defaults_applied {
|
||||||
defaults_applied = true
|
defaults_applied = true
|
||||||
@@ -4287,6 +4614,15 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
defaulted = true
|
defaulted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for fallback, slot in checker.record_field_defaults {
|
||||||
|
if !is_inferred_record_field(checker, slot) ||
|
||||||
|
!types.is_constraint(checker.module.types.fields[slot].type) ||
|
||||||
|
!is_runtime_type(checker, fallback) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checker.module.types.fields[slot].type = fallback
|
||||||
|
defaulted = true
|
||||||
|
}
|
||||||
if defaulted {
|
if defaulted {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -4785,6 +5121,19 @@ find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symb
|
|||||||
return 0, {}, false
|
return 0, {}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
find_struct_field_slot :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) {
|
||||||
|
item, ok := types.node(&checker.module.types, struct_type)
|
||||||
|
if !ok || (item.kind != .Struct && item.kind != .Union) {
|
||||||
|
return -1, {}, false
|
||||||
|
}
|
||||||
|
for field, index in types.fields_for(&checker.module.types, struct_type) {
|
||||||
|
if field.name == u32(name) {
|
||||||
|
return int(item.field_start)+index, field, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, {}, false
|
||||||
|
}
|
||||||
|
|
||||||
field_type_from_value :: proc(checker: ^Checker, expr: ast.Expr, base_type: types.Type) -> types.Type {
|
field_type_from_value :: proc(checker: ^Checker, expr: ast.Expr, base_type: types.Type) -> types.Type {
|
||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
field_name := symbol_text(checker, expr.name)
|
field_name := symbol_text(checker, expr.name)
|
||||||
@@ -5508,6 +5857,10 @@ build_compound_expr :: proc(
|
|||||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Try:
|
case .Try:
|
||||||
|
if checker.current_build_ctx != nil && checker.current_build_ctx.defer_depth > 0 {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "cannot 'try' inside a 'defer'")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||||
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
||||||
channel_type := checker.module.exprs[channel].type
|
channel_type := checker.module.exprs[channel].type
|
||||||
@@ -5532,11 +5885,18 @@ build_compound_expr :: proc(
|
|||||||
id := source.add(checker.diagnostics, expr.span, "'try' error channel cannot be widened to the enclosing error channel")
|
id := source.add(checker.diagnostics, expr.span, "'try' error channel cannot be widened to the enclosing error channel")
|
||||||
return invalid_hir_expr(checker, expr.span, id, success)
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
}
|
}
|
||||||
|
cleanup: []hir.Stmt_Id
|
||||||
|
captures: []hir.Expr_Id
|
||||||
|
if checker.current_build_ctx != nil {
|
||||||
|
cleanup, captures = try_cleanup(checker.current_build_ctx)
|
||||||
|
}
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Try,
|
kind=.Try,
|
||||||
span=expr.span,
|
span=expr.span,
|
||||||
type=success,
|
type=success,
|
||||||
left=channel,
|
left=channel,
|
||||||
|
body=cleanup,
|
||||||
|
args=captures,
|
||||||
target=hir.INVALID_REF,
|
target=hir.INVALID_REF,
|
||||||
right=hir.INVALID_EXPR,
|
right=hir.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
@@ -6701,17 +7061,51 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
|||||||
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
|
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replay the deferred statements in frames `[lo, len(defers))` into `body`,
|
// Replay eligible cleanup in `[lo, len(defers))` in LIFO order. Error exits run
|
||||||
// innermost-most-recent first (LIFO across frames); each frame's own statements
|
// both defer forms; other exits skip errdefer. A direct error return supplies its
|
||||||
// keep their forward order. Used at every scope-exit path in `build_block`.
|
// preserved payload so captured errors can be initialized before each cleanup.
|
||||||
flush_defers :: proc(ctx: ^Build_Ctx, body: ^[dynamic]hir.Stmt_Id, lo: int) {
|
flush_defers :: proc(
|
||||||
|
ctx: ^Build_Ctx,
|
||||||
|
body: ^[dynamic]hir.Stmt_Id,
|
||||||
|
lo: int,
|
||||||
|
error_exit := false,
|
||||||
|
error_value := hir.INVALID_EXPR,
|
||||||
|
) {
|
||||||
for i := len(ctx.defers^) - 1; i >= lo; i -= 1 {
|
for i := len(ctx.defers^) - 1; i >= lo; i -= 1 {
|
||||||
for stmt_id in ctx.defers^[i] {
|
entry := ctx.defers^[i]
|
||||||
|
if entry.error_only && !error_exit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if error_exit && entry.capture != hir.INVALID_LOCAL && error_value != hir.INVALID_EXPR {
|
||||||
|
append(body, hir.stmt_id(len(ctx.checker.module.statements)))
|
||||||
|
append(&ctx.checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Declaration, local=entry.capture, expr=error_value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for stmt_id in entry.body {
|
||||||
append(body, stmt_id)
|
append(body, stmt_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy the active error-exit cleanup onto a Try expression. Capture locals are
|
||||||
|
// initialized by lowering once the propagated error has been extracted/widened.
|
||||||
|
try_cleanup :: proc(ctx: ^Build_Ctx) -> ([]hir.Stmt_Id, []hir.Expr_Id) {
|
||||||
|
body: [dynamic]hir.Stmt_Id
|
||||||
|
body.allocator = ctx.checker.allocator
|
||||||
|
captures: [dynamic]hir.Expr_Id
|
||||||
|
captures.allocator = ctx.checker.allocator
|
||||||
|
for i := len(ctx.defers^) - 1; i >= 0; i -= 1 {
|
||||||
|
entry := ctx.defers^[i]
|
||||||
|
if entry.error_only && entry.capture != hir.INVALID_LOCAL {
|
||||||
|
append(&captures, hir.Expr_Id(entry.capture))
|
||||||
|
}
|
||||||
|
append(&body, ..entry.body)
|
||||||
|
}
|
||||||
|
return body[:], captures[:]
|
||||||
|
}
|
||||||
|
|
||||||
build_block :: proc(
|
build_block :: proc(
|
||||||
ctx: ^Build_Ctx,
|
ctx: ^Build_Ctx,
|
||||||
statements: []ast.Stmt_Id,
|
statements: []ast.Stmt_Id,
|
||||||
@@ -7144,6 +7538,8 @@ build_block :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value := hir.INVALID_EXPR
|
value := hir.INVALID_EXPR
|
||||||
|
error_exit := false
|
||||||
|
error_value := hir.INVALID_EXPR
|
||||||
if statement.value_control_flow {
|
if statement.value_control_flow {
|
||||||
if types.kind(ctx.result, store) == .Fallible {
|
if types.kind(ctx.result, store) == .Fallible {
|
||||||
success := types.fallible_success(ctx.result, store)
|
success := types.fallible_success(ctx.result, store)
|
||||||
@@ -7157,13 +7553,12 @@ build_block :: proc(
|
|||||||
} else if types.kind(ctx.result, store) == .Fallible {
|
} else if types.kind(ctx.result, store) == .Fallible {
|
||||||
success := types.fallible_success(ctx.result, store)
|
success := types.fallible_success(ctx.result, store)
|
||||||
error_type := types.fallible_error(ctx.result, store)
|
error_type := types.fallible_error(ctx.result, store)
|
||||||
error_path := false
|
|
||||||
expr_ast := checker.ast_module.exprs[statement.expr]
|
expr_ast := checker.ast_module.exprs[statement.expr]
|
||||||
if expr_ast.kind == .Enum_Literal {
|
if expr_ast.kind == .Enum_Literal {
|
||||||
success_has := types.sum_has_name(store, success, u32(expr_ast.name))
|
success_has := types.sum_has_name(store, success, u32(expr_ast.name))
|
||||||
error_has := types.sum_has_name(store, error_type, u32(expr_ast.name))
|
error_has := types.sum_has_name(store, error_type, u32(expr_ast.name))
|
||||||
if error_has && !success_has {
|
if error_has && !success_has {
|
||||||
error_path = true
|
error_exit = true
|
||||||
} else if error_has && success_has {
|
} else if error_has && success_has {
|
||||||
id := source.add(checker.diagnostics, expr_ast.span, "ambiguous fallible return member")
|
id := source.add(checker.diagnostics, expr_ast.span, "ambiguous fallible return member")
|
||||||
value = invalid_hir_expr(checker, expr_ast.span, id, ctx.result)
|
value = invalid_hir_expr(checker, expr_ast.span, id, ctx.result)
|
||||||
@@ -7172,7 +7567,7 @@ build_block :: proc(
|
|||||||
target_pkg, available := expr_package(checker, expr_ast, ctx.pkg, ctx.file, true)
|
target_pkg, available := expr_package(checker, expr_ast, ctx.pkg, ctx.file, true)
|
||||||
named := types.find_named(store, u32(target_pkg), u32(expr_ast.name), file=u32(expr_lookup_file(expr_ast, ctx.file))) if available else types.INVALID
|
named := types.find_named(store, u32(target_pkg), u32(expr_ast.name), file=u32(expr_lookup_file(expr_ast, ctx.file))) if available else types.INVALID
|
||||||
named = types.resolve_alias(named, store)
|
named = types.resolve_alias(named, store)
|
||||||
error_path = can_implicitly_convert_type(checker, named, error_type)
|
error_exit = can_implicitly_convert_type(checker, named, error_type)
|
||||||
} else {
|
} else {
|
||||||
probe := build_expr(
|
probe := build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
@@ -7181,22 +7576,38 @@ build_block :: proc(
|
|||||||
probe_type := checker.module.exprs[probe].type
|
probe_type := checker.module.exprs[probe].type
|
||||||
if can_implicitly_convert_type(checker, probe_type, error_type) &&
|
if can_implicitly_convert_type(checker, probe_type, error_type) &&
|
||||||
!can_implicitly_convert_type(checker, probe_type, success) {
|
!can_implicitly_convert_type(checker, probe_type, success) {
|
||||||
error_path = true
|
error_exit = true
|
||||||
value = probe
|
value = probe
|
||||||
} else if can_implicitly_convert_type(checker, probe_type, success) {
|
} else if can_implicitly_convert_type(checker, probe_type, success) {
|
||||||
value = probe
|
value = probe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value == hir.INVALID_EXPR {
|
if value == hir.INVALID_EXPR {
|
||||||
expected := error_type if error_path else success
|
expected := error_type if error_exit else success
|
||||||
value = build_expr(
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
expected, ctx.pkg, ctx.file,
|
expected, ctx.pkg, ctx.file,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
expected := error_type if error_path else success
|
expected := error_type if error_exit else success
|
||||||
value = coerce_expr(checker, value, expected, statement.span)
|
value = coerce_expr(checker, value, expected, statement.span)
|
||||||
value = fallible_aggregate(checker, statement.span, ctx.result, value, error_path)
|
if error_exit && len(ctx.defers^) > 0 && checker.module.exprs[value].kind != .Invalid {
|
||||||
|
tmp := append_tracked_local(
|
||||||
|
ctx.hir_locals, ctx.local_spans, ctx.local_used, ctx.local_warnable,
|
||||||
|
hir.Local{name=checker.sink_symbol, type=error_type, mutable=false}, source.Span{},
|
||||||
|
)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Declaration, span=statement.span, local=tmp, expr=value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
error_value = hir.expr_id(len(checker.module.exprs))
|
||||||
|
append(&checker.module.exprs, hir.Expr{
|
||||||
|
kind=.Local, span=statement.span, type=error_type, target=hir.local_ref(tmp),
|
||||||
|
})
|
||||||
|
value = error_value
|
||||||
|
}
|
||||||
|
value = fallible_aggregate(checker, statement.span, ctx.result, value, error_exit)
|
||||||
} else {
|
} else {
|
||||||
value = build_expr(
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
@@ -7210,7 +7621,7 @@ build_block :: proc(
|
|||||||
// can't change what is returned — Zig evaluates the return value, then
|
// can't change what is returned — Zig evaluates the return value, then
|
||||||
// runs defers.
|
// runs defers.
|
||||||
if len(ctx.defers^) > 0 {
|
if len(ctx.defers^) > 0 {
|
||||||
if checker.module.exprs[value].kind != .Invalid {
|
if !error_exit && checker.module.exprs[value].kind != .Invalid {
|
||||||
tmp := append_tracked_local(
|
tmp := append_tracked_local(
|
||||||
ctx.hir_locals,
|
ctx.hir_locals,
|
||||||
ctx.local_spans,
|
ctx.local_spans,
|
||||||
@@ -7229,7 +7640,7 @@ build_block :: proc(
|
|||||||
kind = .Local, span = statement.span, type = ctx.result, target = hir.local_ref(tmp),
|
kind = .Local, span = statement.span, type = ctx.result, target = hir.local_ref(tmp),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
flush_defers(ctx, &body, 0)
|
flush_defers(ctx, &body, 0, error_exit, error_value)
|
||||||
}
|
}
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
@@ -7753,16 +8164,37 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if statement.error_only && types.kind(ctx.result, store) != .Fallible {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "'errdefer' requires an enclosing fallible function")
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Trap, span=statement.span, expr=hir.INVALID_EXPR,
|
||||||
|
local=hir.INVALID_LOCAL, diagnostic=id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Build the deferred statement once, guarded so a `return` inside it is
|
// Build the deferred statement once, guarded so a `return` inside it is
|
||||||
// rejected and `break`/`continue` only target loops opened within the
|
// rejected and `break`/`continue` only target loops opened within the
|
||||||
// defer; its hir is replayed at each scope exit, not emitted here.
|
// defer; its hir is replayed at each scope exit, not emitted here.
|
||||||
|
capture_start := len(ctx.locals^)
|
||||||
|
capture := hir.INVALID_LOCAL
|
||||||
|
if statement.error_only && len(statement.captures) > 0 &&
|
||||||
|
statement.captures[0] != checker.sink_symbol {
|
||||||
|
capture = append_build_local(
|
||||||
|
ctx, statement.captures[0], types.fallible_error(ctx.result, store), false, statement.span,
|
||||||
|
)
|
||||||
|
}
|
||||||
saved_floor := ctx.loop_floor
|
saved_floor := ctx.loop_floor
|
||||||
ctx.defer_depth += 1
|
ctx.defer_depth += 1
|
||||||
ctx.loop_floor = len(ctx.loop_defer_starts^)
|
ctx.loop_floor = len(ctx.loop_defer_starts^)
|
||||||
entry := build_block(ctx, []ast.Stmt_Id{statement.update})
|
entry := build_block(ctx, []ast.Stmt_Id{statement.update})
|
||||||
ctx.loop_floor = saved_floor
|
ctx.loop_floor = saved_floor
|
||||||
ctx.defer_depth -= 1
|
ctx.defer_depth -= 1
|
||||||
append(ctx.defers, entry)
|
resize(ctx.locals, capture_start)
|
||||||
|
append(ctx.defers, Defer_Entry{
|
||||||
|
body=entry, error_only=statement.error_only, capture=capture,
|
||||||
|
})
|
||||||
case .Match:
|
case .Match:
|
||||||
build_match(ctx, &body, statement)
|
build_match(ctx, &body, statement)
|
||||||
case .Match_Arm:
|
case .Match_Arm:
|
||||||
@@ -7795,7 +8227,7 @@ build_block :: proc(
|
|||||||
// Free this block's deferred-statement entry slices (their stmt ids were
|
// Free this block's deferred-statement entry slices (their stmt ids were
|
||||||
// already replayed at every path that can leave this block) and pop the frame.
|
// already replayed at every path that can leave this block) and pop the frame.
|
||||||
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
||||||
delete(ctx.defers^[i], checker.allocator)
|
delete(ctx.defers^[i].body, checker.allocator)
|
||||||
}
|
}
|
||||||
resize(ctx.defers, defer_start)
|
resize(ctx.defers, defer_start)
|
||||||
resize(ctx.locals, scope_start)
|
resize(ctx.locals, scope_start)
|
||||||
@@ -7887,7 +8319,7 @@ build_value_block :: proc(
|
|||||||
}
|
}
|
||||||
// Close the scope (build_block left it open for us).
|
// Close the scope (build_block left it open for us).
|
||||||
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
||||||
delete(ctx.defers^[i], checker.allocator)
|
delete(ctx.defers^[i].body, checker.allocator)
|
||||||
}
|
}
|
||||||
resize(ctx.defers, defer_start)
|
resize(ctx.defers, defer_start)
|
||||||
resize(ctx.locals, scope_start)
|
resize(ctx.locals, scope_start)
|
||||||
@@ -8924,7 +9356,7 @@ block_element_type :: proc(ctx: ^Build_Ctx, block_stmts: []ast.Stmt_Id) -> types
|
|||||||
result := checker.module.exprs[probe].type if checker.module.exprs[probe].kind != .Invalid else types.INVALID
|
result := checker.module.exprs[probe].type if checker.module.exprs[probe].kind != .Invalid else types.INVALID
|
||||||
// Discard the throwaway leading build's scope (its hir stmts/locals are dead but stable).
|
// Discard the throwaway leading build's scope (its hir stmts/locals are dead but stable).
|
||||||
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
||||||
delete(ctx.defers^[i], checker.allocator)
|
delete(ctx.defers^[i].body, checker.allocator)
|
||||||
}
|
}
|
||||||
resize(ctx.defers, defer_start)
|
resize(ctx.defers, defer_start)
|
||||||
resize(ctx.locals, scope_start)
|
resize(ctx.locals, scope_start)
|
||||||
@@ -9310,7 +9742,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
defers: [dynamic][]hir.Stmt_Id
|
defers: [dynamic]Defer_Entry
|
||||||
defers.allocator = checker.allocator
|
defers.allocator = checker.allocator
|
||||||
loop_defer_starts: [dynamic]int
|
loop_defer_starts: [dynamic]int
|
||||||
loop_defer_starts.allocator = checker.allocator
|
loop_defer_starts.allocator = checker.allocator
|
||||||
@@ -9392,7 +9824,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
for entry in defers {
|
for entry in defers {
|
||||||
delete(entry, checker.allocator)
|
delete(entry.body, checker.allocator)
|
||||||
}
|
}
|
||||||
delete(defers)
|
delete(defers)
|
||||||
delete(loop_defer_starts)
|
delete(loop_defer_starts)
|
||||||
@@ -9856,6 +10288,11 @@ check :: proc(
|
|||||||
checker.global_open_const = make([]bool, len(ast_module.globals), allocator)
|
checker.global_open_const = make([]bool, len(ast_module.globals), allocator)
|
||||||
checker.global_open_float = make([]bool, len(ast_module.globals), allocator)
|
checker.global_open_float = make([]bool, len(ast_module.globals), allocator)
|
||||||
checker.global_const_value = make([]i128, len(ast_module.globals), allocator)
|
checker.global_const_value = make([]i128, len(ast_module.globals), allocator)
|
||||||
|
checker.record_field_constraints = make([]types.Type, len(checker.module.types.fields), allocator)
|
||||||
|
checker.record_field_defaults = make([]types.Type, len(checker.module.types.fields), allocator)
|
||||||
|
checker.record_field_conflicts = make([]types.Type, len(checker.module.types.fields), allocator)
|
||||||
|
checker.record_field_conflict_spans = make([]source.Span, len(checker.module.types.fields), allocator)
|
||||||
|
init_record_field_inference(&checker)
|
||||||
checker.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
|
checker.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
|
||||||
checker.external_global_diagnostics = make([]source.Diagnostic_Id, len(ast_module.globals), allocator)
|
checker.external_global_diagnostics = make([]source.Diagnostic_Id, len(ast_module.globals), allocator)
|
||||||
for &canonical in checker.external_global_canonical {
|
for &canonical in checker.external_global_canonical {
|
||||||
@@ -9883,6 +10320,10 @@ check :: proc(
|
|||||||
delete(checker.global_open_const, allocator)
|
delete(checker.global_open_const, allocator)
|
||||||
delete(checker.global_open_float, allocator)
|
delete(checker.global_open_float, allocator)
|
||||||
delete(checker.global_const_value, allocator)
|
delete(checker.global_const_value, allocator)
|
||||||
|
delete(checker.record_field_constraints, allocator)
|
||||||
|
delete(checker.record_field_defaults, allocator)
|
||||||
|
delete(checker.record_field_conflicts, allocator)
|
||||||
|
delete(checker.record_field_conflict_spans, allocator)
|
||||||
delete(checker.external_global_canonical, allocator)
|
delete(checker.external_global_canonical, allocator)
|
||||||
delete(checker.external_global_diagnostics, allocator)
|
delete(checker.external_global_diagnostics, allocator)
|
||||||
delete(checker.constants, allocator)
|
delete(checker.constants, allocator)
|
||||||
@@ -9943,6 +10384,7 @@ check :: proc(
|
|||||||
validate_declarations(&checker)
|
validate_declarations(&checker)
|
||||||
configure_io_main(&checker)
|
configure_io_main(&checker)
|
||||||
infer_all(&checker)
|
infer_all(&checker)
|
||||||
|
finalize_record_field_inference(&checker)
|
||||||
validate_external_globals(&checker)
|
validate_external_globals(&checker)
|
||||||
prune_specs(&checker)
|
prune_specs(&checker)
|
||||||
build_globals(&checker)
|
build_globals(&checker)
|
||||||
|
|||||||
@@ -290,7 +290,8 @@ Ct_State :: struct {
|
|||||||
places: [dynamic]Ct_Place,
|
places: [dynamic]Ct_Place,
|
||||||
paths: [dynamic]Ct_Path_Elem,
|
paths: [dynamic]Ct_Path_Elem,
|
||||||
bindings: [dynamic]Ct_Binding,
|
bindings: [dynamic]Ct_Binding,
|
||||||
defers: [dynamic]ast.Stmt_Id,
|
defers: [dynamic]Ct_Defer,
|
||||||
|
defer_depth: int,
|
||||||
steps: int,
|
steps: int,
|
||||||
error: Ct_Error_Kind,
|
error: Ct_Error_Kind,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
@@ -298,6 +299,12 @@ Ct_State :: struct {
|
|||||||
demanded: ^[dynamic]Spec_Id,
|
demanded: ^[dynamic]Spec_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ct_Defer :: struct {
|
||||||
|
statement: ast.Stmt_Id,
|
||||||
|
error_only: bool,
|
||||||
|
capture: symbol.Id,
|
||||||
|
}
|
||||||
|
|
||||||
ct_state_make :: proc(
|
ct_state_make :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
@@ -2217,6 +2224,9 @@ ct_clone_value :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ct_eval_try_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
ct_eval_try_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||||
|
if state.defer_depth > 0 {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "cannot 'try' inside a 'defer'")
|
||||||
|
}
|
||||||
checker := state.checker
|
checker := state.checker
|
||||||
channel, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
channel, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
@@ -2465,7 +2475,17 @@ ct_exec_statements :: proc(
|
|||||||
case .Block:
|
case .Block:
|
||||||
flow, ok = ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
flow, ok = ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
||||||
case .Defer:
|
case .Defer:
|
||||||
append(&state.defers, statement.update)
|
if statement.error_only && types.kind(state.result, &checker.module.types) != .Fallible {
|
||||||
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'errdefer' requires an enclosing fallible function")
|
||||||
|
} else {
|
||||||
|
capture := symbol.INVALID
|
||||||
|
if len(statement.captures) > 0 {
|
||||||
|
capture = statement.captures[0]
|
||||||
|
}
|
||||||
|
append(&state.defers, Ct_Defer{
|
||||||
|
statement=statement.update, error_only=statement.error_only, capture=capture,
|
||||||
|
})
|
||||||
|
}
|
||||||
case .Match:
|
case .Match:
|
||||||
flow, ok = ct_exec_match(state, statement, yield_returns, depth+1)
|
flow, ok = ct_exec_match(state, statement, yield_returns, depth+1)
|
||||||
case .Match_Arm:
|
case .Match_Arm:
|
||||||
@@ -2477,22 +2497,50 @@ ct_exec_statements :: proc(
|
|||||||
return flow, false
|
return flow, false
|
||||||
}
|
}
|
||||||
if flow.kind != .Normal {
|
if flow.kind != .Normal {
|
||||||
if !ct_flush_defers(state, defer_start, depth+1) {
|
if !ct_flush_defers(state, defer_start, flow, depth+1) {
|
||||||
return flow, false
|
return flow, false
|
||||||
}
|
}
|
||||||
return flow, true
|
return flow, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ct_flush_defers(state, defer_start, depth+1) {
|
if !ct_flush_defers(state, defer_start, ct_flow(.Normal), depth+1) {
|
||||||
return ct_flow(.Normal), false
|
return ct_flow(.Normal), false
|
||||||
}
|
}
|
||||||
return ct_flow(.Normal), true
|
return ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
|
|
||||||
ct_flush_defers :: proc(state: ^Ct_State, start: int, depth: int) -> bool {
|
ct_flush_defers :: proc(state: ^Ct_State, start: int, exit: Ct_Flow, depth: int) -> bool {
|
||||||
|
error_exit := false
|
||||||
|
error_value := INVALID_CT_VALUE
|
||||||
|
if exit.kind == .Return && exit.value != INVALID_CT_VALUE && int(exit.value) < len(state.values) {
|
||||||
|
returned := state.values[exit.value]
|
||||||
|
if returned.kind == .Fallible && returned.active != 0 {
|
||||||
|
error_exit = true
|
||||||
|
children := ct_child_slice(state, returned)
|
||||||
|
if len(children) > 0 {
|
||||||
|
error_value = children[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for index := len(state.defers) - 1; index >= start; index -= 1 {
|
for index := len(state.defers) - 1; index >= start; index -= 1 {
|
||||||
stmt := [1]ast.Stmt_Id{state.defers[index]}
|
entry := state.defers[index]
|
||||||
|
if entry.error_only && !error_exit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
binding_start := len(state.bindings)
|
||||||
|
bound_capture := false
|
||||||
|
if entry.error_only && symbol.is_valid(entry.capture) &&
|
||||||
|
entry.capture != state.checker.sink_symbol && error_value != INVALID_CT_VALUE {
|
||||||
|
ct_bind_value(state, entry.capture, state.values[error_value].type, error_value, false)
|
||||||
|
bound_capture = true
|
||||||
|
}
|
||||||
|
stmt := [1]ast.Stmt_Id{entry.statement}
|
||||||
|
state.defer_depth += 1
|
||||||
flow, ok := ct_exec_statements(state, stmt[:], false, depth+1)
|
flow, ok := ct_exec_statements(state, stmt[:], false, depth+1)
|
||||||
|
state.defer_depth -= 1
|
||||||
|
if bound_capture {
|
||||||
|
ct_pop_bindings(state, binding_start)
|
||||||
|
}
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,10 +136,13 @@ Expr :: struct {
|
|||||||
span: source.Span,
|
span: source.Span,
|
||||||
type: types.Type,
|
type: types.Type,
|
||||||
integer: i64,
|
integer: i64,
|
||||||
|
// Aggregate/call children normally; Try stores errdefer capture local IDs
|
||||||
|
// encoded as Expr_Id because it otherwise has no args.
|
||||||
args: []Expr_Id,
|
args: []Expr_Id,
|
||||||
// `Catch` block handlers use `body` for the handler statements and `target`
|
// `Catch` block handlers use `body` for the handler statements and `target`
|
||||||
// for the optional captured error local. A missing `right` means the handler
|
// for the optional captured error local. A missing `right` means the handler
|
||||||
// exits on every path and therefore has no fallback value.
|
// exits on every path and therefore has no fallback value. `Try` uses `body`
|
||||||
|
// for active error-exit cleanup.
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
target: Ref,
|
target: Ref,
|
||||||
left: Expr_Id,
|
left: Expr_Id,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
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
|
||||||
|
case "errdefer": return .Keyword_Errdefer
|
||||||
case "yield": return .Keyword_Yield
|
case "yield": return .Keyword_Yield
|
||||||
case "match": return .Keyword_Match
|
case "match": return .Keyword_Match
|
||||||
case "else": return .Keyword_Else
|
case "else": return .Keyword_Else
|
||||||
|
|||||||
@@ -416,10 +416,11 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
})
|
})
|
||||||
if expr.kind == .Try {
|
if expr.kind == .Try {
|
||||||
result := channel
|
result := channel
|
||||||
if !types.equal(channel_type, state.func_result) {
|
error_value := ir.INVALID_INSTRUCTION
|
||||||
|
if !types.equal(channel_type, state.func_result) || len(expr.args) > 0 {
|
||||||
error_type := types.fallible_error(channel_type, &state.hir_module.types)
|
error_type := types.fallible_error(channel_type, &state.hir_module.types)
|
||||||
enclosing_error := types.fallible_error(state.func_result, &state.hir_module.types)
|
enclosing_error := types.fallible_error(state.func_result, &state.hir_module.types)
|
||||||
error_value := append_instruction(state, ir.Instruction{
|
error_value = append_instruction(state, ir.Instruction{
|
||||||
op=.Fallible_Error, span=expr.span, type=error_type,
|
op=.Fallible_Error, span=expr.span, type=error_type,
|
||||||
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
@@ -431,6 +432,7 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if !types.equal(channel_type, state.func_result) {
|
||||||
args := make([]ir.Instruction_Id, 1, state.allocator)
|
args := make([]ir.Instruction_Id, 1, state.allocator)
|
||||||
args[0] = error_value
|
args[0] = error_value
|
||||||
result = append_instruction(state, ir.Instruction{
|
result = append_instruction(state, ir.Instruction{
|
||||||
@@ -439,6 +441,27 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
for encoded_capture in expr.args {
|
||||||
|
capture := hir.Local_Id(encoded_capture)
|
||||||
|
if capture == hir.INVALID_LOCAL || int(capture) >= len(state.func_locals) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
error_type := state.func_locals[capture].type
|
||||||
|
capture_slot := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Alloca, span=expr.span, type=error_type,
|
||||||
|
target=ir.local_ref(ir.Local_Id(capture)),
|
||||||
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
state.local_slots[capture] = capture_slot
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=expr.span, type=error_type,
|
||||||
|
target=ir.INVALID_REF, a=capture_slot, b=error_value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
lower_statements(state, expr.body)
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Return, span=expr.span, type=state.func_result,
|
op=.Return, span=expr.span, type=state.func_result,
|
||||||
target=ir.INVALID_REF, a=result, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=result, b=ir.INVALID_INSTRUCTION,
|
||||||
|
|||||||
@@ -1410,18 +1410,44 @@ parse_block_statement :: proc(parser: ^Parser, label := symbol.INVALID) -> ast.S
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// `defer <statement>` runs the statement when the enclosing scope exits. The
|
// `defer <statement>` runs whenever the enclosing scope exits; `errdefer`
|
||||||
// statement may be a block (`defer { ... }`). The checker rejects deferring a
|
// runs only when it exits through a function error and may capture that error.
|
||||||
// `return`/`break`/`continue`/`defer`.
|
|
||||||
parse_defer :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
parse_defer :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||||
marker := advance(parser) // consume 'defer'
|
marker := advance(parser)
|
||||||
|
error_only := marker.kind == .Keyword_Errdefer
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
captures: []symbol.Id
|
||||||
|
if error_only {
|
||||||
|
if _, ok := allow(parser, .Pipe); ok {
|
||||||
|
capture := current(parser)
|
||||||
|
if capture.kind != .Identifier && capture.kind != .Underscore {
|
||||||
|
source.add(parser.diagnostics, capture.span, "expected an errdefer capture name")
|
||||||
|
} else {
|
||||||
|
advance(parser)
|
||||||
|
captures = make([]symbol.Id, 1, parser.module.allocator)
|
||||||
|
captures[0] = capture.symbol
|
||||||
|
}
|
||||||
|
if current(parser).kind == .Comma {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "'errdefer' accepts exactly one capture")
|
||||||
|
for current(parser).kind != .Pipe && 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 '|' after errdefer capture")
|
||||||
|
}
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
inner := parse_statement(parser)
|
inner := parse_statement(parser)
|
||||||
id := ast.stmt_id(len(parser.module.statements))
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=.Defer,
|
kind=.Defer,
|
||||||
span=marker.span,
|
span=marker.span,
|
||||||
update=inner,
|
update=inner,
|
||||||
|
error_only=error_only,
|
||||||
|
captures=captures,
|
||||||
expr=ast.INVALID_EXPR,
|
expr=ast.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
@@ -1486,7 +1512,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
if current(parser).kind == .Keyword_Continue {
|
if current(parser).kind == .Keyword_Continue {
|
||||||
return parse_loop_control(parser, .Continue)
|
return parse_loop_control(parser, .Continue)
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Keyword_Defer {
|
if current(parser).kind == .Keyword_Defer || current(parser).kind == .Keyword_Errdefer {
|
||||||
return parse_defer(parser)
|
return parse_defer(parser)
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Keyword_Yield {
|
if current(parser).kind == .Keyword_Yield {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_Break,
|
Keyword_Break,
|
||||||
Keyword_Continue,
|
Keyword_Continue,
|
||||||
Keyword_Defer,
|
Keyword_Defer,
|
||||||
|
Keyword_Errdefer,
|
||||||
Keyword_Yield,
|
Keyword_Yield,
|
||||||
Keyword_Match,
|
Keyword_Match,
|
||||||
Keyword_Else,
|
Keyword_Else,
|
||||||
|
|||||||
@@ -3747,8 +3747,13 @@ defer_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
bad_defer_return()
|
bad_defer_return()
|
||||||
bad_defer_break()
|
bad_defer_break()
|
||||||
bad_return_in_defer()
|
bad_return_in_defer()
|
||||||
|
bad_errdefer_nonfallible()
|
||||||
|
_ = bad_try_in_defer() catch 0
|
||||||
|
_ = bad_try_in_errdefer() catch 0
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
Failure :: enum { bad }
|
||||||
|
fail func() i32 ! Failure { return .bad }
|
||||||
bad_defer_return func() void {
|
bad_defer_return func() void {
|
||||||
defer return
|
defer return
|
||||||
}
|
}
|
||||||
@@ -3763,6 +3768,17 @@ bad_return_in_defer func() void {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bad_errdefer_nonfallible func() void {
|
||||||
|
errdefer {}
|
||||||
|
}
|
||||||
|
bad_try_in_defer func() i32 ! Failure {
|
||||||
|
defer _ = try fail()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
bad_try_in_errdefer func() i32 ! Failure {
|
||||||
|
errdefer _ = try fail()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
`
|
`
|
||||||
source_file := source.Source{path="test.bro", text=text}
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
diagnostics := source.init_diagnostics(&source_file)
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
@@ -3779,14 +3795,50 @@ bad_return_in_defer func() void {
|
|||||||
defer_return := false
|
defer_return := false
|
||||||
defer_break := false
|
defer_break := false
|
||||||
return_in_defer := false
|
return_in_defer := false
|
||||||
|
errdefer_nonfallible := false
|
||||||
|
try_in_defer := 0
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
defer_return = defer_return || strings.contains(diagnostic.message, "cannot defer a 'return' statement")
|
defer_return = defer_return || strings.contains(diagnostic.message, "cannot defer a 'return' statement")
|
||||||
defer_break = defer_break || strings.contains(diagnostic.message, "cannot defer a 'break' statement")
|
defer_break = defer_break || strings.contains(diagnostic.message, "cannot defer a 'break' statement")
|
||||||
return_in_defer = return_in_defer || strings.contains(diagnostic.message, "cannot 'return' inside a 'defer'")
|
return_in_defer = return_in_defer || strings.contains(diagnostic.message, "cannot 'return' inside a 'defer'")
|
||||||
|
errdefer_nonfallible = errdefer_nonfallible || strings.contains(diagnostic.message, "'errdefer' requires an enclosing fallible function")
|
||||||
|
try_in_defer += 1 if strings.contains(diagnostic.message, "cannot 'try' inside a 'defer'") else 0
|
||||||
}
|
}
|
||||||
testing.expect(t, defer_return)
|
testing.expect(t, defer_return)
|
||||||
testing.expect(t, defer_break)
|
testing.expect(t, defer_break)
|
||||||
testing.expect(t, return_in_defer)
|
testing.expect(t, return_in_defer)
|
||||||
|
testing.expect(t, errdefer_nonfallible)
|
||||||
|
testing.expect_value(t, try_in_defer, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
errdefer_capture_syntax_is_diagnosed :: proc(t: ^testing.T) {
|
||||||
|
text := `Failure :: enum { bad }
|
||||||
|
bad func() i32 ! Failure {
|
||||||
|
errdefer || {}
|
||||||
|
errdefer |first, second| {}
|
||||||
|
return .bad
|
||||||
|
}
|
||||||
|
main func() i32 { return bad() catch 0 }
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
missing := false
|
||||||
|
multiple := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
missing = missing || strings.contains(diagnostic.message, "expected an errdefer capture name")
|
||||||
|
multiple = multiple || strings.contains(diagnostic.message, "'errdefer' accepts exactly one capture")
|
||||||
|
}
|
||||||
|
testing.expect(t, missing)
|
||||||
|
testing.expect(t, multiple)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -7411,6 +7463,7 @@ main func() void { value Bad(i32) = undefined; _ = &value }
|
|||||||
symbol.destroy_table(&symbols)
|
symbol.destroy_table(&symbols)
|
||||||
source.destroy_diagnostics(&diagnostics)
|
source.destroy_diagnostics(&diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -7880,6 +7933,7 @@ parser_diagnoses_malformed_conditional_unwrap_captures_and_guards :: proc(t: ^te
|
|||||||
symbol.destroy_table(&symbols)
|
symbol.destroy_table(&symbols)
|
||||||
source.destroy_diagnostics(&diagnostics)
|
source.destroy_diagnostics(&diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -10946,3 +11000,218 @@ main func() void {
|
|||||||
}
|
}
|
||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
named_record_field_type :: proc(
|
||||||
|
module: ^hir.Module,
|
||||||
|
symbols: ^symbol.Table,
|
||||||
|
record_name, field_name: string,
|
||||||
|
) -> (types.Type, bool) {
|
||||||
|
record := types.find_named(&module.types, 0, u32(symbol.intern(symbols, record_name)))
|
||||||
|
if !types.is_record(record, &module.types) {
|
||||||
|
return types.INVALID, false
|
||||||
|
}
|
||||||
|
field_symbol := symbol.intern(symbols, field_name)
|
||||||
|
for field in types.fields_for(&module.types, record) {
|
||||||
|
if field.name == u32(field_symbol) {
|
||||||
|
return field.type, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.INVALID, false
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
native_record_constraint_fields_resolve_program_wide :: proc(t: ^testing.T) {
|
||||||
|
text := `Token :: struct { start int }
|
||||||
|
Backward :: struct { start int }
|
||||||
|
Wide :: struct { value int }
|
||||||
|
Literal :: struct { value int }
|
||||||
|
Measurement :: struct {
|
||||||
|
ratio float
|
||||||
|
span range
|
||||||
|
}
|
||||||
|
Payload :: union { count int }
|
||||||
|
|
||||||
|
take_usize func(value usize) usize { return value }
|
||||||
|
|
||||||
|
exercise func(cursor usize, narrow i8, wider i16, count i32) i32 {
|
||||||
|
token Token = Token{start = cursor}
|
||||||
|
wide Wide = Wide{value = narrow}
|
||||||
|
wide.value = wider
|
||||||
|
small Literal = Literal{value = 1}
|
||||||
|
large Literal = Literal{value = 1000}
|
||||||
|
backward Backward = Backward{start = 1}
|
||||||
|
measurement Measurement = Measurement{ratio = 1.5, span = 0..3}
|
||||||
|
payload Payload = Payload{count = count}
|
||||||
|
_ = token.start
|
||||||
|
_ = wide.value
|
||||||
|
_ = small.value
|
||||||
|
_ = large.value
|
||||||
|
_ = take_usize(backward.start)
|
||||||
|
_ = measurement.ratio
|
||||||
|
_ = measurement.span
|
||||||
|
_ = payload.count
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 { return exercise(7, 1, 1000, 3) }
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="record_constraints.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)
|
||||||
|
|
||||||
|
token_start, token_ok := named_record_field_type(&hir_module, &symbols, "Token", "start")
|
||||||
|
backward_start, backward_ok := named_record_field_type(&hir_module, &symbols, "Backward", "start")
|
||||||
|
wide_value, wide_ok := named_record_field_type(&hir_module, &symbols, "Wide", "value")
|
||||||
|
literal_value, literal_ok := named_record_field_type(&hir_module, &symbols, "Literal", "value")
|
||||||
|
ratio, ratio_ok := named_record_field_type(&hir_module, &symbols, "Measurement", "ratio")
|
||||||
|
span, span_ok := named_record_field_type(&hir_module, &symbols, "Measurement", "span")
|
||||||
|
count, count_ok := named_record_field_type(&hir_module, &symbols, "Payload", "count")
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, token_ok && backward_ok && wide_ok && literal_ok && ratio_ok && span_ok && count_ok)
|
||||||
|
testing.expect_value(t, token_start, types.USIZE)
|
||||||
|
testing.expect_value(t, backward_start, types.USIZE)
|
||||||
|
testing.expect_value(t, wide_value, types.I16)
|
||||||
|
testing.expect_value(t, literal_value, types.I16)
|
||||||
|
testing.expect_value(t, ratio, types.F64)
|
||||||
|
testing.expect(t, types.is_range(span, &hir_module.types))
|
||||||
|
testing.expect_value(t, types.child_type(span, &hir_module.types), types.I8)
|
||||||
|
testing.expect_value(t, count, types.I32)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
native_record_int_field_compiles_and_runs_as_usize :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-record-field-usize"
|
||||||
|
main_path := "/tmp/brolang-test-record-field-usize/main.bro"
|
||||||
|
output := "/tmp/brolang-test-record-field-usize-output"
|
||||||
|
text := `Token :: struct { start int }
|
||||||
|
main func() i32 {
|
||||||
|
cursor usize = 7
|
||||||
|
token Token = Token{start = cursor}
|
||||||
|
if (token.start != cursor) return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
native_record_constraint_fields_report_unresolved_and_conflicting_demands :: proc(t: ^testing.T) {
|
||||||
|
cases := [2]struct {
|
||||||
|
text: string,
|
||||||
|
needle: string,
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
text = `Token :: struct { start int }
|
||||||
|
main func() void {}
|
||||||
|
`,
|
||||||
|
needle = "could not resolve the 'int' constraint for field 'Token.start'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = `Token :: struct { start int }
|
||||||
|
use func(signed i32, unsigned usize) void {
|
||||||
|
a Token = Token{start = signed}
|
||||||
|
b Token = Token{start = unsigned}
|
||||||
|
_ = a
|
||||||
|
_ = b
|
||||||
|
}
|
||||||
|
main func() void { use(1, 2) }
|
||||||
|
`,
|
||||||
|
needle = "conflicting types i32 and usize for field 'Token.start' declared as 'int'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for test_case in cases {
|
||||||
|
source_file := source.Source{path="bad_record_constraint.bro", text=test_case.text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||||
|
|
||||||
|
matching := 0
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
matching += 1 if strings.contains(diagnostic.message, test_case.needle) else 0
|
||||||
|
}
|
||||||
|
testing.expect_value(t, matching, 1)
|
||||||
|
testing.expect(t, len(llvm_text) > 0)
|
||||||
|
|
||||||
|
delete(llvm_text)
|
||||||
|
ir.destroy_module(&ir_module)
|
||||||
|
hir.destroy_module(&hir_module)
|
||||||
|
ast.destroy_module(&ast_module)
|
||||||
|
delete(stream.items)
|
||||||
|
symbol.destroy_table(&symbols)
|
||||||
|
source.destroy_diagnostics(&diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
directory := "/tmp/brolang-test-record-field-conflict"
|
||||||
|
main_path := "/tmp/brolang-test-record-field-conflict/main.bro"
|
||||||
|
output := "/tmp/brolang-test-record-field-conflict-output"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)cases[1].text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
constraint_fields_remain_rejected_outside_named_native_records :: proc(t: ^testing.T) {
|
||||||
|
text := `BadC :: c_struct { value int }
|
||||||
|
BadNested :: struct { values []int }
|
||||||
|
|
||||||
|
make_type func() type {
|
||||||
|
return struct { value int }
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() void {
|
||||||
|
Generated :: @make_type()
|
||||||
|
_ = Generated
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="excluded_record_constraints.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)
|
||||||
|
|
||||||
|
record_errors := 0
|
||||||
|
anonymous_error := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
record_errors += 1 if strings.contains(diagnostic.message, "record fields must have runtime value types") else 0
|
||||||
|
anonymous_error = anonymous_error || strings.contains(diagnostic.message, "anonymous struct field 'value' requires a concrete runtime type")
|
||||||
|
}
|
||||||
|
c_field, c_ok := named_record_field_type(&hir_module, &symbols, "BadC", "value")
|
||||||
|
nested_field, nested_ok := named_record_field_type(&hir_module, &symbols, "BadNested", "values")
|
||||||
|
testing.expect_value(t, record_errors, 1)
|
||||||
|
testing.expect(t, anonymous_error)
|
||||||
|
testing.expect(t, c_ok && nested_ok)
|
||||||
|
testing.expect_value(t, c_field, types.INT)
|
||||||
|
testing.expect(t, types.is_slice(nested_field, &hir_module.types))
|
||||||
|
testing.expect_value(t, types.child_type(nested_field, &hir_module.types), types.INT)
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,6 +91,36 @@ use_try func() i32 ! Error {
|
|||||||
return value + 1
|
return value + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct_errdefer func(fail bool) i32 ! Error {
|
||||||
|
trace i32 = 0
|
||||||
|
defer trace = trace * 10 + 1
|
||||||
|
errdefer |err| {
|
||||||
|
if (err == .bad) trace = trace * 10 + 2
|
||||||
|
}
|
||||||
|
defer trace = trace * 10 + 3
|
||||||
|
if (fail) return .bad
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_try_errdefer func() i32 ! Error {
|
||||||
|
trace i32 = 0
|
||||||
|
defer trace = trace * 10 + 4
|
||||||
|
errdefer |err| {
|
||||||
|
if (err == .bad) trace = trace * 10 + 5
|
||||||
|
}
|
||||||
|
return try may_fail(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_errdefer_check func() i32 {
|
||||||
|
ok i32 :: ct_errdefer(false) catch 0
|
||||||
|
if (ok != 7) return 1
|
||||||
|
explicit_error i32 :: ct_errdefer(true) catch 9
|
||||||
|
if (explicit_error != 9) return 2
|
||||||
|
try_error i32 :: ct_try_errdefer() catch 9
|
||||||
|
if (try_error != 9) return 3
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
|
||||||
recover func() i32 {
|
recover func() i32 {
|
||||||
return may_fail(true) catch |e| {
|
return may_fail(true) catch |e| {
|
||||||
match e {
|
match e {
|
||||||
@@ -135,6 +165,7 @@ storage_mutation func() i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GLOBAL :: $sum_loop(4)
|
GLOBAL :: $sum_loop(4)
|
||||||
|
ERRDEFER :: $ct_errdefer_check()
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
point Point :: $make_point()
|
point Point :: $make_point()
|
||||||
@@ -205,5 +236,8 @@ main func() i32 {
|
|||||||
if fallible_err != 13 {
|
if fallible_err != 13 {
|
||||||
return 16
|
return 16
|
||||||
}
|
}
|
||||||
|
if ERRDEFER != 42 {
|
||||||
|
return 17
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Milestone 19: `defer` and bare block statements.
|
# Milestone 19: `defer`, `errdefer`, and bare block statements.
|
||||||
#
|
#
|
||||||
# `defer <stmt>` runs the statement when the enclosing scope exits, in reverse
|
# `defer <stmt>` runs the statement when the enclosing scope exits, in reverse
|
||||||
# (LIFO) order, on every exit path. A bare `{ ... }` introduces a scope. Each
|
# (LIFO) order, on every exit path. A bare `{ ... }` introduces a scope. Each
|
||||||
@@ -24,6 +24,66 @@ enclosing_defer_check func() i32 {
|
|||||||
return v # 0->1 (i=0 fall-through), ->2 (i=1 break); the +100 runs after capture
|
return v # 0->1 (i=0 fall-through), ->2 (i=1 break); the +100 runs after capture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CleanupError :: enum {
|
||||||
|
bad
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtraError :: enum {
|
||||||
|
extra
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanupErrors :: alias CleanupError | ExtraError
|
||||||
|
|
||||||
|
explicit_cleanup func(fail bool, trace *mut i32) i32 ! CleanupError {
|
||||||
|
defer trace^ = trace^ * 10 + 1
|
||||||
|
errdefer |err| {
|
||||||
|
if (err == .bad) {
|
||||||
|
trace^ = trace^ * 10 + 2
|
||||||
|
} else {
|
||||||
|
trace^ = 99
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer trace^ = trace^ * 10 + 3
|
||||||
|
if (fail) return .bad
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
|
||||||
|
fail_cleanup func() i32 ! CleanupError {
|
||||||
|
return .bad
|
||||||
|
}
|
||||||
|
|
||||||
|
try_cleanup func(trace *mut i32) i32 ! CleanupError {
|
||||||
|
defer trace^ = trace^ * 10 + 4
|
||||||
|
errdefer trace^ = trace^ * 10 + 5
|
||||||
|
return try fail_cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
widen_cleanup func(trace *mut i32) i32 ! CleanupErrors {
|
||||||
|
errdefer |err| {
|
||||||
|
match err {
|
||||||
|
.bad: trace^ = trace^ * 10 + 6
|
||||||
|
.extra: trace^ = 99
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return try fail_cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_cleanup func(trace *mut i32) i32 ! CleanupError {
|
||||||
|
errdefer trace^ = trace^ * 10 + 7
|
||||||
|
{
|
||||||
|
errdefer trace^ = 99
|
||||||
|
}
|
||||||
|
return .bad
|
||||||
|
}
|
||||||
|
|
||||||
|
multi_exit_cleanup func(direct bool, trace *mut i32) i32 ! CleanupError {
|
||||||
|
errdefer |err| {
|
||||||
|
if (err == .bad) trace^ = trace^ + 8
|
||||||
|
}
|
||||||
|
if (direct) return .bad
|
||||||
|
return try fail_cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
# 1. return value captured before defers run.
|
# 1. return value captured before defers run.
|
||||||
if (spill_check() != 5) return 101
|
if (spill_check() != 5) return 101
|
||||||
@@ -78,5 +138,31 @@ main func() i32 {
|
|||||||
# 7. break does not run an enclosing function-scope defer.
|
# 7. break does not run an enclosing function-scope defer.
|
||||||
if (enclosing_defer_check() != 2) return 107
|
if (enclosing_defer_check() != 2) return 107
|
||||||
|
|
||||||
|
# 8. errdefer is skipped on success; ordinary defers stay interleaved.
|
||||||
|
trace i32 = 0
|
||||||
|
if ((explicit_cleanup(false, &trace) catch 0) != 7 or trace != 31) return 108
|
||||||
|
|
||||||
|
# 9. Explicit errors run errdefer and expose the captured error.
|
||||||
|
trace = 0
|
||||||
|
if ((explicit_cleanup(true, &trace) catch 9) != 9 or trace != 321) return 109
|
||||||
|
|
||||||
|
# 10. Propagated errors run both errdefer and ordinary defer.
|
||||||
|
trace = 0
|
||||||
|
if ((try_cleanup(&trace) catch 9) != 9 or trace != 54) return 110
|
||||||
|
|
||||||
|
# 11. Captures observe the widened enclosing error type.
|
||||||
|
trace = 0
|
||||||
|
if ((widen_cleanup(&trace) catch 9) != 9 or trace != 6) return 111
|
||||||
|
|
||||||
|
# 12. An errdefer expires when its block exits normally.
|
||||||
|
trace = 0
|
||||||
|
if ((scoped_cleanup(&trace) catch 9) != 9 or trace != 7) return 112
|
||||||
|
|
||||||
|
# 13. One captured errdefer can be replayed at several distinct error exits.
|
||||||
|
trace = 0
|
||||||
|
_ = multi_exit_cleanup(true, &trace) catch 0
|
||||||
|
_ = multi_exit_cleanup(false, &trace) catch 0
|
||||||
|
if (trace != 16) return 113
|
||||||
|
|
||||||
return 42
|
return 42
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
id = "brolang"
|
id = "brolang"
|
||||||
name = "Brolang"
|
name = "Brolang"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Brolang contributors"]
|
authors = ["Brolang contributors"]
|
||||||
description = "Brolang language support"
|
description = "Brolang language support"
|
||||||
@@ -9,5 +9,5 @@ languages = ["languages/brolang"]
|
|||||||
|
|
||||||
[grammars.brolang]
|
[grammars.brolang]
|
||||||
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
|
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
|
||||||
rev = "zed-dev"
|
rev = "ba171a2d5e248fa24f5f4ee960e21056893f72e7"
|
||||||
path = "tree-sitter-brolang"
|
path = "tree-sitter-brolang"
|
||||||
|
|||||||
Binary file not shown.
@@ -23,6 +23,16 @@
|
|||||||
(builtin_type) @type.builtin
|
(builtin_type) @type.builtin
|
||||||
(named_type) @type
|
(named_type) @type
|
||||||
|
|
||||||
|
(named_type
|
||||||
|
(qualified_identifier
|
||||||
|
(identifier) @function .)
|
||||||
|
(argument_list))
|
||||||
|
|
||||||
|
(struct_literal
|
||||||
|
type: (qualified_identifier
|
||||||
|
(identifier) @function .)
|
||||||
|
(argument_list))
|
||||||
|
|
||||||
(type_declaration name: (identifier) @type)
|
(type_declaration name: (identifier) @type)
|
||||||
(function_declaration name: (identifier) @function)
|
(function_declaration name: (identifier) @function)
|
||||||
(parameter name: (identifier) @variable.parameter)
|
(parameter name: (identifier) @variable.parameter)
|
||||||
@@ -62,6 +72,7 @@
|
|||||||
"break"
|
"break"
|
||||||
"continue"
|
"continue"
|
||||||
"defer"
|
"defer"
|
||||||
|
"errdefer"
|
||||||
"yield"
|
"yield"
|
||||||
"match"
|
"match"
|
||||||
"else"
|
"else"
|
||||||
@@ -86,6 +97,7 @@
|
|||||||
"/"
|
"/"
|
||||||
"!"
|
"!"
|
||||||
"&"
|
"&"
|
||||||
|
"@"
|
||||||
"?"
|
"?"
|
||||||
"^"
|
"^"
|
||||||
".."
|
".."
|
||||||
|
|||||||
@@ -278,7 +278,17 @@ module.exports = grammar({
|
|||||||
break_statement: $ => seq('break', optional(seq(':', field('label', $.identifier)))),
|
break_statement: $ => seq('break', optional(seq(':', field('label', $.identifier)))),
|
||||||
continue_statement: $ => seq('continue', optional(seq(':', field('label', $.identifier)))),
|
continue_statement: $ => seq('continue', optional(seq(':', field('label', $.identifier)))),
|
||||||
|
|
||||||
defer_statement: $ => seq('defer', repeat($._newline), field('body', $.statement)),
|
defer_statement: $ => choice(
|
||||||
|
seq('defer', repeat($._newline), field('body', $.statement)),
|
||||||
|
seq(
|
||||||
|
'errdefer',
|
||||||
|
repeat($._newline),
|
||||||
|
optional(seq(field('capture', $.error_capture), repeat($._newline))),
|
||||||
|
field('body', $.statement),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
error_capture: $ => seq('|', choice($.identifier, $.sink), '|'),
|
||||||
|
|
||||||
labeled_block: $ => seq(
|
labeled_block: $ => seq(
|
||||||
field('label', $.identifier),
|
field('label', $.identifier),
|
||||||
|
|||||||
@@ -23,6 +23,16 @@
|
|||||||
(builtin_type) @type.builtin
|
(builtin_type) @type.builtin
|
||||||
(named_type) @type
|
(named_type) @type
|
||||||
|
|
||||||
|
(named_type
|
||||||
|
(qualified_identifier
|
||||||
|
(identifier) @function .)
|
||||||
|
(argument_list))
|
||||||
|
|
||||||
|
(struct_literal
|
||||||
|
type: (qualified_identifier
|
||||||
|
(identifier) @function .)
|
||||||
|
(argument_list))
|
||||||
|
|
||||||
(type_declaration name: (identifier) @type)
|
(type_declaration name: (identifier) @type)
|
||||||
(function_declaration name: (identifier) @function)
|
(function_declaration name: (identifier) @function)
|
||||||
(parameter name: (identifier) @variable.parameter)
|
(parameter name: (identifier) @variable.parameter)
|
||||||
@@ -62,6 +72,7 @@
|
|||||||
"break"
|
"break"
|
||||||
"continue"
|
"continue"
|
||||||
"defer"
|
"defer"
|
||||||
|
"errdefer"
|
||||||
"yield"
|
"yield"
|
||||||
"match"
|
"match"
|
||||||
"else"
|
"else"
|
||||||
@@ -86,6 +97,7 @@
|
|||||||
"/"
|
"/"
|
||||||
"!"
|
"!"
|
||||||
"&"
|
"&"
|
||||||
|
"@"
|
||||||
"?"
|
"?"
|
||||||
"^"
|
"^"
|
||||||
".."
|
".."
|
||||||
|
|||||||
@@ -1855,6 +1855,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"defer_statement": {
|
"defer_statement": {
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
{
|
{
|
||||||
@@ -1878,6 +1881,86 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "errdefer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REPEAT",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "capture",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "error_capture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REPEAT",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_newline"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BLANK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "body",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "statement"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"error_capture": {
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "|"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "sink"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "|"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"labeled_block": {
|
"labeled_block": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
|||||||
@@ -517,6 +517,16 @@
|
|||||||
"named": true
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"capture": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "error_capture",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -629,6 +639,25 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "error_capture",
|
||||||
|
"named": true,
|
||||||
|
"fields": {},
|
||||||
|
"children": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "sink",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "expression",
|
"type": "expression",
|
||||||
"named": true,
|
"named": true,
|
||||||
@@ -2471,6 +2500,10 @@
|
|||||||
"type": "enum",
|
"type": "enum",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "errdefer",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "escape_sequence",
|
"type": "escape_sequence",
|
||||||
"named": true
|
"named": true
|
||||||
|
|||||||
+112536
-105418
File diff suppressed because it is too large
Load Diff
@@ -95,3 +95,62 @@ sum func(a, b i32) i32 ! Status {
|
|||||||
(expression
|
(expression
|
||||||
(enum_literal
|
(enum_literal
|
||||||
(identifier))))))))))))
|
(identifier))))))))))))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Errdefer
|
||||||
|
==================
|
||||||
|
|
||||||
|
Failure :: enum { bad }
|
||||||
|
|
||||||
|
work func() i32 ! Failure {
|
||||||
|
errdefer cleanup()
|
||||||
|
errdefer |err| {
|
||||||
|
_ = err
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(type_declaration
|
||||||
|
(identifier)
|
||||||
|
(enum_type
|
||||||
|
(enum_body
|
||||||
|
(enum_member
|
||||||
|
(identifier)))))
|
||||||
|
(function_declaration
|
||||||
|
(identifier)
|
||||||
|
(parameter_list)
|
||||||
|
(type
|
||||||
|
(builtin_type))
|
||||||
|
(type
|
||||||
|
(named_type
|
||||||
|
(qualified_identifier
|
||||||
|
(identifier))))
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(defer_statement
|
||||||
|
(statement
|
||||||
|
(expression_statement
|
||||||
|
(expression
|
||||||
|
(call_expression
|
||||||
|
(expression
|
||||||
|
(identifier))
|
||||||
|
(argument_list)))))))
|
||||||
|
(statement
|
||||||
|
(defer_statement
|
||||||
|
(error_capture
|
||||||
|
(identifier))
|
||||||
|
(statement
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(assignment_statement
|
||||||
|
(expression
|
||||||
|
(sink))
|
||||||
|
(expression
|
||||||
|
(identifier))))))))
|
||||||
|
(statement
|
||||||
|
(return_statement
|
||||||
|
(expression
|
||||||
|
(integer)))))))
|
||||||
|
|||||||
Reference in New Issue
Block a user