Compare commits

..

2 Commits

Author SHA1 Message Date
hl-valdemar 5157cf3bcc rename intrinsics 2026-07-14 20:15:52 +02:00
hl-valdemar 471896b48a favor return over return _ (void return); newline/closing terminates 2026-07-14 19:11:58 +02:00
30 changed files with 98322 additions and 99757 deletions
+28 -24
View File
@@ -25,11 +25,11 @@ roadmap and milestone history.
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars - target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions - contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)` - strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
- compile-time `min_value(T)` and `max_value(T)` bounds for concrete native and C integer scalar types - compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native and C integer scalar types
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T` - arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?` - pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening - pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
- `ptr_cast(T, ptr)` as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape - `ptrcast!(T, ptr)` as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings - UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
- narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange - narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange
- optionals with `none`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps - optionals with `none`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
@@ -89,56 +89,60 @@ fields. `_` is not a keyword member name.
- boolean `if` / `else if` / `else`, braceless single-statement branches, and optional parenthesized conditions - boolean `if` / `else if` / `else`, braceless single-statement branches, and optional parenthesized conditions
- `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; `break :label` can cross nested scopes to exit a labeled block
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO - bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
- value blocks, value `if`, value loops, value `match`, `yield`, and labeled `yield :label value` - bare void `return`, same-line `return value`, value blocks, value `if`, value loops, value `match`, and strictly value-producing `yield value` / `yield :label value`
- `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns - `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks - fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
- direct `return match ...` and `yield match ...` value-control-flow operands - direct `return match ...` and `yield match ...` value-control-flow operands
#### division #### division
Compiler intrinsics use direct unqualified `name!(...)` syntax. The `!` marks the call as an
intrinsic; it is not part of the identifier. Bare and qualified calls without `!` resolve as
ordinary user functions, while qualified bang calls are rejected.
`/` and `/=` accept only floating-point operands. Integer division must state its rounding and `/` and `/=` accept only floating-point operands. Integer division must state its rounding and
remainder convention with one of these unqualified builtins: remainder convention with one of these intrinsics:
| Builtin | Result | | Builtin | Result |
| --- | --- | | --- | --- |
| `div_trunc(a, b)` | quotient rounded toward zero | | `divtrunc!(a, b)` | quotient rounded toward zero |
| `div_floor(a, b)` | quotient rounded toward negative infinity | | `divfloor!(a, b)` | quotient rounded toward negative infinity |
| `div_exact(a, b)` | truncated quotient; traps unless it divides exactly | | `divexact!(a, b)` | truncated quotient; traps unless it divides exactly |
| `div_ceil(a, b)` | quotient rounded toward positive infinity | | `divceil!(a, b)` | quotient rounded toward positive infinity |
| `rem(a, b)` | remainder paired with `div_trunc`; sign follows `a` | | `rem!(a, b)` | remainder paired with `divtrunc!`; sign follows `a` |
| `mod(a, b)` | modulus paired with `div_floor`; sign follows `b` | | `mod!(a, b)` | modulus paired with `divfloor!`; sign follows `b` |
The operands may be compatible concrete integer or float scalars. Existing literal coercion and The operands may be compatible concrete integer or float scalars. Existing literal coercion and
numeric widening rules apply, the result has the common operand type, and float quotients are numeric widening rules apply, the result has the common operand type, and float quotients are
integral-valued floats. These identities hold when representable: integral-valued floats. These identities hold when representable:
```bro ```bro
div_trunc(a, b) * b + rem(a, b) == a divtrunc!(a, b) * b + rem!(a, b) == a
div_floor(a, b) * b + mod(a, b) == a divfloor!(a, b) * b + mod!(a, b) == a
``` ```
Negative operands distinguish the operations: Negative operands distinguish the operations:
```bro ```bro
div_trunc(-5, 3) == -1 divtrunc!(-5, 3) == -1
div_floor(-5, 3) == -2 divfloor!(-5, 3) == -2
div_ceil(-5, 3) == -1 divceil!(-5, 3) == -1
rem(-5, 3) == -2 rem!(-5, 3) == -2
mod(-5, 3) == 1 mod!(-5, 3) == 1
mod(5, -3) == -1 mod!(5, -3) == -1
``` ```
All six builtins diagnose a zero denominator at comptime and trap at runtime, including float All six builtins diagnose a zero denominator at comptime and trap at runtime, including float
zero. Quotient operations also trap for signed `min_value(T), -1`; `rem` and `mod` return zero for zero. Quotient operations also trap for signed `minval!(T), -1`; `rem!` and `mod!` return zero for
that pair. `div_exact` traps when `div_trunc(a, b) * b == a` is false in the operand type, so float that pair. `divexact!` traps when `divtrunc!(a, b) * b == a` is false in the operand type, so float
exactness follows floating-point equality. Other float NaN and infinity behavior follows the exactness follows floating-point equality. Other float NaN and infinity behavior follows the
underlying IEEE operations. Ordinary float `/` remains unchecked and therefore preserves IEEE underlying IEEE operations. Ordinary float `/` remains unchecked and therefore preserves IEEE
infinity/NaN behavior. infinity/NaN behavior.
The six spellings are reserved only as direct unqualified calls. A qualified call such as Only the six bang calls are intrinsic. Bare calls such as `divfloor(a, b)` and qualified calls such
`math.div_floor(a, b)` resolves to an ordinary package function. as `math.divfloor(a, b)` resolve to ordinary functions.
### functions, C interop, and linking ### functions, C interop, and linking
@@ -188,7 +192,7 @@ The six spellings are reserved only as direct unqualified calls. A qualified cal
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments - non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics - arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
- recursive type factories, type reflection, and type-producing unions/enums - recursive type factories, type reflection, and type-producing unions/enums
- broader Zig-style pointer/result casts beyond V1 `ptr_cast(T, ptr)` - broader Zig-style pointer/result casts beyond V1 `ptrcast!(T, ptr)`
- sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism - sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism
- backed/C enum composition and must-consume fallible linting - backed/C enum composition and must-consume fallible linting
- result-to-argument type-demand propagation through function call boundaries - result-to-argument type-demand propagation through function call boundaries
+2 -2
View File
@@ -183,7 +183,7 @@ Current prototype features:
- `#` comments - `#` comments
- Immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks - Immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int` - Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int`
- Target-dependent atomic `c_*` primitive types, `c_func`, complete `c_struct`, `opaque`, `anyopaque`, and V1 `ptr_cast(T, ptr)` - Target-dependent atomic `c_*` primitive types, `c_func`, complete `c_struct`, `opaque`, `anyopaque`, and V1 `ptrcast!(T, ptr)`
- Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs - Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
- String literals as immutable pointers to static zero-terminated byte arrays - String literals as immutable pointers to static zero-terminated byte arrays
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals - Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
@@ -203,7 +203,7 @@ Current prototype features:
- Bodyless manual and imported C variadic declarations with default argument promotions - Bodyless manual and imported C variadic declarations with default argument promotions
- Ordered linking of additional C sources, objects, archives, and libraries - Ordered linking of additional C sources, objects, archives, and libraries
- Checked signed addition and unary negation - Checked signed addition and unary negation
- Float-only `/` plus explicit `div_trunc`, `div_floor`, `div_exact`, `div_ceil`, `rem`, and `mod` scalar builtins - Float-only `/` plus explicit `divtrunc!`, `divfloor!`, `divexact!`, `divceil!`, `rem!`, and `mod!` scalar intrinsics
- Static, eager runtime, mutable runtime, and deferred problematic globals - Static, eager runtime, mutable runtime, and deferred problematic globals
- Runtime diagnostics followed by `llvm.trap` - Runtime diagnostics followed by `llvm.trap`
+18 -15
View File
@@ -173,7 +173,7 @@
- `Name :: opaque` is the incomplete nominal record spelling; bodyless `c_struct` is invalid - `Name :: opaque` is the incomplete nominal record spelling; bodyless `c_struct` is invalid
- `anyopaque` is the erased object type used behind pointers for C `void*` and allocator contexts - `anyopaque` is the erased object type used behind pointers for C `void*` and allocator contexts
- C `void` function results remain `void`; C `void*` / `const void*` import and render as `?*mut anyopaque` / `?*anyopaque` - C `void` function results remain `void`; C `void*` / `const void*` import and render as `?*mut anyopaque` / `?*anyopaque`
- `ptr_cast(T, ptr)` preserves pointer shape and only changes the child type in v1 - `ptrcast!(T, ptr)` preserves pointer shape and only changes the child type in v1
- future direction: generalize toward Zig-style arbitrary pointer-result casts once casts have a broader result-type story - future direction: generalize toward Zig-style arbitrary pointer-result casts once casts have a broader result-type story
12. `undefined` as inspired by zig (implemented): 12. `undefined` as inspired by zig (implemented):
@@ -464,7 +464,7 @@
active/field `integer`) is sufficient. **no HIR/IR/lowering change** (like 18/19/20); the delta active/field `integer`) is sufficient. **no HIR/IR/lowering change** (like 18/19/20); the delta
is parser + types layout + one checker validation + three LLVM emit sites is parser + types layout + one checker validation + three LLVM emit sites
- runtime layout `{tag, payload-carrier}`: tag at offset 0, payload carrier at - runtime layout `{tag, payload-carrier}`: tag at offset 0, payload carrier at
`payload_offset = round_up(sizeof(tag), payload_align)` (`types.union_payload_offset`, shared by `payload_offset = round_up(sizeof!(tag), payload_align)` (`types.union_payload_offset`, shared by
`size` and the emitter). field access uses byte-offset GEPs, so offsets stay self-consistent `size` and the emitter). field access uses byte-offset GEPs, so offsets stay self-consistent
- construction `T{ variant = value }` reuses the union-literal path and additionally stores the - construction `T{ variant = value }` reuses the union-literal path and additionally stores the
derived tag; payload read `x.variant` reuses field access, reading at the payload offset derived tag; payload read `x.variant` reuses field access, reading at the payload offset
@@ -733,8 +733,8 @@
(needs string building), struct field defaults to drop `&[]` on empty lists (needs string building), struct field defaults to drop `&[]` on empty lists
29. fix bugs (implemented) 29. fix bugs (implemented)
- `return _` was already the supported empty return for void functions; the original - bare `return` is the empty return for void functions; `yield` always requires a
bare-`return` report was stale same-line, non-void value because `break` handles valueless scope exits
- catch value blocks may end by returning from the function instead of yielding when - catch value blocks may end by returning from the function instead of yielding when
every path exits every path exits
- implicit-conversion diagnostics render source-level composite and named types instead - implicit-conversion diagnostics render source-level composite and named types instead
@@ -794,19 +794,19 @@
32. explicit division family (implemented) 32. explicit division family (implemented)
- `/` and `/=` are float-only; every integer use is rejected with guidance toward explicit - `/` and `/=` are float-only; every integer use is rejected with guidance toward explicit
division, including literals, comptime execution, array counts, and compound assignment division, including literals, comptime execution, array counts, and compound assignment
- direct unqualified calls reserve `div_trunc`, `div_floor`, `div_exact`, `div_ceil`, `rem`, and - direct bang calls use `divtrunc!`, `divfloor!`, `divexact!`, `divceil!`, `rem!`, and `mod!`;
`mod`; qualified names remain ordinary package functions bare and qualified names remain ordinary functions
- the builtins accept compatible concrete integer or float scalars, reuse existing literal and - the builtins accept compatible concrete integer or float scalars, reuse existing literal and
widening rules, and return the common operand type (integral-valued floats for quotients) widening rules, and return the common operand type (integral-valued floats for quotients)
- all builtins diagnose zero denominators at comptime and trap at runtime; quotient operations - all builtins diagnose zero denominators at comptime and trap at runtime; quotient operations
also trap on signed `min_value(T) / -1`, while `rem` and `mod` return zero for that pair also trap on signed `minval!(T) / -1`, while `rem!` and `mod!` return zero for that pair
- `div_exact` checks the reconstructed dividend in the operand type; `rem` pairs with truncation - `divexact!` checks the reconstructed dividend in the operand type; `rem!` pairs with truncation
and follows the numerator sign, while `mod` pairs with floor and follows the denominator sign and follows the numerator sign, while `mod!` pairs with floor and follows the denominator sign
- HIR/IR use compact semantic enum tags; integer floor, ceil, and exact lowering reconstructs the - HIR/IR use compact semantic enum tags; integer floor, ceil, and exact lowering reconstructs the
remainder from one quotient so each produces only one hardware-division candidate remainder from one quotient so each produces only one hardware-division candidate
- float lowering uses the typed LLVM trunc/floor/ceil intrinsics, `frem`, and ordered equality; - float lowering uses the typed LLVM trunc/floor/ceil intrinsics, `frem`, and ordered equality;
ordinary float `/` remains the unchecked IEEE infinity/NaN escape hatch ordinary float `/` remains the unchecked IEEE infinity/NaN escape hatch
- migrated `std/mem`, `std/arraylist`, and the compound-assignment example to `div_trunc` - migrated `std/mem`, `std/arraylist`, and the compound-assignment example to `divtrunc!`
33. explicit I/O provider (implemented) 33. explicit I/O provider (implemented)
- `main` may take one canonical `@std/io Io`; parameterless entry points remain valid - `main` may take one canonical `@std/io Io`; parameterless entry points remain valid
@@ -834,6 +834,9 @@
- add `debug.print` function making use of `std/io` to print values to the console - add `debug.print` function making use of `std/io` to print values to the console
- this may either require native variadic arguments or a tuple value to like zig's approach (consider pros and cons) - this may either require native variadic arguments or a tuple value to like zig's approach (consider pros and cons)
38. place every intrinsic behind direct unqualified `name!(...)` syntax, freeing the bare names for
user functions (implemented)
## 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:
@@ -842,7 +845,7 @@ For casts that bypass safety checks, Honey provides builtin functions:
| -- | -- | -- | | -- | -- | -- |
| `truncate(x, T)` | Keep low bits, discard rest | Never | | `truncate(x, T)` | Keep low bits, discard rest | Never |
| `bitcast(x, T)` | Reinterpret bits, no cast | Sizes don't match (compile error) | | `bitcast(x, T)` | Reinterpret bits, no cast | Sizes don't match (compile error) |
| `ptrcast(p, T)` | Change pointer type | Gaining mutability (compile error) | | `ptrcast!(p, T)` | Change pointer type | Gaining mutability (compile error) |
```honey ```honey
# truncation # truncation
@@ -857,10 +860,10 @@ bits := bitcast(f, u32) # IEEE 754 representation
# pointer casts (element type, many ↔ single, pointer ↔ usize) # pointer casts (element type, many ↔ single, pointer ↔ usize)
buf: *u8 = get_buffer() buf: *u8 = get_buffer()
ints := ptrcast(buf, *u32) # element type change ints := ptrcast!(buf, *u32) # element type change
single := ptrcast(buf, @u8) # many → single (restricting) single := ptrcast!(buf, @u8) # many → single (restricting)
addr := ptrcast(buf, usize) # pointer to integer addr := ptrcast!(buf, usize) # pointer to integer
ptr := ptrcast(addr, @u8) # integer to pointer ptr := ptrcast!(addr, @u8) # integer to pointer
``` ```
## A word on multi-unwrap ## A word on multi-unwrap
+1
View File
@@ -120,6 +120,7 @@ Expr :: struct {
body: []Stmt_Id, body: []Stmt_Id,
diagnostic: source.Diagnostic_Id, diagnostic: source.Diagnostic_Id,
parenthesized: bool, parenthesized: bool,
intrinsic: bool,
kind: Expr_Kind, kind: Expr_Kind,
} }
+76 -30
View File
@@ -401,10 +401,11 @@ type_label :: proc(checker: ^Checker, value: types.Type) -> string {
return strings.to_string(builder) return strings.to_string(builder)
} }
is_ptr_cast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool { is_ptrcast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
return expr.left == ast.INVALID_EXPR && return expr.intrinsic &&
expr.left == ast.INVALID_EXPR &&
!symbol.is_valid(expr.qualifier) && !symbol.is_valid(expr.qualifier) &&
symbol_text(checker, expr.name) == "ptr_cast" symbol_text(checker, expr.name) == "ptrcast"
} }
Type_Builtin :: enum u8 { Type_Builtin :: enum u8 {
@@ -426,14 +427,14 @@ Division_Builtin :: enum u8 {
} }
division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin { division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin {
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) { if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
return .None return .None
} }
switch symbol_text(checker, expr.name) { switch symbol_text(checker, expr.name) {
case "div_trunc": return .Trunc case "divtrunc": return .Trunc
case "div_floor": return .Floor case "divfloor": return .Floor
case "div_exact": return .Exact case "divexact": return .Exact
case "div_ceil": return .Ceil case "divceil": return .Ceil
case "rem": return .Rem case "rem": return .Rem
case "mod": return .Mod case "mod": return .Mod
} }
@@ -441,26 +442,26 @@ division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Bui
} }
type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin { type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin {
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) { if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
return .None return .None
} }
name := symbol_text(checker, expr.name) name := symbol_text(checker, expr.name)
if name == "size_of" { if name == "sizeof" {
return .Size_Of return .Size_Of
} }
if name == "align_of" { if name == "alignof" {
return .Align_Of return .Align_Of
} }
if name == "min_value" { if name == "minval" {
return .Min_Value return .Min_Value
} }
if name == "max_value" { if name == "maxval" {
return .Max_Value return .Max_Value
} }
return .None return .None
} }
valid_ptr_cast_child :: proc(checker: ^Checker, value: types.Type) -> bool { valid_ptrcast_child :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_valid(value) && return types.is_valid(value) &&
!types.is_void(value) && !types.is_void(value) &&
!types.is_anyopaque(value) && !types.is_anyopaque(value) &&
@@ -501,7 +502,7 @@ build_type_builtin :: proc(
file: ast.File_Id, file: ast.File_Id,
) -> hir.Expr_Id { ) -> hir.Expr_Id {
if len(expr.args) != 1 { if len(expr.args) != 1 {
id := source.addf(checker.diagnostics, expr.span, "%s expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args)) id := source.addf(checker.diagnostics, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
return invalid_hir_expr(checker, expr.span, id, types.USIZE) return invalid_hir_expr(checker, expr.span, id, types.USIZE)
} }
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file) target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
@@ -640,7 +641,7 @@ type_from_syntax :: proc(
} }
} else { } else {
if constant.kind == .Integer_Division { if constant.kind == .Integer_Division {
source.add(checker.diagnostics, span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil") source.add(checker.diagnostics, span, "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!")
return types.INVALID return types.INVALID
} }
source.add(checker.diagnostics, span, "array count must be a compile-time integer expression") source.add(checker.diagnostics, span, "array count must be a compile-time integer expression")
@@ -3365,7 +3366,7 @@ infer_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
if is_ptr_cast_call(checker, expr) { if is_ptrcast_call(checker, expr) {
if len(expr.args) != 2 { if len(expr.args) != 2 {
last = types.INVALID last = types.INVALID
_ = pop(&stack) _ = pop(&stack)
@@ -3374,13 +3375,18 @@ infer_expr :: proc(
child, child_ok := resolve_type_argument(checker, expr.args[0], pkg, file) child, child_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
operand := infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types) operand := infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
result := types.INVALID result := types.INVALID
if child_ok && valid_ptr_cast_child(checker, child) { if child_ok && valid_ptrcast_child(checker, child) {
result, _ = types.replace_pointer_child(&checker.module.types, operand, child) result, _ = types.replace_pointer_child(&checker.module.types, operand, child)
} }
last = result last = result
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
if expr.intrinsic {
last = types.INVALID
_ = pop(&stack)
continue
}
if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file); handled { if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file); handled {
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) _, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok { if !ok {
@@ -4957,7 +4963,7 @@ build_constant_expr :: proc(
return invalid_hir_expr(checker, expr.span, id, recovery_type) return invalid_hir_expr(checker, expr.span, id, recovery_type)
} }
if constant.kind == .Integer_Division { if constant.kind == .Integer_Division {
id := source.add(checker.diagnostics, expr.span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil") id := source.add(checker.diagnostics, expr.span, "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!")
return invalid_hir_expr(checker, expr.span, id, recovery_type) return invalid_hir_expr(checker, expr.span, id, recovery_type)
} }
if constant.kind == .Overflow || if constant.kind == .Overflow ||
@@ -5447,7 +5453,7 @@ build_division_builtin :: proc(
) -> hir.Expr_Id { ) -> hir.Expr_Id {
if len(expr.args) != 2 { if len(expr.args) != 2 {
id := source.addf( id := source.addf(
checker.diagnostics, expr.span, "%s expects 2 arguments, got %d", checker.diagnostics, expr.span, "%s! expects 2 arguments, got %d",
symbol_text(checker, expr.name), len(expr.args), symbol_text(checker, expr.name), len(expr.args),
) )
return invalid_hir_expr(checker, expr.span, id) return invalid_hir_expr(checker, expr.span, id)
@@ -6223,7 +6229,7 @@ build_binary_arith :: proc(
if op == .Div && !types.is_float(result, checker.target) { if op == .Div && !types.is_float(result, checker.target) {
id := source.add( id := source.add(
checker.diagnostics, span, checker.diagnostics, span,
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil", "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
) )
return invalid_hir_expr(checker, span, id, result) return invalid_hir_expr(checker, span, id, result)
} }
@@ -6463,22 +6469,22 @@ build_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
if is_ptr_cast_call(checker, expr) { if is_ptrcast_call(checker, expr) {
if len(expr.args) != 2 { if len(expr.args) != 2 {
id := source.addf(checker.diagnostics, expr.span, "ptr_cast expects 2 arguments, got %d", len(expr.args)) id := source.addf(checker.diagnostics, expr.span, "ptrcast! expects 2 arguments, got %d", len(expr.args))
last = invalid_hir_expr(checker, expr.span, id) last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file) target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
if !target_ok { if !target_ok {
id := source.add(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptr_cast target must be a type") id := source.add(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptrcast! target must be a type")
last = invalid_hir_expr(checker, expr.span, id) last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
if !valid_ptr_cast_child(checker, target) { if !valid_ptrcast_child(checker, target) {
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptr_cast target must be a sized runtime object type, got %s", type_label(checker, target)) id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptrcast! target must be a sized runtime object type, got %s", type_label(checker, target))
last = invalid_hir_expr(checker, expr.span, id) last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack) _ = pop(&stack)
continue continue
@@ -6488,6 +6494,17 @@ build_expr :: proc(
append(&stack, Build_Expr_Frame{expr=expr.args[1], expected=types.INVALID, template=ast.INVALID_FUNCTION}) append(&stack, Build_Expr_Frame{expr=expr.args[1], expected=types.INVALID, template=ast.INVALID_FUNCTION})
continue continue
} }
if expr.intrinsic {
id := source.INVALID_DIAGNOSTIC
if symbol.is_valid(expr.qualifier) {
id = source.add(checker.diagnostics, expr.span, "intrinsic calls must be unqualified")
} else {
id = source.addf(checker.diagnostics, expr.span, "unknown intrinsic '%s!'", symbol_text(checker, expr.name))
}
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if callee, handled, ok := build_qualified_value_field(checker, expr, locals, global_reads, pkg, file); handled { if callee, handled, ok := build_qualified_value_field(checker, expr, locals, global_reads, pkg, file); handled {
if !ok { if !ok {
last = callee last = callee
@@ -6996,7 +7013,7 @@ build_expr :: proc(
if frame.stage == 9 { if frame.stage == 9 {
result, ok := types.replace_pointer_child(&checker.module.types, checker.module.exprs[last].type, frame.target_type) result, ok := types.replace_pointer_child(&checker.module.types, checker.module.exprs[last].type, frame.target_type)
if !ok { if !ok {
id := source.add(checker.diagnostics, expr.span, "ptr_cast operand must be a pointer or optional pointer") id := source.add(checker.diagnostics, expr.span, "ptrcast! operand must be a pointer or optional pointer")
last = invalid_hir_expr(checker, expr.span, id) last = invalid_hir_expr(checker, expr.span, id)
} else { } else {
last = add_hir_expr(checker, hir.Expr{ last = add_hir_expr(checker, hir.Expr{
@@ -7516,7 +7533,7 @@ build_block :: proc(
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC, local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
}) })
} else if !types.is_void(ctx.result) { } else if !types.is_void(ctx.result) {
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function") id := source.add(checker.diagnostics, statement.span, "non-void function must return a 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{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR, kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
@@ -8118,6 +8135,16 @@ build_block :: proc(
} else { } else {
yielded = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file) yielded = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
} }
if yielded != hir.INVALID_EXPR && types.is_void(checker.module.exprs[yielded].type) {
id := source.add(checker.diagnostics, statement.span, "'yield' expression must produce a non-void value")
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
}
yielded = resolve_loop_slot(ctx, target, yielded, checker.module.exprs[yielded].type if yielded != hir.INVALID_EXPR else types.INVALID, statement.span) yielded = resolve_loop_slot(ctx, target, yielded, checker.module.exprs[yielded].type if yielded != hir.INVALID_EXPR else types.INVALID, statement.span)
if target.slot == hir.INVALID_LOCAL || yielded == hir.INVALID_EXPR { if target.slot == hir.INVALID_LOCAL || yielded == hir.INVALID_EXPR {
id := source.add(checker.diagnostics, statement.span, id := source.add(checker.diagnostics, statement.span,
@@ -8291,9 +8318,19 @@ build_value_block :: proc(
checker, yield_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, checker, yield_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
expected, ctx.pkg, ctx.file, expected, ctx.pkg, ctx.file,
) )
}
value_type = checker.module.exprs[value].type value_type = checker.module.exprs[value].type
if is_runtime_type(checker, expected) { }
if types.is_void(value_type) {
id := source.add(checker.diagnostics, yield_stmt.span, "'yield' expression must produce a non-void value")
value = invalid_hir_expr(checker, yield_stmt.span, id)
value_type = types.INVALID
ctx.problematic^ = true
} else if types.is_void(expected) {
id := source.add(checker.diagnostics, yield_stmt.span, "void value context must fall through instead of yielding")
value = invalid_hir_expr(checker, yield_stmt.span, id)
value_type = types.INVALID
ctx.problematic^ = true
} else if is_runtime_type(checker, expected) {
value = coerce_expr(checker, value, expected, yield_stmt.span) value = coerce_expr(checker, value, expected, yield_stmt.span)
value_type = checker.module.exprs[value].type value_type = checker.module.exprs[value].type
} }
@@ -9521,6 +9558,15 @@ build_value_loop :: proc(
// The fall-through value initializes the slot before the loop (loop captures are // The fall-through value initializes the slot before the loop (loop captures are
// out of scope here), so the loop completing leaves it as the result. // out of scope here), so the loop completing leaves it as the result.
fall_value := build_expr(checker, fall_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file) fall_value := build_expr(checker, fall_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
if fall_value != hir.INVALID_EXPR && types.is_void(checker.module.exprs[fall_value].type) {
for s in loop_block {
append(body, s)
}
delete(loop_block, checker.allocator)
id := source.add(checker.diagnostics, fall_stmt.span, "'yield' expression must produce a non-void value")
ctx.problematic^ = true
return invalid_hir_expr(checker, fall_stmt.span, id), types.INVALID
}
fall_value = resolve_loop_slot(ctx, &target, fall_value, checker.module.exprs[fall_value].type if fall_value != hir.INVALID_EXPR else types.INVALID, fall_stmt.span) fall_value = resolve_loop_slot(ctx, &target, fall_value, checker.module.exprs[fall_value].type if fall_value != hir.INVALID_EXPR else types.INVALID, fall_stmt.span)
if target.slot == hir.INVALID_LOCAL || fall_value == hir.INVALID_EXPR { if target.slot == hir.INVALID_LOCAL || fall_value == hir.INVALID_EXPR {
for s in loop_block { for s in loop_block {
+15 -2
View File
@@ -1862,7 +1862,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
if op == .Div { if op == .Div {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail( return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Integer_Division, span, state, .Integer_Division, span,
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil", "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
) )
} }
value: i128 value: i128
@@ -2059,7 +2059,7 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
} }
if builtin := type_builtin_call(checker, expr); builtin != .None { if builtin := type_builtin_call(checker, expr); builtin != .None {
if len(expr.args) != 1 { if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args)) return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
} }
target, target_ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file) target, target_ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file)
if !target_ok { if !target_ok {
@@ -2078,6 +2078,12 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
if builtin := division_builtin_call(checker, expr); builtin != .None { if builtin := division_builtin_call(checker, expr); builtin != .None {
return ct_eval_division_call(state, expr, builtin, expected, depth+1) return ct_eval_division_call(state, expr, builtin, expected, depth+1)
} }
if expr.intrinsic {
if symbol.is_valid(expr.qualifier) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "intrinsic calls must be unqualified")
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown intrinsic '%s!'", symbol_text(checker, expr.name))
}
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false) target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
if !available { if !available {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package") return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package")
@@ -2463,14 +2469,21 @@ ct_exec_statements :: proc(
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid in a comptime value block") ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid in a comptime value block")
} else if statement.value_control_flow { } else if statement.value_control_flow {
flow, ok = ct_exec_statements(state, statement.body, true, depth+1) flow, ok = ct_exec_statements(state, statement.body, true, depth+1)
if ok && flow.kind == .Yield && types.is_void(state.values[flow.value].type) {
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
}
} else { } else {
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1) value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
ok = expr_ok ok = expr_ok
flow = expr_flow flow = expr_flow
if ok && flow.kind == .Normal { if ok && flow.kind == .Normal {
if types.is_void(state.values[value].type) {
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
} else {
flow = ct_flow(.Yield, value, statement.label) flow = ct_flow(.Yield, value, statement.label)
} }
} }
}
case .If: case .If:
flow, ok = ct_exec_if(state, statement, yield_returns, depth+1) flow, ok = ct_exec_if(state, statement, yield_returns, depth+1)
case .While: case .While:
+41 -14
View File
@@ -399,7 +399,7 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
!symbol.is_valid(qualifier) && file_hidden_name(parser, name), !symbol.is_valid(qualifier) && file_hidden_name(parser, name),
) )
if current(parser).kind == .Left_Paren { if current(parser).kind == .Left_Paren {
call := parse_call(parser, qualifier, first, name, 0) call := parse_call(parser, qualifier, first, name, 0, false)
return types.intern(&parser.module.type_store, types.Node{ return types.intern(&parser.module.type_store, types.Node{
kind=.Type_Call, kind=.Type_Call,
count_expr=u32(call), count_expr=u32(call),
@@ -490,7 +490,7 @@ parse_call_args :: proc(parser: ^Parser, nesting: int) -> ([]ast.Expr_Id, token.
return args[:], right_paren return args[:], right_paren
} }
parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> ast.Expr_Id { parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int, intrinsic: bool) -> ast.Expr_Id {
if nesting >= MAX_EXPRESSION_NESTING { if nesting >= MAX_EXPRESSION_NESTING {
span := skip_parenthesized(parser) span := skip_parenthesized(parser)
return invalid_expr(parser, span, "expression nesting exceeds 256 levels") return invalid_expr(parser, span, "expression nesting exceeds 256 levels")
@@ -502,6 +502,7 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok
qualifier=qualifier, qualifier=qualifier,
name=name.symbol, name=name.symbol,
args=args[:], args=args[:],
intrinsic=intrinsic,
left=ast.INVALID_EXPR, left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
@@ -885,9 +886,10 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
qualifier = first.symbol qualifier = first.symbol
name = member name = member
} }
_, intrinsic := allow(parser, .Bang)
if current(parser).kind == .Left_Paren { if current(parser).kind == .Left_Paren {
call := parse_call(parser, qualifier, first, name, nesting) call := parse_call(parser, qualifier, first, name, nesting, intrinsic)
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) { if !intrinsic && current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
left_brace := advance(parser) left_brace := advance(parser)
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal") args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
return add_expr(parser, ast.Expr{ return add_expr(parser, ast.Expr{
@@ -901,6 +903,9 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
} }
return call return call
} }
if intrinsic {
return invalid_expr(parser, previous(parser).span, "expected '(' after intrinsic name")
}
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) { if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
return parse_struct_literal(parser, qualifier, first, name, nesting) return parse_struct_literal(parser, qualifier, first, name, nesting)
} }
@@ -1156,6 +1161,21 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
} }
continue continue
} }
if current(parser).kind == .Bang {
marker := advance(parser)
if current(parser).kind != .Left_Paren {
left = invalid_expr(parser, marker.span, "expected '(' after '!'")
continue
}
left_expr := parser.module.exprs[left]
call_span := skip_parenthesized(parser)
left = invalid_expr(
parser,
span_from(left_expr.span, call_span),
"intrinsic calls require a direct name",
)
continue
}
if current(parser).kind == .Left_Paren { if current(parser).kind == .Left_Paren {
if nesting >= MAX_EXPRESSION_NESTING { if nesting >= MAX_EXPRESSION_NESTING {
span := skip_parenthesized(parser) span := skip_parenthesized(parser)
@@ -1282,14 +1302,11 @@ finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> sourc
parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id { parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := advance(parser) start := advance(parser)
skip_newlines(parser) if current(parser).kind == .Newline || current(parser).kind == .Right_Brace || current(parser).kind == .Eof {
if current(parser).kind == .Underscore {
end := advance(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=.Return, kind=.Return,
span=span_from(start.span, end.span), span=start.span,
name=end.symbol,
expr=ast.INVALID_EXPR, expr=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
@@ -1321,12 +1338,10 @@ parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
return id return id
} }
// `yield <expr>` supplies the value of the enclosing value block. The checker // `yield <expr>` supplies a non-void value to an enclosing value construct.
// only accepts it as the final statement of a value block (a `{ ... }` on the // The expression must start on the same line; `break` handles valueless exits.
// right of a declaration/assignment); it is the block analogue of `return`.
parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id { parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := advance(parser) // consume 'yield' start := advance(parser) // consume 'yield'
skip_newlines(parser)
// `yield :blk x` targets the loop labeled `blk`; a bare `yield x` targets // `yield :blk x` targets the loop labeled `blk`; a bare `yield x` targets
// the directly-enclosing value block / if branch. No expression starts with // the directly-enclosing value block / if branch. No expression starts with
// ':', so a leading colon is unambiguously a label. // ':', so a leading colon is unambiguously a label.
@@ -1335,9 +1350,21 @@ parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id {
if name, name_ok := allow(parser, .Identifier); name_ok { if name, name_ok := allow(parser, .Identifier); name_ok {
label = name.symbol label = name.symbol
} else { } else {
source.add(parser.diagnostics, current(parser).span, "expected a loop label after ':'") source.add(parser.diagnostics, current(parser).span, "expected a yield target label after ':'")
} }
} }
if current(parser).kind == .Newline || current(parser).kind == .Right_Brace || current(parser).kind == .Eof {
expr := invalid_expr(parser, start.span, "'yield' must produce a value")
id := ast.stmt_id(len(parser.module.statements))
append(&parser.module.statements, ast.Stmt{
kind=.Yield,
span=start.span,
label=label,
expr=expr,
diagnostic=source.INVALID_DIAGNOSTIC,
})
return id
}
if cf, is_cf := parse_value_control_flow(parser); is_cf { if cf, is_cf := parse_value_control_flow(parser); is_cf {
cf_span := parser.module.statements[cf].span cf_span := parser.module.statements[cf].span
body := make([]ast.Stmt_Id, 1, parser.module.allocator) body := make([]ast.Stmt_Id, 1, parser.module.allocator)
+355 -107
View File
@@ -86,6 +86,32 @@ main func() void { _ = value }
testing.expect_value(t, module.statements[module.functions[0].body[0]].name, sink_symbol) testing.expect_value(t, module.statements[module.functions[0].body[0]].name, sink_symbol)
} }
@(test)
parser_marks_only_bang_calls_as_intrinsic :: proc(t: ^testing.T) {
text := `main func() void {
_ = sizeof ! (i32)
_ = sizeof(i32)
}
`
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)
marked := module.exprs[module.statements[module.functions[0].body[0]].expr]
ordinary := module.exprs[module.statements[module.functions[0].body[1]].expr]
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, marked.kind, ast.Expr_Kind.Call)
testing.expect_value(t, ordinary.kind, ast.Expr_Kind.Call)
testing.expect(t, marked.intrinsic)
testing.expect(t, !ordinary.intrinsic)
}
@(test) @(test)
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) { compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) {
testing.expect_value(t, size_of(source.Span), 12) testing.expect_value(t, size_of(source.Span), 12)
@@ -1372,14 +1398,14 @@ Color :: enum {
blue blue
} }
UserID :: distinct u32 UserID :: distinct u32
SIZE_GLOBAL :: size_of(i32) SIZE_GLOBAL :: sizeof!(i32)
needs_usize func(value usize) usize { needs_usize func(value usize) usize {
return value return value
} }
buffer func($T type) [size_of(T)]u8 { buffer func($T type) [sizeof!(T)]u8 {
data [size_of(T)]u8 = undefined data [sizeof!(T)]u8 = undefined
return data return data
} }
@@ -1387,18 +1413,18 @@ main func() i32 {
bytes [_]u8 :: buffer(i32) bytes [_]u8 :: buffer(i32)
if (needs_usize(SIZE_GLOBAL) != 4) return 1 if (needs_usize(SIZE_GLOBAL) != 4) return 1
if (bytes.len != 4) return 2 if (bytes.len != 4) return 2
if (size_of([3]u8) != 3) return 3 if (sizeof!([3]u8) != 3) return 3
if (size_of([]u8) != 16) return 4 if (sizeof!([]u8) != 16) return 4
if (align_of([]u8) != 8) return 5 if (alignof!([]u8) != 8) return 5
if (size_of(*anyopaque) != 8) return 6 if (sizeof!(*anyopaque) != 8) return 6
if (size_of(?*i32) != 8) return 7 if (sizeof!(?*i32) != 8) return 7
if (size_of(*Opaque) != 8) return 8 if (sizeof!(*Opaque) != 8) return 8
if (size_of(Color) != 2) return 9 if (sizeof!(Color) != 2) return 9
if (align_of(Color) != 2) return 10 if (alignof!(Color) != 2) return 10
if (size_of(Point) != 8) return 11 if (sizeof!(Point) != 8) return 11
if (align_of(Point) != 4) return 12 if (alignof!(Point) != 4) return 12
if (size_of(UserID) != 4) return 13 if (sizeof!(UserID) != 4) return 13
if (align_of(UserID) != 4) return 14 if (alignof!(UserID) != 4) return 14
return 0 return 0
} }
` `
@@ -1418,23 +1444,23 @@ integer_bound_builtins_compile_and_run :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-integer-bounds" directory := "/tmp/brolang-test-integer-bounds"
main_path := "/tmp/brolang-test-integer-bounds/main.bro" main_path := "/tmp/brolang-test-integer-bounds/main.bro"
output := "/tmp/brolang-test-integer-bounds-output" output := "/tmp/brolang-test-integer-bounds-output"
text := `MAX_U64 u64 :: max_value(u64) text := `MAX_U64 u64 :: maxval!(u64)
maximum func($T type) T { maximum func($T type) T {
return max_value(T) return maxval!(T)
} }
main func() i32 { main func() i32 {
if (min_value(i8) != -128) return 1 if (minval!(i8) != -128) return 1
if (max_value(i8) != 127) return 2 if (maxval!(i8) != 127) return 2
if (min_value(u8) != 0) return 3 if (minval!(u8) != 0) return 3
if (max_value(u8) != 255) return 4 if (maxval!(u8) != 255) return 4
if (min_value(isize) != -9223372036854775808) return 5 if (minval!(isize) != -9223372036854775808) return 5
if (max_value(usize) != 18446744073709551615) return 6 if (maxval!(usize) != 18446744073709551615) return 6
if (MAX_U64 != 18446744073709551615) return 7 if (MAX_U64 != 18446744073709551615) return 7
if (maximum(u16) != 65535) return 8 if (maximum(u16) != 65535) return 8
if (min_value(c_int) != -2147483648) return 9 if (minval!(c_int) != -2147483648) return 9
if (max_value(c_ulong) != 18446744073709551615) return 10 if (maxval!(c_ulong) != 18446744073709551615) return 10
return 0 return 0
} }
` `
@@ -1455,14 +1481,14 @@ integer_bound_builtins_reject_invalid_targets :: proc(t: ^testing.T) {
Choice :: enum { one } Choice :: enum { one }
main func() void { main func() void {
_ = min_value() _ = minval!()
_ = max_value(u8, u16) _ = maxval!(u8, u16)
_ = min_value(1) _ = minval!(1)
_ = max_value(int) _ = maxval!(int)
_ = max_value(f32) _ = maxval!(f32)
_ = max_value(bool) _ = maxval!(bool)
_ = max_value(Named) _ = maxval!(Named)
_ = max_value(Choice) _ = maxval!(Choice)
} }
` `
source_file := source.Source{path="test.bro", text=text} source_file := source.Source{path="test.bro", text=text}
@@ -1496,11 +1522,11 @@ layout_builtins_reject_unsized_targets :: proc(t: ^testing.T) {
Fn :: alias func() void Fn :: alias func() void
main func() void { main func() void {
_ = size_of(void) _ = sizeof!(void)
_ = align_of(anyopaque) _ = alignof!(anyopaque)
_ = size_of(Fn) _ = sizeof!(Fn)
_ = size_of(Opaque) _ = sizeof!(Opaque)
_ = align_of(1) _ = alignof!(1)
} }
` `
source_file := source.Source{path="test.bro", text=text} source_file := source.Source{path="test.bro", text=text}
@@ -1803,14 +1829,14 @@ main func() void {
} }
@(test) @(test)
opaque_anyopaque_and_ptr_cast_compile_and_lower :: proc(t: ^testing.T) { opaque_anyopaque_and_ptrcast_compile_and_lower :: proc(t: ^testing.T) {
text := `Handle :: opaque text := `Handle :: opaque
take func(_ ?*mut anyopaque) void {} take func(_ ?*mut anyopaque) void {}
use_handle func(_ ?@mut Handle) void {} use_handle func(_ ?@mut Handle) void {}
main func() void { main func() void {
values [2]mut u8 = [1, 2] values [2]mut u8 = [1, 2]
raw ?*mut anyopaque = (&values).ptr raw ?*mut anyopaque = (&values).ptr
bytes ?*mut u8 = ptr_cast(u8, raw) bytes ?*mut u8 = ptrcast!(u8, raw)
take(bytes) take(bytes)
if bytes |p| { if bytes |p| {
p[1] = 5 p[1] = 5
@@ -1818,7 +1844,7 @@ main func() void {
one u8 = 1 one u8 = 1
single ?@mut anyopaque = &one single ?@mut anyopaque = &one
typed ?@mut u8 = ptr_cast(u8, single) typed ?@mut u8 = ptrcast!(u8, single)
if typed |p| { if typed |p| {
p^ = 2 p^ = 2
} }
@@ -1858,16 +1884,16 @@ main func() void {
} }
@(test) @(test)
anyopaque_by_value_and_invalid_ptr_casts_are_rejected :: proc(t: ^testing.T) { anyopaque_by_value_and_invalid_ptrcasts_are_rejected :: proc(t: ^testing.T) {
text := `Callback :: alias c_func() void text := `Callback :: alias c_func() void
main func() void { main func() void {
raw ?*mut anyopaque = none raw ?*mut anyopaque = none
value anyopaque = undefined value anyopaque = undefined
_ = ptr_cast(void, raw) _ = ptrcast!(void, raw)
_ = ptr_cast(anyopaque, raw) _ = ptrcast!(anyopaque, raw)
_ = ptr_cast(Callback, raw) _ = ptrcast!(Callback, raw)
_ = ptr_cast(u8, 1) _ = ptrcast!(u8, 1)
_ = ptr_cast(1, raw) _ = ptrcast!(1, raw)
} }
` `
source_file := source.Source{path="test.bro", text=text} source_file := source.Source{path="test.bro", text=text}
@@ -1888,9 +1914,9 @@ main func() void {
found_target_type := false found_target_type := false
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
found_by_value = found_by_value || strings.contains(diagnostic.message, "could not infer a concrete type") found_by_value = found_by_value || strings.contains(diagnostic.message, "could not infer a concrete type")
found_bad_target = found_bad_target || strings.contains(diagnostic.message, "ptr_cast target must be a sized runtime object type") found_bad_target = found_bad_target || strings.contains(diagnostic.message, "ptrcast! target must be a sized runtime object type")
found_bad_operand = found_bad_operand || strings.contains(diagnostic.message, "ptr_cast operand must be a pointer") found_bad_operand = found_bad_operand || strings.contains(diagnostic.message, "ptrcast! operand must be a pointer")
found_target_type = found_target_type || strings.contains(diagnostic.message, "ptr_cast target must be a type") found_target_type = found_target_type || strings.contains(diagnostic.message, "ptrcast! target must be a type")
} }
testing.expect(t, found_by_value) testing.expect(t, found_by_value)
testing.expect(t, found_bad_target) testing.expect(t, found_bad_target)
@@ -1898,6 +1924,138 @@ main func() void {
testing.expect(t, found_target_type) testing.expect(t, found_target_type)
} }
@(test)
old_intrinsic_spellings_are_not_recognized :: proc(t: ^testing.T) {
text := `main func() void {
_ = ptr_cast(1, 1)
_ = size_of(1)
_ = align_of(1)
_ = min_value(1)
_ = max_value(1)
_ = div_trunc(1, 1)
_ = div_floor(1, 1)
_ = div_exact(1, 1)
_ = div_ceil(1, 1)
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
names := [?]string{
"ptr_cast", "size_of", "align_of", "min_value", "max_value",
"div_trunc", "div_floor", "div_exact", "div_ceil",
}
found := [len(names)]bool{}
for diagnostic in diagnostics.items {
if !strings.contains(diagnostic.message, "unresolved function") {
continue
}
for name, index in names {
found[index] = found[index] || strings.contains(diagnostic.message, name)
}
}
for value in found {
testing.expect(t, value)
}
}
@(test)
bare_intrinsic_names_are_available_to_user_functions :: proc(t: ^testing.T) {
text := `ptrcast func() i32 { return 1 }
sizeof func() i32 { return 2 }
alignof func() i32 { return 3 }
minval func() i32 { return 4 }
maxval func() i32 { return 5 }
divtrunc func() i32 { return 6 }
divfloor func() i32 { return 7 }
divexact func() i32 { return 8 }
divceil func() i32 { return 9 }
rem func() i32 { return 10 }
mod func() i32 { return 11 }
main func() i32 {
return ptrcast() + sizeof() + alignof() + minval() + maxval() +
divtrunc() + divfloor() + divexact() + divceil() + rem() + mod() - 66
}
`
directory := "/tmp/brolang-test-user-intrinsic-names"
main_path := "/tmp/brolang-test-user-intrinsic-names/main.bro"
output := "/tmp/brolang-test-user-intrinsic-names-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)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
}
@(test)
intrinsic_call_diagnostics_are_precise :: proc(t: ^testing.T) {
text := `main func() void {
_ = mystery!()
_ = math.ptrcast!()
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found_unknown := false
found_qualified := false
for diagnostic in diagnostics.items {
found_unknown = found_unknown || diagnostic.message == "unknown intrinsic 'mystery!'"
found_qualified = found_qualified || diagnostic.message == "intrinsic calls must be unqualified"
}
testing.expect(t, found_unknown)
testing.expect(t, found_qualified)
}
@(test)
malformed_intrinsic_calls_have_targeted_parse_diagnostics :: proc(t: ^testing.T) {
cases := [?]struct {
text, message: string,
}{
{`main func() void { _ = sizeof! }`, "expected '(' after intrinsic name"},
{`callback func() void {}
main func() void { (callback)!() }
`, "intrinsic calls require a direct name"},
}
for test_case in cases {
source_file := source.Source{path="test.bro", text=test_case.text}
diagnostics := source.init_diagnostics(&source_file)
symbols := symbol.init_table()
stream := lexer.lex(&source_file, &diagnostics, &symbols)
module := parser.parse(&stream, &source_file, &diagnostics)
found := false
for diagnostic in diagnostics.items {
found = found || diagnostic.message == test_case.message
}
testing.expect(t, found)
ast.destroy_module(&module)
delete(stream.items)
symbol.destroy_table(&symbols)
source.destroy_diagnostics(&diagnostics)
}
}
@(test) @(test)
aarch64_c_record_abi_classifies_fixed_parameters_and_results :: proc(t: ^testing.T) { aarch64_c_record_abi_classifies_fixed_parameters_and_results :: proc(t: ^testing.T) {
text := `Small :: c_struct { text := `Small :: c_struct {
@@ -2289,7 +2447,7 @@ return_sink_and_unconsumed_values_have_distinct_hir :: proc(t: ^testing.T) {
return 1 return 1
} }
done func() void { done func() void {
return _ return
} }
main func() void { main func() void {
done() done()
@@ -2327,6 +2485,96 @@ main func() void {
testing.expect_value(t, hir_module.statements[main.body[2]].kind, hir.Stmt_Kind.Trap) testing.expect_value(t, hir_module.statements[main.body[2]].kind, hir.Stmt_Kind.Trap)
} }
@(test)
bare_returns_and_strict_yields :: proc(t: ^testing.T) {
text := `Failure :: enum { bad }
noop func() void {}
newline_return func() void {
return
}
inline_return func() void { return }
fallible_return func() void ! Failure { return }
split_return func() i32 {
return
1
}
old_return func() void { return _ }
missing_yield func() i32 {
value :: {
yield
1
}
return value
}
missing_labeled_yield func() i32 {
value :: block: {
yield :block
}
return value
}
void_yield func() i32 {
value :: { yield noop() }
return value
}
sink_yield func() i32 {
value :: { yield _ }
return value
}
void_context func() void {
fallible_return() catch |_| { yield 1 }
}
bad_comptime :: ${ yield noop() }
main func() void {
newline_return()
inline_return()
fallible_return() catch |_| {}
_ = split_return()
old_return()
_ = missing_yield()
_ = missing_labeled_yield()
_ = void_yield()
_ = sink_yield()
void_context()
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, ast_module.statements[ast_module.functions[1].body[0]].expr, ast.INVALID_EXPR)
testing.expect_value(t, ast_module.statements[ast_module.functions[2].body[0]].expr, ast.INVALID_EXPR)
testing.expect_value(t, ast_module.statements[ast_module.functions[3].body[0]].expr, ast.INVALID_EXPR)
testing.expect_value(t, len(ast_module.functions[4].body), 2)
missing_value := false
non_void_function := false
void_yields := 0
sink_read := false
void_context := false
for diagnostic in diagnostics.items {
missing_value = missing_value || strings.contains(diagnostic.message, "'yield' must produce a value")
non_void_function = non_void_function || strings.contains(diagnostic.message, "non-void function must return a value")
if strings.contains(diagnostic.message, "'yield' expression must produce a non-void value") {
void_yields += 1
}
sink_read = sink_read || strings.contains(diagnostic.message, "'_' is a write-only sink and cannot be read")
void_context = void_context || strings.contains(diagnostic.message, "void value context must fall through instead of yielding")
}
testing.expect(t, missing_value)
testing.expect(t, non_void_function)
testing.expect(t, void_yields >= 2)
testing.expect(t, sink_read)
testing.expect(t, void_context)
}
@(test) @(test)
unused_locals_and_params_warn_without_traps :: proc(t: ^testing.T) { unused_locals_and_params_warn_without_traps :: proc(t: ^testing.T) {
text := `warn_only func(value i32, unused i32) i32 { text := `warn_only func(value i32, unused i32) i32 {
@@ -6451,7 +6699,7 @@ main func() void {}
@(test) @(test)
constant_division_by_zero_has_a_precise_diagnostic :: proc(t: ^testing.T) { constant_division_by_zero_has_a_precise_diagnostic :: proc(t: ^testing.T) {
text := `value :: div_trunc(5, 0) text := `value :: divtrunc!(5, 0)
main func() void {} main func() void {}
` `
source_file := source.Source{path="test.bro", text=text} source_file := source.Source{path="test.bro", text=text}
@@ -9579,12 +9827,12 @@ compound_assignment_preserves_checked_numeric_operations :: proc(t: ^testing.T)
signed += 6 signed += 6
signed -= 2 signed -= 2
signed *= 3 signed *= 3
signed = div_trunc(signed, 4) signed = divtrunc!(signed, 4)
unsigned u32 = 24 unsigned u32 = 24
unsigned += 6 unsigned += 6
unsigned -= 2 unsigned -= 2
unsigned *= 3 unsigned *= 3
unsigned = div_trunc(unsigned, 4) unsigned = divtrunc!(unsigned, 4)
real f64 = 24.0 real f64 = 24.0
real += 6.0 real += 6.0
real -= 2.0 real -= 2.0
@@ -9748,7 +9996,7 @@ checked_division_and_subtraction_emit_guarded_llvm :: proc(t: ^testing.T) {
a i32 = 10 a i32 = 10
b i32 = 3 b i32 = 3
c i32 = a - b c i32 = a - b
return div_trunc(c, b) return divtrunc!(c, b)
} }
` `
source_file := source.Source{path="test.bro", text=text} source_file := source.Source{path="test.bro", text=text}
@@ -9843,20 +10091,20 @@ main func() void { _ = $half(4) }`, want="integer '/' is not allowed"},
@(test) @(test)
division_builtins_diagnose_arity_operands_and_comptime_failures :: proc(t: ^testing.T) { division_builtins_diagnose_arity_operands_and_comptime_failures :: proc(t: ^testing.T) {
text := `bad_arity :: div_floor(1) text := `bad_arity :: divfloor!(1)
bad_bool :: rem(true, false) bad_bool :: rem!(true, false)
bad_family :: mod(i32(5), f32(3)) bad_family :: mod!(i32(5), f32(3))
zero_trunc :: div_trunc(1, 0) zero_trunc :: divtrunc!(1, 0)
zero_floor :: div_floor(1.0, 0.0) zero_floor :: divfloor!(1.0, 0.0)
zero_exact :: div_exact(1, 0) zero_exact :: divexact!(1, 0)
zero_ceil :: div_ceil(1.0, 0.0) zero_ceil :: divceil!(1.0, 0.0)
zero_rem :: rem(1, 0) zero_rem :: rem!(1, 0)
zero_mod :: mod(1.0, 0.0) zero_mod :: mod!(1.0, 0.0)
inexact :: div_exact(5, 3) inexact :: divexact!(5, 3)
overflow_trunc :: div_trunc(min_value(i32), -1) overflow_trunc :: divtrunc!(minval!(i32), -1)
overflow_floor :: div_floor(min_value(i32), -1) overflow_floor :: divfloor!(minval!(i32), -1)
overflow_exact :: div_exact(min_value(i32), -1) overflow_exact :: divexact!(minval!(i32), -1)
overflow_ceil :: div_ceil(min_value(i32), -1) overflow_ceil :: divceil!(minval!(i32), -1)
main func() void {} main func() void {}
` `
source_file := source.Source{path="test.bro", text=text} source_file := source.Source{path="test.bro", text=text}
@@ -9896,28 +10144,28 @@ division_family_compiles_and_runs_for_integer_and_float_scalars :: proc(t: ^test
directory := "/tmp/brolang-test-division-family" directory := "/tmp/brolang-test-division-family"
main_path := "/tmp/brolang-test-division-family/main.bro" main_path := "/tmp/brolang-test-division-family/main.bro"
output := "/tmp/brolang-test-division-family-output" output := "/tmp/brolang-test-division-family-output"
text := `COUNT :: div_exact(8, 2) text := `COUNT :: divexact!(8, 2)
items [div_ceil(10, 3)]u8 :: [0, 0, 0, 0] items [divceil!(10, 3)]u8 :: [0, 0, 0, 0]
OPEN :: 5 OPEN :: 5
open_ceil i32 :: div_ceil(OPEN, 3) open_ceil i32 :: divceil!(OPEN, 3)
check_i32 func(a, b, qt, qf, qc, r, m i32) bool { check_i32 func(a, b, qt, qf, qc, r, m i32) bool {
return div_trunc(a, b) == qt and div_floor(a, b) == qf and return divtrunc!(a, b) == qt and divfloor!(a, b) == qf and
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m divceil!(a, b) == qc and rem!(a, b) == r and mod!(a, b) == m
} }
check_f32 func(a, b, qt, qf, qc, r, m f32) bool { check_f32 func(a, b, qt, qf, qc, r, m f32) bool {
return div_trunc(a, b) == qt and div_floor(a, b) == qf and return divtrunc!(a, b) == qt and divfloor!(a, b) == qf and
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m divceil!(a, b) == qc and rem!(a, b) == r and mod!(a, b) == m
} }
check_f64 func(a, b, qt, qf, qc, r, m f64) bool { check_f64 func(a, b, qt, qf, qc, r, m f64) bool {
return div_trunc(a, b) == qt and div_floor(a, b) == qf and return divtrunc!(a, b) == qt and divfloor!(a, b) == qf and
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m divceil!(a, b) == qc and rem!(a, b) == r and mod!(a, b) == m
} }
edge_rem func(a, b i32) i32 { return rem(a, b) } edge_rem func(a, b i32) i32 { return rem!(a, b) }
edge_mod func(a, b i32) i32 { return mod(a, b) } edge_mod func(a, b i32) i32 { return mod!(a, b) }
main func() i32 { main func() i32 {
if COUNT != 4 or items.len != 4 or open_ceil != 2 { return 1 } if COUNT != 4 or items.len != 4 or open_ceil != 2 { return 1 }
@@ -9925,9 +10173,9 @@ main func() i32 {
if !check_i32(5, -3, -1, -2, -1, 2, -1) { return 3 } if !check_i32(5, -3, -1, -2, -1, 2, -1) { return 3 }
if !check_i32(-5, 3, -1, -2, -1, -2, 1) { return 4 } if !check_i32(-5, 3, -1, -2, -1, -2, 1) { return 4 }
if !check_i32(-5, -3, 1, 1, 2, -2, -2) { return 5 } if !check_i32(-5, -3, 1, 1, 2, -2, -2) { return 5 }
if div_trunc(u32(5), u32(3)) != 1 or div_floor(u32(5), u32(3)) != 1 or if divtrunc!(u32(5), u32(3)) != 1 or divfloor!(u32(5), u32(3)) != 1 or
div_ceil(u32(5), u32(3)) != 2 or rem(u32(5), u32(3)) != 2 or mod(u32(5), u32(3)) != 2 { return 6 } divceil!(u32(5), u32(3)) != 2 or rem!(u32(5), u32(3)) != 2 or mod!(u32(5), u32(3)) != 2 { return 6 }
if div_exact(i32(6), i32(3)) != 2 or div_exact(u32(6), u32(3)) != 2 { return 7 } if divexact!(i32(6), i32(3)) != 2 or divexact!(u32(6), u32(3)) != 2 { return 7 }
if !check_f32(f32(5.0), f32(3.0), f32(1.0), f32(1.0), f32(2.0), f32(2.0), f32(2.0)) or if !check_f32(f32(5.0), f32(3.0), f32(1.0), f32(1.0), f32(2.0), f32(2.0), f32(2.0)) or
!check_f32(f32(5.0), f32(-3.0), f32(-1.0), f32(-2.0), f32(-1.0), f32(2.0), f32(-1.0)) or !check_f32(f32(5.0), f32(-3.0), f32(-1.0), f32(-2.0), f32(-1.0), f32(2.0), f32(-1.0)) or
!check_f32(f32(-5.0), f32(3.0), f32(-1.0), f32(-2.0), f32(-1.0), f32(-2.0), f32(1.0)) or !check_f32(f32(-5.0), f32(3.0), f32(-1.0), f32(-2.0), f32(-1.0), f32(-2.0), f32(1.0)) or
@@ -9936,7 +10184,7 @@ main func() i32 {
!check_f64(5.0, -3.0, -1.0, -2.0, -1.0, 2.0, -1.0) or !check_f64(5.0, -3.0, -1.0, -2.0, -1.0, 2.0, -1.0) or
!check_f64(-5.0, 3.0, -1.0, -2.0, -1.0, -2.0, 1.0) or !check_f64(-5.0, 3.0, -1.0, -2.0, -1.0, -2.0, 1.0) or
!check_f64(-5.0, -3.0, 1.0, 1.0, 2.0, -2.0, -2.0) { return 9 } !check_f64(-5.0, -3.0, 1.0, 1.0, 2.0, -2.0, -2.0) { return 9 }
if div_exact(f32(6.0), f32(3.0)) != 2.0 or div_exact(f64(6.0), f64(3.0)) != 2.0 { return 10 } if divexact!(f32(6.0), f32(3.0)) != 2.0 or divexact!(f64(6.0), f64(3.0)) != 2.0 { return 10 }
if edge_rem(-2147483648, -1) != 0 or edge_mod(-2147483648, -1) != 0 { return 11 } if edge_rem(-2147483648, -1) != 0 or edge_mod(-2147483648, -1) != 0 { return 11 }
return 0 return 0
} }
@@ -9961,24 +10209,24 @@ division_builtins_trap_for_runtime_zero_overflow_and_inexact_results :: proc(t:
right: string, right: string,
} }
cases := [?]Case{ cases := [?]Case{
{name="div_trunc", type_name="i32", left="1", right="0"}, {name="divtrunc", type_name="i32", left="1", right="0"},
{name="div_floor", type_name="f32", left="f32(1.0)", right="f32(0.0)"}, {name="divfloor", type_name="f32", left="f32(1.0)", right="f32(0.0)"},
{name="div_exact", type_name="f64", left="1.0", right="0.0"}, {name="divexact", type_name="f64", left="1.0", right="0.0"},
{name="div_ceil", type_name="i32", left="1", right="0"}, {name="divceil", type_name="i32", left="1", right="0"},
{name="rem", type_name="f32", left="f32(1.0)", right="f32(0.0)"}, {name="rem", type_name="f32", left="f32(1.0)", right="f32(0.0)"},
{name="mod", type_name="f64", left="1.0", right="0.0"}, {name="mod", type_name="f64", left="1.0", right="0.0"},
{name="div_exact", type_name="i32", left="5", right="3"}, {name="divexact", type_name="i32", left="5", right="3"},
{name="div_trunc", type_name="i32", left="-2147483648", right="-1"}, {name="divtrunc", type_name="i32", left="-2147483648", right="-1"},
{name="div_floor", type_name="i32", left="-2147483648", right="-1"}, {name="divfloor", type_name="i32", left="-2147483648", right="-1"},
{name="div_exact", type_name="i32", left="-2147483648", right="-1"}, {name="divexact", type_name="i32", left="-2147483648", right="-1"},
{name="div_ceil", type_name="i32", left="-2147483648", right="-1"}, {name="divceil", type_name="i32", left="-2147483648", right="-1"},
} }
for test_case, index in cases { for test_case, index in cases {
directory := fmt.aprintf("/tmp/brolang-test-division-trap-%d", index) directory := fmt.aprintf("/tmp/brolang-test-division-trap-%d", index)
main_path := fmt.aprintf("%s/main.bro", directory) main_path := fmt.aprintf("%s/main.bro", directory)
output := fmt.aprintf("/tmp/brolang-test-division-trap-output-%d", index) output := fmt.aprintf("/tmp/brolang-test-division-trap-output-%d", index)
text := fmt.aprintf( text := fmt.aprintf(
"invoke func(a, b %s) %s {{ return %s(a, b) }}\nmain func() void {{ _ = invoke(%s, %s) }}\n", "invoke func(a, b %s) %s {{ return %s!(a, b) }}\nmain func() void {{ _ = invoke(%s, %s) }}\n",
test_case.type_name, test_case.type_name, test_case.name, test_case.left, test_case.right, test_case.type_name, test_case.type_name, test_case.name, test_case.left, test_case.right,
) )
_ = os2.remove_all(directory) _ = os2.remove_all(directory)
@@ -10006,10 +10254,10 @@ qualified_division_builtin_names_resolve_as_package_functions :: proc(t: ^testin
math_path := "/tmp/brolang-test-qualified-division/math/math.bro" math_path := "/tmp/brolang-test-qualified-division/math/math.bro"
main_path := "/tmp/brolang-test-qualified-division/app/main.bro" main_path := "/tmp/brolang-test-qualified-division/app/main.bro"
output := "/tmp/brolang-test-qualified-division-output" output := "/tmp/brolang-test-qualified-division-output"
math_text := `div_floor func(a, b i32) i32 { return a + b } math_text := `divfloor func(a, b i32) i32 { return a + b }
` `
main_text := `math :: import "../math" main_text := `math :: import "../math"
main func() i32 { return math.div_floor(20, 22) } main func() i32 { return math.divfloor(20, 22) }
` `
_ = os2.remove_all(directory) _ = os2.remove_all(directory)
defer _ = os2.remove_all(directory) defer _ = os2.remove_all(directory)
@@ -10027,17 +10275,17 @@ main func() i32 { return math.div_floor(20, 22) }
@(test) @(test)
division_builtins_emit_guards_rounding_and_single_integer_divisions :: proc(t: ^testing.T) { division_builtins_emit_guards_rounding_and_single_integer_divisions :: proc(t: ^testing.T) {
text := `floor_i32 func(a, b i32) i32 { return div_floor(a, b) } text := `floor_i32 func(a, b i32) i32 { return divfloor!(a, b) }
ceil_i32 func(a, b i32) i32 { return div_ceil(a, b) } ceil_i32 func(a, b i32) i32 { return divceil!(a, b) }
exact_i32 func(a, b i32) i32 { return div_exact(a, b) } exact_i32 func(a, b i32) i32 { return divexact!(a, b) }
floor_u32 func(a, b u32) u32 { return div_floor(a, b) } floor_u32 func(a, b u32) u32 { return divfloor!(a, b) }
rem_i16 func(a, b i16) i16 { return rem(a, b) } rem_i16 func(a, b i16) i16 { return rem!(a, b) }
mod_i16 func(a, b i16) i16 { return mod(a, b) } mod_i16 func(a, b i16) i16 { return mod!(a, b) }
floor_f32 func(a, b f32) f32 { return div_floor(a, b) } floor_f32 func(a, b f32) f32 { return divfloor!(a, b) }
ceil_f64 func(a, b f64) f64 { return div_ceil(a, b) } ceil_f64 func(a, b f64) f64 { return divceil!(a, b) }
exact_f32 func(a, b f32) f32 { return div_exact(a, b) } exact_f32 func(a, b f32) f32 { return divexact!(a, b) }
rem_f64 func(a, b f64) f64 { return rem(a, b) } rem_f64 func(a, b f64) f64 { return rem!(a, b) }
mod_f32 func(a, b f32) f32 { return mod(a, b) } mod_f32 func(a, b f32) f32 { return mod!(a, b) }
main func() void { main func() void {
_ = floor_i32(5, 3) _ = floor_i32(5, 3)
_ = ceil_i32(5, 3) _ = ceil_i32(5, 3)
-3
View File
@@ -23,8 +23,6 @@ _fail_allocator mem.Allocator :: mem.Allocator {
vtable = &_fail_vtable, vtable = &_fail_vtable,
} }
_noop func() void {}
run func() i32 ! mem.AllocError { run func() i32 ! mem.AllocError {
values std.ArrayList(i32) = arraylist.init(mem.c_allocator) values std.ArrayList(i32) = arraylist.init(mem.c_allocator)
defer arraylist.deinit(&values) defer arraylist.deinit(&values)
@@ -65,7 +63,6 @@ run func() i32 ! mem.AllocError {
failed_as_expected bool = false failed_as_expected bool = false
arraylist.append(&failed, 1) catch |_| { arraylist.append(&failed, 1) catch |_| {
failed_as_expected = true failed_as_expected = true
yield _noop()
} }
if (failed_as_expected == false or failed.items.len != 0 or failed.capacity != 0) return 9 if (failed_as_expected == false or failed.items.len != 0 or failed.capacity != 0) return 9
arraylist.deinit(&failed) arraylist.deinit(&failed)
@@ -14,7 +14,7 @@ check_float func() i32 {
check_unsigned func() i32 { check_unsigned func() i32 {
n u32 = 100 n u32 = 100
n = div_trunc(n, 7) # 14 n = divtrunc!(n, 7) # 14
n -= 4 # 10 n -= 4 # 10
if n == 10 { if n == 10 {
return 1 return 1
@@ -27,7 +27,7 @@ main func() i32 {
total += 10 # 10 total += 10 # 10
total -= 3 # 7 total -= 3 # 7
total *= 4 # 28 total *= 4 # 28
total = div_trunc(total, 2) # 14 total = divtrunc!(total, 2) # 14
# binary operators honour precedence: 14 + (2 * 3) - 4 == 16 # binary operators honour precedence: 14 + (2 * 3) - 4 == 16
total = total + 2 * 3 - 4 total = total + 2 * 3 - 4
@@ -2,7 +2,7 @@ mem :: import "@std/mem"
_probe_count func(context ?*mut anyopaque) void { _probe_count func(context ?*mut anyopaque) void {
if context |raw| { if context |raw| {
counts *mut usize :: ptr_cast(usize, raw) counts *mut usize :: ptrcast!(usize, raw)
counts[0] += 1 counts[0] += 1
} }
} }
@@ -28,7 +28,7 @@ _probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
} }
typed_allocator_test func() i32 { typed_allocator_test func() i32 {
if (size_of(mem.Allocator) != 16) return 31 if (sizeof!(mem.Allocator) != 16) return 31
first_calls [1]mut usize = [0] first_calls [1]mut usize = [0]
second_calls [1]mut usize = [0] second_calls [1]mut usize = [0]
@@ -76,7 +76,7 @@ typed_allocator_test func() i32 {
} }
if (overflow_fallback_failed) return 37 if (overflow_fallback_failed) return 37
overflow_failed bool = false overflow_failed bool = false
_ = mem.alloc(u64, first_allocator, max_value(usize)) catch |_| { _ = mem.alloc(u64, first_allocator, maxval!(usize)) catch |_| {
overflow_failed = true overflow_failed = true
yield overflow_fallback yield overflow_fallback
} }
+1 -1
View File
@@ -39,6 +39,6 @@ main func() i32 {
if (pointer.value != 42) return 4 if (pointer.value != 42) return 4
buffer Buffer(u8, 4) :: Buffer(u8, 4) { values = [1, 2, 3, 4] } buffer Buffer(u8, 4) :: Buffer(u8, 4) { values = [1, 2, 3, 4] }
if (buffer.values.len != 4) return 2 if (buffer.values.len != 4) return 2
if (size_of(Buffer(u8, 4)) != 4) return 5 if (sizeof!(Buffer(u8, 4)) != 4) return 5
return 0 return 0
} }
+14
View File
@@ -266,6 +266,19 @@ stmt_block_escape func() i32 {
return hits # 4 + 1000 (defer) = 1004 return hits # 4 + 1000 (defer) = 1004
} }
# A labeled block can also be exited through an ordinary nested block.
stmt_block_nested func() i32 {
hits i32 = 0
outer: {
{
hits = 1
break :outer
}
hits = 100 # skipped by break :outer
}
return hits
}
# Item B: a `none` yielded before a concrete `yield :blk` that references a block local. # Item B: a `none` yielded before a concrete `yield :blk` that references a block local.
lblock_local func() i32 { lblock_local func() i32 {
r :: blk: { r :: blk: {
@@ -319,6 +332,7 @@ main func() i32 {
if (stmt_block(0) != 2) return 132 if (stmt_block(0) != 2) return 132
if (stmt_block_escape() != 1004) return 133 if (stmt_block_escape() != 1004) return 133
if (lblock_local() != 9) return 134 if (lblock_local() != 9) return 134
if (stmt_block_nested() != 1) return 135
return 42 return 42
} }
+1
View File
@@ -37,6 +37,7 @@
(function_declaration name: (identifier) @function) (function_declaration name: (identifier) @function)
(parameter name: (identifier) @variable.parameter) (parameter name: (identifier) @variable.parameter)
(intrinsic_call_expression function: (identifier) @function.builtin)
(call_expression function: (expression (identifier) @function)) (call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function))) (call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property) (field_expression field: (identifier) @property)
+6 -6
View File
@@ -25,13 +25,13 @@ deinit func($T type, list @mut ArrayList(T)) void {
reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem.AllocError { reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem.AllocError {
if minimum_capacity <= list.capacity { if minimum_capacity <= list.capacity {
return _ return
} }
new_capacity usize = 8 new_capacity usize = 8
if list.capacity >= 8 { if list.capacity >= 8 {
half usize :: div_trunc(list.capacity, 2) half usize :: divtrunc!(list.capacity, 2)
if list.capacity > max_value(usize) - half { if list.capacity > maxval!(usize) - half {
new_capacity = minimum_capacity new_capacity = minimum_capacity
} else { } else {
new_capacity = list.capacity + half new_capacity = list.capacity + half
@@ -48,18 +48,18 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
} }
list.items = grown.ptr[..length] list.items = grown.ptr[..length]
list.capacity = new_capacity list.capacity = new_capacity
return _ return
} }
append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError { append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
length usize :: list.items.len length usize :: list.items.len
if length == max_value(usize) { if length == maxval!(usize) {
return .out_of_memory return .out_of_memory
} }
try reserve(list, length + 1) try reserve(list, length + 1)
list.items = list.items.ptr[..length + 1] list.items = list.items.ptr[..length + 1]
list.items[length] = value list.items[length] = value
return _ return
} }
clear func($T type, list @mut ArrayList(T)) void { clear func($T type, list @mut ArrayList(T)) void {
+3 -3
View File
@@ -71,12 +71,12 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
} }
offset += count offset += count
} }
return _ return
} }
_system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError { _system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
request usize = buffer.len request usize = buffer.len
maximum usize :: usize(max_value(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { if request > maximum {
request = maximum request = maximum
} }
@@ -94,7 +94,7 @@ _system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize !
_system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError { _system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
fd c_int :: c_int(stream) fd c_int :: c_int(stream)
request usize = bytes.len request usize = bytes.len
maximum usize :: usize(max_value(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { if request > maximum {
request = maximum request = maximum
} }
+16 -16
View File
@@ -44,7 +44,7 @@ eql func($T type, left, right []T) bool {
_empty_storage [1]mut u64 = [0] _empty_storage [1]mut u64 = [0]
_empty_slice func($T type, count usize) []mut T { _empty_slice func($T type, count usize) []mut T {
pointer *mut T :: ptr_cast(T, (&_empty_storage).ptr) pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
return pointer[..count] return pointer[..count]
} }
@@ -57,17 +57,17 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
return _empty_slice(T, 0) return _empty_slice(T, 0)
} }
element_size usize :: size_of(T) element_size usize :: sizeof!(T)
if element_size == 0 { if element_size == 0 {
return _empty_slice(T, count) return _empty_slice(T, count)
} }
if count > div_trunc(max_value(usize), element_size) { if count > divtrunc!(maxval!(usize), element_size) {
return .out_of_memory return .out_of_memory
} }
memory ?*mut u8 = raw_alloc(allocator, count * element_size, align_of(T)) memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
if memory |bytes| { if memory |bytes| {
pointer *mut T :: ptr_cast(T, bytes) pointer *mut T :: ptrcast!(T, bytes)
return pointer[..count] return pointer[..count]
} }
return .out_of_memory return .out_of_memory
@@ -82,18 +82,18 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
return _empty_slice(T, 0) return _empty_slice(T, 0)
} }
element_size usize :: size_of(T) element_size usize :: sizeof!(T)
if element_size == 0 { if element_size == 0 {
return _empty_slice(T, new_count) return _empty_slice(T, new_count)
} }
if new_count > div_trunc(max_value(usize), element_size) { if new_count > divtrunc!(maxval!(usize), element_size) {
return .out_of_memory return .out_of_memory
} }
old_memory ?*mut u8 = none old_memory ?*mut u8 = none
old_size usize = 0 old_size usize = 0
if memory.len != 0 { if memory.len != 0 {
old_memory = ptr_cast(u8, memory.ptr) old_memory = ptrcast!(u8, memory.ptr)
old_size = memory.len * element_size old_size = memory.len * element_size
} }
resized ?*mut u8 = raw_realloc( resized ?*mut u8 = raw_realloc(
@@ -101,18 +101,18 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
old_memory, old_memory,
old_size, old_size,
new_count * element_size, new_count * element_size,
align_of(T), alignof!(T),
) )
if resized |bytes| { if resized |bytes| {
pointer *mut T :: ptr_cast(T, bytes) pointer *mut T :: ptrcast!(T, bytes)
return pointer[..new_count] return pointer[..new_count]
} }
return .out_of_memory return .out_of_memory
} }
free func($T type, allocator Allocator, memory []mut T) void { free func($T type, allocator Allocator, memory []mut T) void {
if memory.len != 0 and size_of(T) != 0 { if memory.len != 0 and sizeof!(T) != 0 {
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T)) raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
} }
} }
@@ -125,7 +125,7 @@ _power_of_two func(value usize) bool {
current usize = value current usize = value
while current > 1 { while current > 1 {
half usize = div_trunc(current, 2) half usize = divtrunc!(current, 2)
if half * 2 != current { if half * 2 != current {
return false return false
} }
@@ -141,7 +141,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
} }
if alignment <= _malloc_alignment { if alignment <= _malloc_alignment {
return ptr_cast(u8, c.malloc(c_ulong(size))) return ptrcast!(u8, c.malloc(c_ulong(size)))
} }
memory [1]mut ?*mut anyopaque = [none] memory [1]mut ?*mut anyopaque = [none]
@@ -150,7 +150,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
return none return none
} }
return ptr_cast(u8, memory[0]) return ptrcast!(u8, memory[0])
} }
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
@@ -165,7 +165,7 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
if memory |old_memory| { if memory |old_memory| {
if alignment <= _malloc_alignment { if alignment <= _malloc_alignment {
return ptr_cast(u8, c.realloc(old_memory, c_ulong(new_size))) return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
} }
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment) new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
+2 -2
View File
@@ -44,7 +44,7 @@ _append func(tokens @mut std.ArrayList(Token), kind Kind, start, end usize) void
length = end - start, length = end - start,
kind = kind, kind = kind,
}) })
return _ return
} }
lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError { lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
@@ -92,7 +92,7 @@ lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
} }
} }
try _append(tokens, .eof, cursor, cursor) try _append(tokens, .eof, cursor, cursor)
return _ return
} }
_kind_name func(kind Kind) *c_char { _kind_name func(kind Kind) *c_char {
+6 -6
View File
@@ -25,13 +25,13 @@ deinit func($T type, list @mut ArrayList(T)) void {
reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem.AllocError { reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem.AllocError {
if minimum_capacity <= list.capacity { if minimum_capacity <= list.capacity {
return _ return
} }
new_capacity usize = 8 new_capacity usize = 8
if list.capacity >= 8 { if list.capacity >= 8 {
half usize :: div_trunc(list.capacity, 2) half usize :: divtrunc!(list.capacity, 2)
if list.capacity > max_value(usize) - half { if list.capacity > maxval!(usize) - half {
new_capacity = minimum_capacity new_capacity = minimum_capacity
} else { } else {
new_capacity = list.capacity + half new_capacity = list.capacity + half
@@ -48,18 +48,18 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
} }
list.items = grown.ptr[..length] list.items = grown.ptr[..length]
list.capacity = new_capacity list.capacity = new_capacity
return _ return
} }
append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError { append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
length usize :: list.items.len length usize :: list.items.len
if length == max_value(usize) { if length == maxval!(usize) {
return .out_of_memory return .out_of_memory
} }
try reserve(list, length + 1) try reserve(list, length + 1)
list.items = list.items.ptr[..length + 1] list.items = list.items.ptr[..length + 1]
list.items[length] = value list.items[length] = value
return _ return
} }
clear func($T type, list @mut ArrayList(T)) void { clear func($T type, list @mut ArrayList(T)) void {
+3 -3
View File
@@ -71,12 +71,12 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
} }
offset += count offset += count
} }
return _ return
} }
_system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError { _system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
request usize = buffer.len request usize = buffer.len
maximum usize :: usize(max_value(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { if request > maximum {
request = maximum request = maximum
} }
@@ -94,7 +94,7 @@ _system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize !
_system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError { _system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
fd c_int :: c_int(stream) fd c_int :: c_int(stream)
request usize = bytes.len request usize = bytes.len
maximum usize :: usize(max_value(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { if request > maximum {
request = maximum request = maximum
} }
+16 -16
View File
@@ -44,7 +44,7 @@ eql func($T type, left, right []T) bool {
_empty_storage [1]mut u64 = [0] _empty_storage [1]mut u64 = [0]
_empty_slice func($T type, count usize) []mut T { _empty_slice func($T type, count usize) []mut T {
pointer *mut T :: ptr_cast(T, (&_empty_storage).ptr) pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
return pointer[..count] return pointer[..count]
} }
@@ -57,17 +57,17 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
return _empty_slice(T, 0) return _empty_slice(T, 0)
} }
element_size usize :: size_of(T) element_size usize :: sizeof!(T)
if element_size == 0 { if element_size == 0 {
return _empty_slice(T, count) return _empty_slice(T, count)
} }
if count > div_trunc(max_value(usize), element_size) { if count > divtrunc!(maxval!(usize), element_size) {
return .out_of_memory return .out_of_memory
} }
memory ?*mut u8 = raw_alloc(allocator, count * element_size, align_of(T)) memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
if memory |bytes| { if memory |bytes| {
pointer *mut T :: ptr_cast(T, bytes) pointer *mut T :: ptrcast!(T, bytes)
return pointer[..count] return pointer[..count]
} }
return .out_of_memory return .out_of_memory
@@ -82,18 +82,18 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
return _empty_slice(T, 0) return _empty_slice(T, 0)
} }
element_size usize :: size_of(T) element_size usize :: sizeof!(T)
if element_size == 0 { if element_size == 0 {
return _empty_slice(T, new_count) return _empty_slice(T, new_count)
} }
if new_count > div_trunc(max_value(usize), element_size) { if new_count > divtrunc!(maxval!(usize), element_size) {
return .out_of_memory return .out_of_memory
} }
old_memory ?*mut u8 = none old_memory ?*mut u8 = none
old_size usize = 0 old_size usize = 0
if memory.len != 0 { if memory.len != 0 {
old_memory = ptr_cast(u8, memory.ptr) old_memory = ptrcast!(u8, memory.ptr)
old_size = memory.len * element_size old_size = memory.len * element_size
} }
resized ?*mut u8 = raw_realloc( resized ?*mut u8 = raw_realloc(
@@ -101,18 +101,18 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
old_memory, old_memory,
old_size, old_size,
new_count * element_size, new_count * element_size,
align_of(T), alignof!(T),
) )
if resized |bytes| { if resized |bytes| {
pointer *mut T :: ptr_cast(T, bytes) pointer *mut T :: ptrcast!(T, bytes)
return pointer[..new_count] return pointer[..new_count]
} }
return .out_of_memory return .out_of_memory
} }
free func($T type, allocator Allocator, memory []mut T) void { free func($T type, allocator Allocator, memory []mut T) void {
if memory.len != 0 and size_of(T) != 0 { if memory.len != 0 and sizeof!(T) != 0 {
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T)) raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
} }
} }
@@ -125,7 +125,7 @@ _power_of_two func(value usize) bool {
current usize = value current usize = value
while current > 1 { while current > 1 {
half usize = div_trunc(current, 2) half usize = divtrunc!(current, 2)
if half * 2 != current { if half * 2 != current {
return false return false
} }
@@ -141,7 +141,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
} }
if alignment <= _malloc_alignment { if alignment <= _malloc_alignment {
return ptr_cast(u8, c.malloc(c_ulong(size))) return ptrcast!(u8, c.malloc(c_ulong(size)))
} }
memory [1]mut ?*mut anyopaque = [none] memory [1]mut ?*mut anyopaque = [none]
@@ -150,7 +150,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
return none return none
} }
return ptr_cast(u8, memory[0]) return ptrcast!(u8, memory[0])
} }
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
@@ -165,7 +165,7 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
if memory |old_memory| { if memory |old_memory| {
if alignment <= _malloc_alignment { if alignment <= _malloc_alignment {
return ptr_cast(u8, c.realloc(old_memory, c_ulong(new_size))) return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
} }
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment) new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
+1 -1
View File
@@ -6,7 +6,7 @@ main func() void {
data :: mem.alloc(u8, allocator, 24) catch |_| { data :: mem.alloc(u8, allocator, 24) catch |_| {
_ = c.printf("Failed to allocate memory\n") _ = c.printf("Failed to allocate memory\n")
return _ return
} }
defer mem.free(u8, allocator, data) defer mem.free(u8, allocator, data)
+10 -10
View File
@@ -30,7 +30,7 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
_empty_storage [1]mut u64 = [0] _empty_storage [1]mut u64 = [0]
_empty_slice func($T type, count usize) []mut T { _empty_slice func($T type, count usize) []mut T {
pointer *mut T :: ptr_cast(T, (&_empty_storage).ptr) pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
return pointer[..count] return pointer[..count]
} }
@@ -39,25 +39,25 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
return _empty_slice(T, 0) return _empty_slice(T, 0)
} }
element_size usize :: size_of(T) element_size usize :: sizeof!(T)
if element_size == 0 { if element_size == 0 {
return _empty_slice(T, count) return _empty_slice(T, count)
} }
if count > max_value(usize) / element_size { if count > maxval!(usize) / element_size {
return .out_of_memory return .out_of_memory
} }
memory ?*mut u8 = raw_alloc(allocator, count * element_size, align_of(T)) memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
if memory |bytes| { if memory |bytes| {
pointer *mut T :: ptr_cast(T, bytes) pointer *mut T :: ptrcast!(T, bytes)
return pointer[..count] return pointer[..count]
} }
return .out_of_memory return .out_of_memory
} }
free func($T type, allocator Allocator, memory []mut T) void { free func($T type, allocator Allocator, memory []mut T) void {
if memory.len != 0 and size_of(T) != 0 { if memory.len != 0 and sizeof!(T) != 0 {
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T)) raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
} }
} }
@@ -86,7 +86,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
} }
if alignment <= _malloc_alignment { if alignment <= _malloc_alignment {
return ptr_cast(u8, c.malloc(c_ulong(size))) return ptrcast!(u8, c.malloc(c_ulong(size)))
} }
memory [1]mut ?*mut anyopaque = [none] memory [1]mut ?*mut anyopaque = [none]
@@ -95,7 +95,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
return none return none
} }
return ptr_cast(u8, memory[0]) return ptrcast!(u8, memory[0])
} }
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
@@ -110,7 +110,7 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
if memory |old_memory| { if memory |old_memory| {
if alignment <= _malloc_alignment { if alignment <= _malloc_alignment {
return ptr_cast(u8, c.realloc(old_memory, c_ulong(new_size))) return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
} }
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment) new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
+10 -5
View File
@@ -262,15 +262,13 @@ module.exports = grammar({
expression_statement: $ => $.expression, expression_statement: $ => $.expression,
return_statement: $ => seq( return_statement: $ => prec.right(seq(
'return', 'return',
repeat($._newline), optional(field('value', $._value)),
field('value', $._value), )),
),
yield_statement: $ => seq( yield_statement: $ => seq(
'yield', 'yield',
repeat($._newline),
optional(seq(':', field('label', $.identifier))), optional(seq(':', field('label', $.identifier))),
field('value', $._value), field('value', $._value),
), ),
@@ -385,6 +383,7 @@ module.exports = grammar({
$.catch_expression, $.catch_expression,
$.unary_expression, $.unary_expression,
$.field_expression, $.field_expression,
$.intrinsic_call_expression,
$.call_expression, $.call_expression,
$.index_expression, $.index_expression,
$.slice_expression, $.slice_expression,
@@ -442,6 +441,12 @@ module.exports = grammar({
field('field', $.identifier), field('field', $.identifier),
)), )),
intrinsic_call_expression: $ => prec(PREC.POSTFIX, seq(
field('function', $.identifier),
field('marker', '!'),
field('arguments', $.argument_list),
)),
call_expression: $ => prec.left(PREC.POSTFIX, seq( call_expression: $ => prec.left(PREC.POSTFIX, seq(
field('function', $.expression), field('function', $.expression),
field('arguments', $.argument_list), field('arguments', $.argument_list),
@@ -37,6 +37,7 @@
(function_declaration name: (identifier) @function) (function_declaration name: (identifier) @function)
(parameter name: (identifier) @variable.parameter) (parameter name: (identifier) @variable.parameter)
(intrinsic_call_expression function: (identifier) @function.builtin)
(call_expression function: (expression (identifier) @function)) (call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function))) (call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property) (field_expression field: (identifier) @property)
+48 -13
View File
@@ -1714,6 +1714,9 @@
"name": "expression" "name": "expression"
}, },
"return_statement": { "return_statement": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{ {
@@ -1721,12 +1724,8 @@
"value": "return" "value": "return"
}, },
{ {
"type": "REPEAT", "type": "CHOICE",
"content": { "members": [
"type": "SYMBOL",
"name": "_newline"
}
},
{ {
"type": "FIELD", "type": "FIELD",
"name": "value", "name": "value",
@@ -1734,8 +1733,14 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "_value" "name": "_value"
} }
},
{
"type": "BLANK"
} }
] ]
}
]
}
}, },
"yield_statement": { "yield_statement": {
"type": "SEQ", "type": "SEQ",
@@ -1744,13 +1749,6 @@
"type": "STRING", "type": "STRING",
"value": "yield" "value": "yield"
}, },
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{ {
"type": "CHOICE", "type": "CHOICE",
"members": [ "members": [
@@ -2698,6 +2696,10 @@
"type": "SYMBOL", "type": "SYMBOL",
"name": "field_expression" "name": "field_expression"
}, },
{
"type": "SYMBOL",
"name": "intrinsic_call_expression"
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "call_expression" "name": "call_expression"
@@ -3273,6 +3275,39 @@
] ]
} }
}, },
"intrinsic_call_expression": {
"type": "PREC",
"value": 9,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "function",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "FIELD",
"name": "marker",
"content": {
"type": "STRING",
"value": "!"
}
},
{
"type": "FIELD",
"name": "arguments",
"content": {
"type": "SYMBOL",
"name": "argument_list"
}
}
]
}
},
"call_expression": { "call_expression": {
"type": "PREC_LEFT", "type": "PREC_LEFT",
"value": 9, "value": 9,
+41 -1
View File
@@ -730,6 +730,10 @@
"type": "integer", "type": "integer",
"named": true "named": true
}, },
{
"type": "intrinsic_call_expression",
"named": true
},
{ {
"type": "multiline_string", "type": "multiline_string",
"named": true "named": true
@@ -1309,6 +1313,42 @@
] ]
} }
}, },
{
"type": "intrinsic_call_expression",
"named": true,
"fields": {
"arguments": {
"multiple": false,
"required": true,
"types": [
{
"type": "argument_list",
"named": true
}
]
},
"function": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
},
"marker": {
"multiple": false,
"required": true,
"types": [
{
"type": "!",
"named": false
}
]
}
}
},
{ {
"type": "keyed_field_initializer", "type": "keyed_field_initializer",
"named": true, "named": true,
@@ -1667,7 +1707,7 @@
"fields": { "fields": {
"value": { "value": {
"multiple": false, "multiple": false,
"required": true, "required": false,
"types": [ "types": [
{ {
"type": "block", "type": "block",
+97498 -99456
View File
File diff suppressed because it is too large Load Diff
@@ -96,6 +96,34 @@ sum func(a, b i32) i32 ! Status {
(enum_literal (enum_literal
(identifier)))))))))))) (identifier))))))))))))
==================
Intrinsic calls
==================
main func() void {
_ = sizeof ! (i32)
}
---
(source_file
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(assignment_statement
(expression
(sink))
(expression
(intrinsic_call_expression
(identifier)
(argument_list
(expression
(builtin_type))))))))))
================== ==================
Errdefer Errdefer
================== ==================
@@ -154,3 +182,56 @@ work func() i32 ! Failure {
(return_statement (return_statement
(expression (expression
(integer))))))) (integer)))))))
==================
Bare return and value yield
==================
done func() void {
return
}
inline func() void { return }
choose func() i32 {
result :: { yield 1 }
return result
}
---
(source_file
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(return_statement))))
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(return_statement))))
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(constant_declaration
(identifier)
(block
(statement
(yield_statement
(expression
(integer)))))))
(statement
(return_statement
(expression
(identifier)))))))
@@ -0,0 +1,7 @@
main func() void {
_ = sizeof!(i32)
# ^^^^^^ function.builtin
# ^ operator
_ = sizeof(i32)
# ^^^^^^ function
}