rename intrinsics

This commit is contained in:
2026-07-14 19:22:20 +02:00
parent 471896b48a
commit 5157cf3bcc
26 changed files with 107640 additions and 106418 deletions
+26 -22
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
@@ -98,47 +98,51 @@ fields. `_` is not a keyword member name.
#### 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`
+15 -14
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
@@ -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,7 +834,8 @@
- 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. consider renaming `ptr_cast` to `ptrcast` 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
@@ -844,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
@@ -859,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,
} }
+44 -27
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{
+8 -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")
+24 -4
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)
+264 -106
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 {
@@ -6541,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}
@@ -9669,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
@@ -9838,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}
@@ -9933,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}
@@ -9986,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 }
@@ -10015,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
@@ -10026,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
} }
@@ -10051,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)
@@ -10096,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)
@@ -10117,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)
@@ -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
} }
+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)
+3 -3
View File
@@ -30,8 +30,8 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
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
@@ -53,7 +53,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
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)
+2 -2
View File
@@ -76,7 +76,7 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
_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)
+3 -3
View File
@@ -30,8 +30,8 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
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
@@ -53,7 +53,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
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)
+2 -2
View File
@@ -76,7 +76,7 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
_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)
+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)
+7
View File
@@ -383,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,
@@ -440,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)
+37
View File
@@ -2696,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"
@@ -3271,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,
+40
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,
+107077 -106183
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
================== ==================
@@ -0,0 +1,7 @@
main func() void {
_ = sizeof!(i32)
# ^^^^^^ function.builtin
# ^ operator
_ = sizeof(i32)
# ^^^^^^ function
}