Compare commits
13 Commits
378eb637c6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 00984d1728 | |||
| e804f027f3 | |||
| ecb7b2fc64 | |||
| 3a90a90428 | |||
| cbbf5e7d6c | |||
| a6e688db71 | |||
| 81531208b2 | |||
| 8ebbf72388 | |||
| eddca5c3bf | |||
| 8ab5b71a0c | |||
| 7cb4ace0fa | |||
| 7f7de7a8d2 | |||
| 5dac5baa05 |
+46
-9
@@ -30,12 +30,15 @@ roadmap and milestone history.
|
||||
- exact-width integers, concrete pointer-sized `isize` / `usize`, `f32`, `f64`, `bool`, `void`, `noreturn`, and `anyopaque`; `noreturn` is a bottom type valid as a native function result and coerces to any expected value type; contextual `int` accepts the whole integer family, while `uint` accepts only unsigned native and target-classified C integers
|
||||
- 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/bitwise expressions, and typed compile-time evaluation for numeric constant expressions
|
||||
- integer literals use decimal digits or explicit binary `0b`, octal `0o`, and hexadecimal `0x` prefixes; uppercase prefixes and hexadecimal digits are also accepted. leading-zero unprefixed literals remain decimal, signs remain unary operators, and tuple ordinals remain canonical decimal. digits must match the base and magnitudes must fit `u64` before contextual range checking; digit separators and nondecimal floating-point literals are not supported.
|
||||
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar casts through keywords or transparent aliases, such as `i32(x)`, `c_float(x)`, or `StringId(x)`
|
||||
- implicit contextual integer constants must be exactly representable in the destination float, including folded expressions and inferred constants; `f32` rejects `16777217` but accepts `16777218`, while `f64` rejects `9007199254740993` but accepts `9007199254740994`; powers of two remain exact
|
||||
- float literals retain IEEE rounding, and explicit scalar casts such as `f32(16777217)` or `f64(9007199254740993)` deliberately permit rounding; existing typed-value widening and C scalar coercion rules are unchanged
|
||||
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native, C, and scalar-backed distinct integer types; the result retains `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-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
|
||||
- `ptrcast!(T, ptr)` as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape
|
||||
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, pointer/slice sentinel weakening, and checked explicit sentinel slicing `base[start..end;S]`
|
||||
- `ptrcast!(ptr)` infers its complete pointer result from context; `ptrcast!(P, ptr)` supplies `P` explicitly; both change the child type, then permit only ordinary implicit pointer conversions to the requested target
|
||||
- unsafe `constcast!(value)` for restoring mutability to pointers, optional pointers, and slices without changing their child type or shape
|
||||
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, supporting `\\`, `\"`, `\n`, `\r`, `\t`, `\0`, and `\xNN` escapes, plus raw backtick multiline strings
|
||||
- narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange
|
||||
@@ -44,10 +47,43 @@ roadmap and milestone history.
|
||||
- compiler-reordered native structs with fields laid out by decreasing alignment (declaration order breaks ties and remains the reflection/diagnostic order), opaque nominal records with `Name :: opaque`, complete source-order `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
||||
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
|
||||
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
|
||||
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids
|
||||
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids; parenthesized types allow newlines after `(`, before `)`, and on either side of `|`, including in aliases and fallible signatures
|
||||
- fallible channel types `T ! E`, where `E` is a native enum, native struct, tagged union, or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
|
||||
- bodyful local functions and root `main` may write `T!` to infer a specialization-local error channel from propagated `try` expressions and concretely typed error returns; inference composes only existing named error types, never synthesizes variants, and requires at least one inferred error
|
||||
|
||||
#### contextual empty mutable slices
|
||||
|
||||
A fresh empty array literal address `&[]` may initialize an expected `[]mut T`,
|
||||
including local declarations, call arguments, and record fields. The context supplies
|
||||
the element type `T`; the resulting slice has length zero and no writable elements.
|
||||
This is literal-only contextual typing, not a readonly-to-mutable conversion:
|
||||
named readonly zero-length arrays, existing readonly empty slices, and nonempty
|
||||
readonly literals still cannot initialize mutable slices. Sentinel slices `[;S]T`
|
||||
and `[;S]mut T` still require backing storage containing the sentinel; `&[]` does
|
||||
not provide it.
|
||||
|
||||
#### checked sentinel slicing
|
||||
|
||||
`base[start..end;S]` constructs a sentinel slice `[;S]T` without copying elements.
|
||||
The sentinel `S` must be a compile-time integer representable by the source element type `T`;
|
||||
the result retains the source's element mutability. Bounds use `usize`, as in ordinary slicing.
|
||||
`start` defaults to zero; `end` may be omitted for arrays, pointers-to-arrays, and slices, where
|
||||
it defaults to the logical length. Many-item pointers, including sentinel pointers, always
|
||||
require an explicit end: `pointer[..count;0]`.
|
||||
|
||||
The result contains elements `[start, end)` and excludes the sentinel at `end`.
|
||||
Before constructing it, runtime checks require ordered in-range bounds, a readable endpoint,
|
||||
and `base[end] == S`; failure traps. For a bounded source without a sentinel, `end` must be
|
||||
strictly less than its length. A sentinel source permits `end == len`, reading its stored
|
||||
sentinel. A many-item pointer has no length to check: the caller must provide valid memory
|
||||
through the endpoint, and the endpoint value is still checked. Comptime evaluation enforces
|
||||
the same endpoint and sentinel requirements and diagnoses failures.
|
||||
|
||||
Ordinary `base[start..end]` slicing keeps its existing bounds rules and preserves a source
|
||||
sentinel only when the end bound is omitted. Explicit sentinel slicing can assert a different
|
||||
sentinel only when the actual endpoint matches it; it never adds a terminator or restores
|
||||
mutability.
|
||||
|
||||
#### distinct types
|
||||
|
||||
`Name :: distinct T` creates a nominal identity and reuses `T`'s runtime representation. When
|
||||
@@ -235,14 +271,14 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
||||
- comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities; equal structural values and aliases of one function declaration share specializations, while distinct declarations remain distinct and pointers, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity
|
||||
- comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole
|
||||
- direct bodyful value calls with no runtime parameters, including parameterless and all-`$` functions, are evaluated at comptime when their resolved result can materialize; otherwise they retain their zero-argument runtime specialization, while a reached `compile_error!` remains a diagnostic
|
||||
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
|
||||
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`; imports referenced only by these initializers count as used, including when the initialized global is read from another package
|
||||
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, exact type `==`/`!=`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
|
||||
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
|
||||
- `struct_type!(layout, names, types, defaults)` constructs a nominal record type from comptime fixed arrays or tuples and is valid in every type position; layout is `.auto` or `.c`, bare `null` means a required field, and `some!(null)` installs an optional `null` default
|
||||
- `some!(value)` explicitly constructs the present branch of an expected optional, including nested optionals where `some!(null)` differs from outer `null`
|
||||
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
|
||||
- anonymous keyed records use `{x = 1, name = "bro"}`; without context their declaration-ordered names and inferred value types form a structurally interned record type, while a record context applies that type's coercions and field defaults; `{}` remains an empty tuple without context and constructs an empty contextual record when a record is expected
|
||||
- `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
|
||||
- `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, including native `A | B` compositions whose resulting cases are all payload-free, and aliases retain the canonical composed type for reflection, `field!(E, name)`, and equality; reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
|
||||
- `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name
|
||||
- bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
|
||||
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
|
||||
@@ -264,9 +300,10 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
||||
- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist`
|
||||
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
|
||||
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
||||
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order, where outer `null` means no field default
|
||||
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order (or left-to-right first occurrence for payload-free compositions), where outer `null` means no field default; payload-bearing sums and nominal tagged or untagged unions are not enum keys
|
||||
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, recursively scalar-backed distinct values, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
||||
- entry points are either `main func() ...` or `main func(init process.Init) ...`; their success channel is `void`, `i32`, or `int` and may have an error channel; an unhandled entry error exits with status 1. `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
|
||||
- entry points are either `main func() ...` or `main func(init process.Init) ...`; their success channel is `void`, `i32`, or `int` and may have an error channel; an unhandled entry error exits with status 1. `std/process.Init` contains exactly `io io.Io` and `args Args`, while the system provider remains hidden inside `std/io`
|
||||
- `std/process.Args` contains exactly `values [][;0]u8`; use `args.values.len` and direct indexing. Native startup scans each argument once into heap-owned slice descriptors while borrowing, not copying, the strings; index 0 is the executable name when present, and empty arguments remain empty slices. The descriptors and their borrowed views are valid until `main` returns; the native wrapper frees the descriptors on both success and error
|
||||
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
|
||||
- `std/testing` supplies fallible `expect`, expected-first `expect_equal`, and exact compile-time `expect_type`; direct calls through
|
||||
an alias of exactly `@std/testing` receive compiler-injected source locations
|
||||
@@ -277,7 +314,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
||||
- lazy semantic checking of demanded function specializations
|
||||
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
|
||||
- demand-driven LLVM declarations for referenced foreign functions
|
||||
- root `main` may be parameterless or accept canonical `@std/process Init`; the generated C entry point obtains the hidden system I/O provider and constructs the init value
|
||||
- root `main` may be parameterless or accept canonical `@std/process Init`; the generated C entry point accepts native `argc`/`argv`, obtains the hidden system I/O provider, and constructs the init value using computed record layout. Both `Init` and `Args` must have their canonical auto-layout record shapes. Descriptor allocation is size-checked, zero-count safe, and exits with status 2 on overflow or allocation failure; parameterless `main` does not prepare arguments
|
||||
- `brolang test [root]` reuses `build.bro`, discovers only explicit test-import edges, skips the
|
||||
application entry point, and runs tests sequentially while continuing after assertion failures
|
||||
- replaceable dynamically loaded libclang C-import backend
|
||||
@@ -289,7 +326,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
||||
- 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
|
||||
- recursive type factories, reflection payloads beyond records/enums, and type-producing unions/enums
|
||||
- broader Zig-style pointer/result casts beyond V1 `ptrcast!(T, ptr)`
|
||||
- pointer-shape-changing casts beyond the safe implicit conversions accepted by `ptrcast!`
|
||||
- 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
|
||||
- result-to-argument type-demand propagation through function call boundaries
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
build:
|
||||
mkdir -p build
|
||||
odin build . -out:build/brolang
|
||||
odin build . -no-threaded-checker -out:build/brolang
|
||||
|
||||
install: build
|
||||
install -d "$(HOME)/.brolang/bin"
|
||||
|
||||
@@ -4,13 +4,18 @@ Prototype error-tolerant Brolang compiler written in Odin.
|
||||
|
||||
```sh
|
||||
mkdir -p build
|
||||
odin build . -out:build/brolang
|
||||
odin build . -no-threaded-checker -out:build/brolang
|
||||
./build/brolang examples/programs/prototype -o build/prototype
|
||||
./build/prototype
|
||||
```
|
||||
|
||||
Programs may receive the system I/O capability explicitly. Standard-stream
|
||||
helpers bind the provider, handle, and callback; `main func() ...` remains valid.
|
||||
Bootstrap builds serialize Odin type checking to avoid the `Queue(string)`
|
||||
polymorphic-type assertion observed with Odin `dev-2026-02`. Code generation
|
||||
is unchanged; `make build` and `make install` use the same flag.
|
||||
|
||||
Programs may receive system I/O and process arguments explicitly through
|
||||
`process.Init`; `main func() ...` remains valid. Standard-stream helpers bind the
|
||||
provider, handle, and callback.
|
||||
|
||||
```bro
|
||||
io :: import "@std/io"
|
||||
@@ -23,6 +28,16 @@ main func(init process.Init) void {
|
||||
}
|
||||
```
|
||||
|
||||
`Init` contains `io io.Io` and `args process.Args`. `Args.values` has type
|
||||
`[][;0]u8`: use `init.args.values.len` for the count and
|
||||
`init.args.values[index]` for a borrowed, zero-terminated argument slice.
|
||||
Index 0 is the executable name when present, and empty arguments are empty
|
||||
slices. Startup scans each native argument once and allocates only the slice
|
||||
descriptors; argument strings are never copied. The native entry wrapper owns
|
||||
the descriptors until `main` returns, freeing them on both success and error.
|
||||
An empty native argument list is supported; allocation overflow or failure exits
|
||||
with status 2. Parameterless `main` does not prepare unused arguments.
|
||||
|
||||
Bodyless `c_func` declarations bind exact external symbols and require concrete
|
||||
types. C primitives use atomic target-dependent names and remain semantically
|
||||
distinct from exact-width Brolang primitives:
|
||||
@@ -228,11 +243,13 @@ Current prototype features:
|
||||
- `#` comments
|
||||
- Immutable inferred/typed `::` bindings, mutable inferred/typed `:=` locals/globals, and `_` sinks
|
||||
- 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 `ptrcast!(T, ptr)`
|
||||
- Target-dependent atomic `c_*` primitive types, `c_func`, complete `c_struct`, `opaque`, `anyopaque`, and contextual or explicit full-result `ptrcast!`
|
||||
- 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
|
||||
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
|
||||
- Contextual integer constants and typed compile-time evaluation of arithmetic and Zig-style bitwise expressions
|
||||
- Decimal, binary (`0b101010`), octal (`0o52`), and hexadecimal (`0x2a`) integer literals, with uppercase prefix variants, checked `u64` magnitudes, and ordinary contextual typing; leading zeros alone do not select octal
|
||||
- Contextual integer-to-float constants require exact representation (`f32` accepts `16777218`, not `16777217`; `f64` accepts `9007199254740994`, not `9007199254740993`), including folded and inferred constants; float literals and explicit scalar casts retain IEEE rounding
|
||||
- Integer `~`, `&`, `|`, `xor`, guarded `<<` / `>>`, saturating `<<|`, and their compound assignments; postfix `^` remains pointer dereference
|
||||
- Scalar-backed nominal `distinct` types with same-identity runtime/comptime operators, explicit backing extraction casts, integer bounds, reflection, and recursive standard formatting
|
||||
- Directory packages with merged declarations and file-local relative imports
|
||||
|
||||
@@ -176,8 +176,8 @@
|
||||
- `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
|
||||
- C `void` function results remain `void`; C `void*` / `const void*` import and render as `?*mut anyopaque` / `?*anyopaque`
|
||||
- `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
|
||||
- `ptrcast!(ptr)` infers its complete pointer result from context; `ptrcast!(P, ptr)` accepts an explicit complete pointer type
|
||||
- both forms change the child type, then permit only ordinary implicit pointer conversions to the requested target; broader pointer-shape changes remain deferred
|
||||
|
||||
12. `undefined` as inspired by zig (implemented):
|
||||
- allow mutable local and global declarations with `undefined`
|
||||
@@ -973,7 +973,7 @@ For casts that bypass safety checks, Honey provides builtin functions:
|
||||
| -- | -- | -- |
|
||||
| `truncate(x, T)` | Keep low bits, discard rest | Never |
|
||||
| `bitcast(x, T)` | Reinterpret bits, no cast | Sizes don't match (compile error) |
|
||||
| `ptrcast!(T, p)` | Change a pointer's child type while preserving its shape | Invalid child or non-pointer operand (compile error) |
|
||||
| `ptrcast!(p)` / `ptrcast!(P, p)` | Infer or explicitly supply a complete pointer target, changing the child before ordinary pointer coercion | Missing target context, invalid target, incompatible shape, or non-pointer operand (compile error) |
|
||||
| `constcast!(p)` | Restore pointer or slice mutability | Non-pointer/slice operand (compile error) |
|
||||
|
||||
```honey
|
||||
@@ -989,8 +989,10 @@ bits := bitcast(f, u32) # IEEE 754 representation
|
||||
|
||||
# pointer casts
|
||||
buf *u8 := get_buffer()
|
||||
ints *u32 := ptrcast!(u32, buf) # element type change, same pointer shape
|
||||
writable *mut u8 := constcast!(buf) # explicit unsafe mutability restoration
|
||||
ints *u32 := ptrcast!(buf) # target inferred from the declaration
|
||||
raw ?*mut anyopaque := get_raw_buffer()
|
||||
bytes := ptrcast!(?*mut u8, raw) # complete target supplied explicitly
|
||||
writable *mut u8 := constcast!(buf) # explicit unsafe mutability restoration
|
||||
```
|
||||
|
||||
## A word on multi-unwrap
|
||||
|
||||
+253
-47
@@ -6,6 +6,7 @@ import "../source"
|
||||
import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "base:intrinsics"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:slice"
|
||||
@@ -400,6 +401,9 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
||||
case .Call, .Array, .Struct_Literal, .Slice:
|
||||
append(&expr_stack, ..expr.args)
|
||||
append(&expr_stack, expr.left)
|
||||
if expr.kind == .Slice {
|
||||
append(&expr_stack, expr.right)
|
||||
}
|
||||
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||
append(&expr_stack, expr.left)
|
||||
case .Comptime:
|
||||
@@ -743,12 +747,18 @@ comptime_slice_bytes :: proc(
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> (string, bool) {
|
||||
if expr.right != ast.INVALID_EXPR {
|
||||
return "", false
|
||||
}
|
||||
state := ct_state_make(checker, pkg, file, diagnose=false)
|
||||
defer ct_state_destroy(&state)
|
||||
value, flow, ok := ct_eval_slice_expr(&state, expr, 0)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return "", false
|
||||
}
|
||||
if item, item_ok := types.container(state.values[value].type, &checker.module.types); item_ok && item.has_sentinel {
|
||||
return "", false
|
||||
}
|
||||
return ct_value_bytes(&state, value)
|
||||
}
|
||||
|
||||
@@ -934,6 +944,30 @@ valid_ptrcast_child :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||
types.is_opaque_struct(value, &checker.module.types))
|
||||
}
|
||||
|
||||
ptrcast_target_child :: proc(checker: ^Checker, value: types.Type) -> (types.Type, bool) {
|
||||
item, ok := types.node(&checker.module.types, types.resolve_alias(value, &checker.module.types))
|
||||
if !ok {
|
||||
return types.INVALID, false
|
||||
}
|
||||
if item.kind == .Optional {
|
||||
item, ok = types.node(&checker.module.types, types.resolve_alias(item.child, &checker.module.types))
|
||||
}
|
||||
if !ok || item.kind != .Pointer || !valid_ptrcast_child(checker, item.child) {
|
||||
return types.INVALID, false
|
||||
}
|
||||
return item.child, true
|
||||
}
|
||||
|
||||
ptrcast_retyped_source :: proc(checker: ^Checker, source, target: types.Type) -> (types.Type, bool) {
|
||||
child, child_ok := ptrcast_target_child(checker, target)
|
||||
if !child_ok {
|
||||
return types.INVALID, false
|
||||
}
|
||||
retyped, retyped_ok := types.replace_pointer_child(&checker.module.types, source, child)
|
||||
return retyped, retyped_ok &&
|
||||
can_implicitly_convert_type(checker, retyped, target)
|
||||
}
|
||||
|
||||
valid_layout_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||
return types.is_runtime_value(value, &checker.module.types)
|
||||
}
|
||||
@@ -1173,6 +1207,33 @@ fits_integer_type :: proc(value: i128, value_type: types.Type, selected := targe
|
||||
return fits_signed_type(value, value_type, selected) || fits_unsigned_type(value, value_type, selected)
|
||||
}
|
||||
|
||||
integer_fits_float :: proc(value: i128, expected: types.Type, selected: target.Target) -> bool {
|
||||
if !types.is_float(expected, selected) {
|
||||
return false
|
||||
}
|
||||
precision := 0
|
||||
switch types.bits(expected, selected) {
|
||||
case 32: precision = 24
|
||||
case 64: precision = 53
|
||||
case: return false
|
||||
}
|
||||
if value == 0 {
|
||||
return true
|
||||
}
|
||||
magnitude := u128(0)
|
||||
if value < 0 {
|
||||
magnitude = u128(-(value + 1)) + 1
|
||||
} else {
|
||||
magnitude = u128(value)
|
||||
}
|
||||
// Binary floats preserve the significant bits between the first and last one.
|
||||
// Trailing zeros cost no precision, so large powers of two remain exact.
|
||||
// Unsigned magnitude also handles i128's minimum without a float round-trip.
|
||||
significant_bits := 128 - int(intrinsics.count_leading_zeros(magnitude)) -
|
||||
int(intrinsics.count_trailing_zeros(magnitude))
|
||||
return significant_bits <= precision
|
||||
}
|
||||
|
||||
fits_i64 :: proc(value: i128) -> bool {
|
||||
return fits_signed_type(value, types.I64)
|
||||
}
|
||||
@@ -1427,6 +1488,15 @@ is_numeric_constant_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> boo
|
||||
return eval_constant(checker, expr_id).kind == .Value || is_float_constant_expr(checker, expr_id)
|
||||
}
|
||||
|
||||
is_empty_array_address :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
|
||||
if expr.kind != .Address || expr.left == ast.INVALID_EXPR ||
|
||||
int(expr.left) >= len(checker.ast_module.exprs) {
|
||||
return false
|
||||
}
|
||||
operand := checker.ast_module.exprs[expr.left]
|
||||
return operand.kind == .Array && len(operand.args) == 0
|
||||
}
|
||||
|
||||
is_typed_integer_fold_candidate :: proc(checker: ^Checker, expr_id: ast.Expr_Id, depth := 0) -> bool {
|
||||
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
return false
|
||||
@@ -1723,16 +1793,47 @@ configure_entry_point :: proc(checker: ^Checker) {
|
||||
if io_package == ast.INVALID_PACKAGE {
|
||||
return
|
||||
}
|
||||
init_item, init_ok := types.node(&checker.module.types, parameter_type)
|
||||
init_fields := types.fields_for(&checker.module.types, parameter_type)
|
||||
io_field_name := symbol.intern(checker.symbols, "io")
|
||||
if !init_ok || init_item.kind != .Struct || init_item.tuple || init_item.c_layout ||
|
||||
len(init_fields) != 1 || init_fields[0].name != u32(io_field_name) ||
|
||||
!types.equal(types.resolve_alias(init_fields[0].type, &checker.module.types), io_type) {
|
||||
args_name := symbol.intern(checker.symbols, "Args")
|
||||
args_type := types.resolve_alias(
|
||||
types.find_named(&checker.module.types, u32(process_package), u32(args_name)),
|
||||
&checker.module.types,
|
||||
)
|
||||
args_item, args_ok := types.node(&checker.module.types, args_type)
|
||||
args_fields := types.fields_for(&checker.module.types, args_type)
|
||||
values_name := symbol.intern(checker.symbols, "values")
|
||||
argument_slice := types.slice(&checker.module.types, types.U8, false, true, 0)
|
||||
argument_values := types.slice(&checker.module.types, argument_slice, false)
|
||||
if !args_ok || args_item.kind != .Struct || args_item.tuple || args_item.c_layout ||
|
||||
len(args_fields) != 1 || args_fields[0].name != u32(values_name) ||
|
||||
!types.equal(types.resolve_alias(args_fields[0].type, &checker.module.types), argument_values) {
|
||||
checker.template_diagnostics[main_template] = source.add(
|
||||
checker.diagnostics,
|
||||
main.span,
|
||||
"@std/process Init must be an auto-layout record containing exactly 'io io.Io'",
|
||||
"@std/process Args must be an auto-layout record containing exactly 'values [][;0]u8'",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
init_item, init_ok := types.node(&checker.module.types, parameter_type)
|
||||
init_fields := types.fields_for(&checker.module.types, parameter_type)
|
||||
io_field_name := symbol.intern(checker.symbols, "io")
|
||||
args_field_name := symbol.intern(checker.symbols, "args")
|
||||
io_field_ok, args_field_ok := false, false
|
||||
for field in init_fields {
|
||||
field_type := types.resolve_alias(field.type, &checker.module.types)
|
||||
if field.name == u32(io_field_name) && types.equal(field_type, io_type) {
|
||||
io_field_ok = true
|
||||
}
|
||||
if field.name == u32(args_field_name) && types.equal(field_type, args_type) {
|
||||
args_field_ok = true
|
||||
}
|
||||
}
|
||||
if !init_ok || init_item.kind != .Struct || init_item.tuple || init_item.c_layout ||
|
||||
len(init_fields) != 2 || !io_field_ok || !args_field_ok {
|
||||
checker.template_diagnostics[main_template] = source.add(
|
||||
checker.diagnostics,
|
||||
main.span,
|
||||
"@std/process Init must be an auto-layout record containing exactly 'io io.Io' and 'args Args'",
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -2729,6 +2830,15 @@ match_inferred_type_pattern :: proc(
|
||||
values, bound, span, diagnose, depth+1,
|
||||
)
|
||||
}
|
||||
if actual_ok && actual_item.kind == .Slice {
|
||||
if pattern_item.has_sentinel && (!actual_item.has_sentinel || pattern_item.sentinel != actual_item.sentinel) {
|
||||
return false
|
||||
}
|
||||
return match_inferred_type_pattern(
|
||||
checker, function, prefix, pattern_item.child, actual_item.child,
|
||||
values, bound, span, diagnose, depth+1,
|
||||
)
|
||||
}
|
||||
}
|
||||
if !actual_ok || pattern_item.kind != actual_item.kind {
|
||||
resolved := type_from_syntax(checker, pattern, function.pkg, function.file)
|
||||
@@ -3032,6 +3142,15 @@ infer_call_comptime_values :: proc(
|
||||
{type_call=true, final=true},
|
||||
}
|
||||
for pass in evidence_passes {
|
||||
if pass.final {
|
||||
all_bound = true
|
||||
for value_bound in bound {
|
||||
all_bound = all_bound && value_bound
|
||||
}
|
||||
if all_bound {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for arg_id, source_index in args {
|
||||
param_index := call_param_index(mapping, source_index)
|
||||
if param_index >= len(function.params) || param_index >= len(actual_args) {
|
||||
@@ -3042,8 +3161,9 @@ infer_call_comptime_values :: proc(
|
||||
}
|
||||
arg_expr := checker.ast_module.exprs[arg_id]
|
||||
is_null := arg_expr.kind == .Null
|
||||
// An empty literal supplies no element evidence ahead of concrete arguments.
|
||||
is_weak := is_numeric_constant_expr(checker, arg_id) ||
|
||||
arg_expr.kind == .String || is_null
|
||||
arg_expr.kind == .String || is_null || is_empty_array_address(checker, arg_expr)
|
||||
contains_type_call := type_pattern_contains_type_call(
|
||||
checker, function.params[param_index].type,
|
||||
)
|
||||
@@ -3753,6 +3873,9 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
append(&stack, expr.left)
|
||||
}
|
||||
if expr.kind == .Slice && expr.right != ast.INVALID_EXPR {
|
||||
append(&stack, expr.right)
|
||||
}
|
||||
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||
append(&stack, expr.left)
|
||||
case .Comptime:
|
||||
@@ -4540,6 +4663,12 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
||||
node_count := len(checker.module.types.nodes)
|
||||
for index in 0..<node_count {
|
||||
item := checker.module.types.nodes[index]
|
||||
if item.kind == .Distinct {
|
||||
resolved := type_from_syntax(
|
||||
checker, item.child, ast.Package_Id(item.pkg), ast.File_Id(item.file),
|
||||
)
|
||||
checker.module.types.nodes[index].child = resolved
|
||||
}
|
||||
if item.kind != .Struct && item.kind != .Union {
|
||||
continue
|
||||
}
|
||||
@@ -5101,6 +5230,13 @@ infer_compound_expr :: proc(
|
||||
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
return type_from_syntax(checker, expr.type, pkg, file)
|
||||
case .Address:
|
||||
if is_empty_array_address(checker, expr) {
|
||||
if node, ok := types.node(store, expected);
|
||||
ok && node.kind == .Slice && node.mutable && !node.has_sentinel {
|
||||
child := types.array(store, node.child, 0, true)
|
||||
return types.pointer(store, child, true, false)
|
||||
}
|
||||
}
|
||||
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
return types.pointer(store, child, false, false)
|
||||
case .Deref:
|
||||
@@ -5129,6 +5265,15 @@ infer_compound_expr :: proc(
|
||||
_ = record_demand(checker, bound, types.USIZE, locals, local_types, pkg, file)
|
||||
}
|
||||
}
|
||||
if expr.right != ast.INVALID_EXPR {
|
||||
state := ct_state_make(checker, pkg, file, diagnose=false)
|
||||
defer ct_state_destroy(&state)
|
||||
sentinel, sentinel_ok := checked_slice_sentinel(&state, expr.right, item.child)
|
||||
if !sentinel_ok {
|
||||
return types.INVALID
|
||||
}
|
||||
return types.slice(store, item.child, item.mutable, true, u64(sentinel))
|
||||
}
|
||||
preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||
return types.slice(store, item.child, item.mutable, preserve, item.sentinel)
|
||||
case .Field:
|
||||
@@ -5640,18 +5785,20 @@ infer_expr :: proc(
|
||||
continue
|
||||
}
|
||||
if is_ptrcast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
if len(expr.args) != 1 && len(expr.args) != 2 {
|
||||
last = types.INVALID
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
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)
|
||||
result := types.INVALID
|
||||
if child_ok && valid_ptrcast_child(checker, child) {
|
||||
result, _ = types.replace_pointer_child(&checker.module.types, operand, child)
|
||||
target := frame.expected
|
||||
operand_index := 0
|
||||
if len(expr.args) == 2 {
|
||||
target, _ = resolve_type_argument(checker, expr.args[0], pkg, file)
|
||||
operand_index = 1
|
||||
}
|
||||
last = result
|
||||
operand := infer_nested_expr(checker, expr.args[operand_index], locals, pkg, file, demanded, local_types)
|
||||
_, target_ok := ptrcast_retyped_source(checker, operand, target)
|
||||
last = target if target_ok else types.INVALID
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
@@ -7523,12 +7670,15 @@ build_constant_expr :: proc(
|
||||
numeric_operation := false,
|
||||
) -> hir.Expr_Id {
|
||||
materialized := expected
|
||||
for types.is_optional(materialized, &checker.module.types) {
|
||||
materialized = types.child_type(materialized, &checker.module.types)
|
||||
}
|
||||
nominal := types.INVALID
|
||||
if numeric_operation {
|
||||
if representation, ok := types.distinct_scalar_backing(expected, &checker.module.types);
|
||||
if representation, ok := types.distinct_scalar_backing(materialized, &checker.module.types);
|
||||
ok && !types.is_bool(representation) {
|
||||
nominal = materialized
|
||||
materialized = representation
|
||||
nominal = expected
|
||||
}
|
||||
}
|
||||
recovery_type := types.I64
|
||||
@@ -7538,7 +7688,15 @@ build_constant_expr :: proc(
|
||||
// An integer constant in a float context (e.g. `pi float = 3`) folds to a
|
||||
// float literal, mirroring build_float_expr's bit packing.
|
||||
if constant.kind == .Value && types.is_float(materialized, checker.target) {
|
||||
fval := f64(constant.value) // ponytail: silent precision loss past 2^53, like C int->double
|
||||
if !integer_fits_float(constant.value, materialized, checker.target) {
|
||||
id := source.addf(
|
||||
checker.diagnostics, expr.span,
|
||||
"integer constant %d is not exactly representable in %s",
|
||||
constant.value, type_label(checker, materialized),
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||
}
|
||||
fval := f64(constant.value)
|
||||
bits := transmute(i64)fval
|
||||
if types.bits(materialized, checker.target) == 32 {
|
||||
bits = i64(transmute(u32)f32(fval))
|
||||
@@ -7747,7 +7905,8 @@ sequence_item :: proc(value: types.Type, store: ^types.Store) -> (types.Node, bo
|
||||
}
|
||||
|
||||
find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) {
|
||||
for field, index in types.fields_for(&checker.module.types, struct_type) {
|
||||
record_type := types.runtime_representation(struct_type, &checker.module.types)
|
||||
for field, index in types.fields_for(&checker.module.types, record_type) {
|
||||
if field.name == u32(name) {
|
||||
return index, field, true
|
||||
}
|
||||
@@ -7811,20 +7970,22 @@ find_struct_field_default :: proc(
|
||||
}
|
||||
|
||||
find_tuple_field :: proc(checker: ^Checker, tuple_type: types.Type, index: u64) -> (int, types.Field, bool) {
|
||||
item, ok := types.node(&checker.module.types, tuple_type)
|
||||
record_type := types.runtime_representation(tuple_type, &checker.module.types)
|
||||
item, ok := types.node(&checker.module.types, record_type)
|
||||
if !ok || item.kind != .Struct || !item.tuple || index >= u64(item.field_count) {
|
||||
return 0, {}, false
|
||||
}
|
||||
fields := types.fields_for(&checker.module.types, tuple_type)
|
||||
fields := types.fields_for(&checker.module.types, record_type)
|
||||
return int(index), fields[index], true
|
||||
}
|
||||
|
||||
find_struct_field_slot :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) {
|
||||
item, ok := types.node(&checker.module.types, struct_type)
|
||||
record_type := types.runtime_representation(struct_type, &checker.module.types)
|
||||
item, ok := types.node(&checker.module.types, record_type)
|
||||
if !ok || (item.kind != .Struct && item.kind != .Union) {
|
||||
return -1, {}, false
|
||||
}
|
||||
for field, index in types.fields_for(&checker.module.types, struct_type) {
|
||||
for field, index in types.fields_for(&checker.module.types, record_type) {
|
||||
if field.name == u32(name) {
|
||||
return int(item.field_start)+index, field, true
|
||||
}
|
||||
@@ -8614,11 +8775,9 @@ build_compound_expr :: proc(
|
||||
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
return build_scalar_cast(checker, value, target, expr.span)
|
||||
case .Address:
|
||||
// `&<array literal>` (Zig's `&.{...}`): the operand is an rvalue with no
|
||||
// address, so promote it to an anonymous global constant and take *its*
|
||||
// address. Reuses the existing non-scalar-global storage path; only the
|
||||
// stable global address enters the expression, so it never dangles. The
|
||||
// resulting `*[N]T` then decays to a slice via the usual coercion.
|
||||
// Promote array literals to anonymous globals so their addresses never
|
||||
// dangle. Only a fresh empty literal may acquire mutable slice context;
|
||||
// ordinary literals retain readonly storage and the usual decay rules.
|
||||
if expr.left != ast.INVALID_EXPR && checker.ast_module.exprs[expr.left].kind == .Array {
|
||||
operand := checker.ast_module.exprs[expr.left]
|
||||
// Propagate an element-expected type through `&` so literal elements
|
||||
@@ -8626,12 +8785,15 @@ build_compound_expr :: proc(
|
||||
// Without this, `&["x"]` infers `*[1]*[N:0]u8`, which won't decay to
|
||||
// `[][]u8` because can_decay_array_pointer requires child equality.
|
||||
element := types.INVALID
|
||||
mutable := false
|
||||
if node, ok := types.node(store, expected); ok && (node.kind == .Slice || node.kind == .Array) {
|
||||
element = node.child
|
||||
mutable = len(operand.args) == 0 && node.kind == .Slice &&
|
||||
node.mutable && !node.has_sentinel
|
||||
}
|
||||
synth_expected := types.INVALID
|
||||
if types.is_valid(element) {
|
||||
synth_expected = types.array(store, element, u64(len(operand.args)), false)
|
||||
synth_expected = types.array(store, element, u64(len(operand.args)), mutable)
|
||||
}
|
||||
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, synth_expected, pkg, file)
|
||||
array_type := checker.module.exprs[value].type
|
||||
@@ -8640,7 +8802,7 @@ build_compound_expr :: proc(
|
||||
name = symbol.intern(checker.symbols, "__anon.array"),
|
||||
type = array_type,
|
||||
expr = value,
|
||||
writable = false,
|
||||
writable = mutable,
|
||||
external = false,
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
@@ -8650,7 +8812,7 @@ build_compound_expr :: proc(
|
||||
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Address, span=expr.span, type=types.pointer(store, array_type, false, false),
|
||||
kind=.Address, span=expr.span, type=types.pointer(store, array_type, mutable, false),
|
||||
left=global_ref, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
@@ -8729,6 +8891,22 @@ build_compound_expr :: proc(
|
||||
id := source.add(checker.diagnostics, expr.span, "many-item pointer slicing requires an explicit end bound")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
sentinel_expr := hir.INVALID_EXPR
|
||||
sentinel := item.sentinel
|
||||
if expr.right != ast.INVALID_EXPR {
|
||||
state := ct_state_make(checker, pkg, file)
|
||||
defer ct_state_destroy(&state)
|
||||
constant, sentinel_ok := checked_slice_sentinel(&state, expr.right, item.child)
|
||||
if !sentinel_ok {
|
||||
return invalid_hir_expr(checker, expr.span, state.diagnostic)
|
||||
}
|
||||
sentinel = u64(constant)
|
||||
sentinel_expr = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Integer, span=checker.ast_module.exprs[expr.right].span, type=item.child, integer=i64(sentinel),
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
bounds := make([]hir.Expr_Id, 2, checker.allocator)
|
||||
bounds[0] = hir.INVALID_EXPR
|
||||
bounds[1] = hir.INVALID_EXPR
|
||||
@@ -8747,11 +8925,11 @@ build_compound_expr :: proc(
|
||||
}
|
||||
}
|
||||
}
|
||||
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel)
|
||||
preserve_sentinel := expr.right != ast.INVALID_EXPR || item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, sentinel)
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Slice, span=expr.span, type=result_type, args=bounds, left=container,
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
target=hir.INVALID_REF, right=sentinel_expr, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Field:
|
||||
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, pkg, file, true); enum_ok {
|
||||
@@ -9784,28 +9962,39 @@ build_expr :: proc(
|
||||
continue
|
||||
}
|
||||
if is_ptrcast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "ptrcast! expects 2 arguments, got %d", len(expr.args))
|
||||
if len(expr.args) != 1 && len(expr.args) != 2 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "ptrcast! expects 1 or 2 arguments, got %d", len(expr.args))
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
||||
if !target_ok {
|
||||
id := source.add(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptrcast! target must be a type")
|
||||
target := frame.expected
|
||||
operand_index := 0
|
||||
if len(expr.args) == 2 {
|
||||
target_ok: bool
|
||||
target, target_ok = resolve_type_argument(checker, expr.args[0], pkg, file)
|
||||
if !target_ok {
|
||||
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)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
operand_index = 1
|
||||
} else if !types.is_valid(target) {
|
||||
id := source.add(checker.diagnostics, expr.span, "cannot infer ptrcast! target type; pass a pointer type explicitly")
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if !valid_ptrcast_child(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))
|
||||
if _, target_ok := ptrcast_target_child(checker, target); !target_ok {
|
||||
id := source.addf(checker.diagnostics, expr.span, "ptrcast! target must be a pointer or optional pointer to a sized runtime object type, got %s", type_label(checker, target))
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
stack[frame_index].target_type = target
|
||||
stack[frame_index].stage = 9
|
||||
append(&stack, Build_Expr_Frame{expr=expr.args[1], expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
||||
append(&stack, Build_Expr_Frame{expr=expr.args[operand_index], expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "constcast") {
|
||||
@@ -10218,14 +10407,21 @@ build_expr :: proc(
|
||||
checker.current_comptime_values = comptime_values
|
||||
for source_index in 0..<len(expr.args) {
|
||||
param_index := call_param_index(frame.mapping, source_index)
|
||||
empty_array := is_empty_array_address(checker, checker.ast_module.exprs[expr.args[source_index]])
|
||||
if param_index >= len(function.params) || function.params[param_index].comptime_value ||
|
||||
!is_numeric_constant_expr(checker, expr.args[source_index]) {
|
||||
(!is_numeric_constant_expr(checker, expr.args[source_index]) && !empty_array) {
|
||||
continue
|
||||
}
|
||||
expected_arg := call_arg_expected(checker, function, param_index)
|
||||
if !is_runtime_type(checker, expected_arg) {
|
||||
continue
|
||||
}
|
||||
if empty_array && types.can_decay_array_pointer(
|
||||
checker.module.exprs[stack[frame_index].built_args[source_index]].type,
|
||||
expected_arg, &checker.module.types,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
rebuilt := build_nested_expr(
|
||||
checker, expr.args[source_index], locals, global_reads, calls,
|
||||
expected_arg, pkg, file,
|
||||
@@ -10538,20 +10734,30 @@ build_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
}
|
||||
if frame.stage == 9 {
|
||||
result, ok := types.replace_pointer_child(&checker.module.types, checker.module.exprs[last].type, frame.target_type)
|
||||
if !ok {
|
||||
actual := checker.module.exprs[last].type
|
||||
retyped, ok := ptrcast_retyped_source(checker, actual, frame.target_type)
|
||||
if !types.is_pointer(actual, &checker.module.types) &&
|
||||
!types.is_optional_pointer(actual, &checker.module.types) {
|
||||
id := source.add(checker.diagnostics, expr.span, "ptrcast! operand must be a pointer or optional pointer")
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else if !ok {
|
||||
id := source.addf(
|
||||
checker.diagnostics, expr.span,
|
||||
"ptrcast! cannot convert pointer shape from %s to %s",
|
||||
type_label(checker, actual), type_label(checker, frame.target_type),
|
||||
)
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else {
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
cast_expr := add_hir_expr(checker, hir.Expr{
|
||||
kind=.Pointer_Cast,
|
||||
span=expr.span,
|
||||
type=result,
|
||||
type=retyped,
|
||||
left=last,
|
||||
target=hir.INVALID_REF,
|
||||
right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
last = coerce_expr(checker, cast_expr, frame.target_type, expr.span)
|
||||
}
|
||||
_ = pop(&stack)
|
||||
}
|
||||
|
||||
+173
-21
@@ -761,11 +761,20 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
|
||||
return ct_add_value(state, value), true
|
||||
}
|
||||
if types.is_float(expected, state.checker.target) {
|
||||
if !integer_fits_float(value.integer, expected, state.checker.target) {
|
||||
return INVALID_CT_VALUE, ct_failf(
|
||||
state, .Not_Comptime, span, "integer constant %d is not exactly representable in %s",
|
||||
value.integer, type_label(state.checker, expected),
|
||||
)
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=expected, float=f64(value.integer)}), true
|
||||
}
|
||||
}
|
||||
if value.kind == .Float && types.is_float(expected, state.checker.target) {
|
||||
value.type = expected
|
||||
if types.bits(expected, state.checker.target) == 32 {
|
||||
value.float = f64(f32(value.float))
|
||||
}
|
||||
return ct_add_value(state, value), true
|
||||
}
|
||||
return INVALID_CT_VALUE, ct_failf(
|
||||
@@ -792,6 +801,17 @@ ct_place_get :: proc(state: ^Ct_State, place_id: Ct_Place_Id) -> (Ct_Value_Id, b
|
||||
#partial switch elem.kind {
|
||||
case .Index:
|
||||
index := int(elem.index)
|
||||
if value.kind == .Array {
|
||||
item, ok := types.node(&state.checker.module.types, value.type)
|
||||
if ok && item.has_sentinel && u64(index) == item.count {
|
||||
integer := i128(item.sentinel)
|
||||
if types.is_signed(types.resolve_alias(item.child, &state.checker.module.types), state.checker.target) {
|
||||
integer = i128(i64(item.sentinel))
|
||||
}
|
||||
current = ct_add_value(state, Ct_Value{kind=.Integer, type=item.child, integer=integer})
|
||||
continue
|
||||
}
|
||||
}
|
||||
if value.kind != .Array || index < 0 || index >= len(children) {
|
||||
return INVALID_CT_VALUE, false
|
||||
}
|
||||
@@ -963,7 +983,7 @@ ct_value_can_materialize :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0)
|
||||
store := &state.checker.module.types
|
||||
slice, slice_ok := types.node(store, value.type)
|
||||
place_id := Ct_Place_Id(value.index)
|
||||
if !slice_ok || slice.kind != .Slice || slice.mutable || value.start != 0 ||
|
||||
if !slice_ok || slice.kind != .Slice || slice.mutable ||
|
||||
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
||||
return false
|
||||
}
|
||||
@@ -977,7 +997,7 @@ ct_value_can_materialize :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0)
|
||||
}
|
||||
root := state.values[root_id]
|
||||
array, array_ok := types.node(store, root.type)
|
||||
return array_ok && array.kind == .Array && u64(value.count) == array.count &&
|
||||
return array_ok && array.kind == .Array && u64(value.start)+u64(value.count) <= array.count &&
|
||||
types.equal(slice.child, array.child) && ct_value_can_materialize(state, root_id, depth+1)
|
||||
case .Range, .Array, .Struct, .Optional_Some, .Fallible:
|
||||
for child in ct_child_slice(state, value) {
|
||||
@@ -1065,7 +1085,7 @@ ct_materialize_array_slice :: proc(
|
||||
store := &state.checker.module.types
|
||||
slice, slice_ok := types.node(store, value.type)
|
||||
place_id := Ct_Place_Id(value.index)
|
||||
if !slice_ok || slice.kind != .Slice || slice.mutable || value.start != 0 ||
|
||||
if !slice_ok || slice.kind != .Slice || slice.mutable ||
|
||||
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
@@ -1079,7 +1099,7 @@ ct_materialize_array_slice :: proc(
|
||||
}
|
||||
root := state.values[root_id]
|
||||
array, array_ok := types.node(store, root.type)
|
||||
if !array_ok || array.kind != .Array || u64(value.count) != array.count ||
|
||||
if !array_ok || array.kind != .Array || u64(value.start)+u64(value.count) > array.count ||
|
||||
!types.equal(slice.child, array.child) {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
@@ -1093,7 +1113,28 @@ ct_materialize_array_slice :: proc(
|
||||
if !ok {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
return coerce_expr(state.checker, address, value.type, span), true
|
||||
checker := state.checker
|
||||
bounds := make([]hir.Expr_Id, 2, checker.allocator)
|
||||
endpoints := [2]u32{value.start, value.start+value.count}
|
||||
for integer, index in endpoints {
|
||||
bounds[index] = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Integer, span=span, type=types.USIZE, integer=i64(integer),
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
sentinel_expr := hir.INVALID_EXPR
|
||||
if slice.has_sentinel {
|
||||
sentinel_expr = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Integer, span=span, type=slice.child, integer=i64(slice.sentinel),
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Slice, span=span, type=value.type, args=bounds, left=address, right=sentinel_expr,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}), true
|
||||
}
|
||||
|
||||
ct_materialize_value :: proc(
|
||||
@@ -1223,7 +1264,7 @@ ct_materialize_value :: proc(
|
||||
return result
|
||||
}
|
||||
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
state.diagnostic = source.add(checker.diagnostics, span, "only immutable full-array comptime slices can materialize as runtime memory")
|
||||
state.diagnostic = source.add(checker.diagnostics, span, "only immutable array-backed comptime slices can materialize as runtime memory")
|
||||
}
|
||||
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
||||
case .Function:
|
||||
@@ -1319,6 +1360,12 @@ ct_eval_expr :: proc(
|
||||
if numeric_operation {
|
||||
if representation, distinct_ok := types.distinct_scalar_backing(expected, store); distinct_ok {
|
||||
if types.is_float(representation, checker.target) {
|
||||
if !integer_fits_float(i128(expr.integer), representation, checker.target) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, expr.span, "integer constant %d is not exactly representable in %s",
|
||||
i128(expr.integer), type_label(checker, expected),
|
||||
)
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Float, type=expected, float=f64(expr.integer),
|
||||
}), ct_flow(.Normal), true
|
||||
@@ -1344,13 +1391,21 @@ ct_eval_expr :: proc(
|
||||
if numeric_operation {
|
||||
if representation, distinct_ok := types.distinct_scalar_backing(expected, store);
|
||||
distinct_ok && types.is_float(representation, checker.target) {
|
||||
value := transmute(f64)expr.integer
|
||||
if types.bits(representation, checker.target) == 32 {
|
||||
value = f64(f32(value))
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Float, type=expected, float=transmute(f64)expr.integer,
|
||||
kind=.Float, type=expected, float=value,
|
||||
}), ct_flow(.Normal), true
|
||||
}
|
||||
}
|
||||
value_type := expected if types.is_float(expected, checker.target) else types.F64
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=transmute(f64)expr.integer}), ct_flow(.Normal), true
|
||||
value := transmute(f64)expr.integer
|
||||
if types.bits(value_type, checker.target) == 32 {
|
||||
value = f64(f32(value))
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=value}), ct_flow(.Normal), true
|
||||
case .String:
|
||||
return ct_add_value(state, Ct_Value{kind=.String, type=string_literal_type(checker, expr.integer), index=expr.integer}), ct_flow(.Normal), true
|
||||
case .Name:
|
||||
@@ -1948,6 +2003,22 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
|
||||
return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values)), active=active_field}), ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
checked_slice_sentinel :: proc(state: ^Ct_State, id: ast.Expr_Id, child: types.Type) -> (i128, bool) {
|
||||
checker := state.checker
|
||||
span := checker.ast_module.exprs[id].span
|
||||
previous_error := state.error
|
||||
constant := eval_integer_constant_in_state(state, id, allow_bool=false)
|
||||
state.error = previous_error
|
||||
if constant.kind != .Value {
|
||||
return 0, ct_fail(state, .Not_Comptime, span, "slice sentinel must be a compile-time integer")
|
||||
}
|
||||
representation := types.resolve_alias(child, &checker.module.types)
|
||||
if !types.is_concrete_integer(representation) || !fits_integer_type(constant.value, representation, checker.target) {
|
||||
return 0, ct_failf(state, .Not_Comptime, span, "slice sentinel value does not fit element type %s", type_label(checker, child))
|
||||
}
|
||||
return constant.value, true
|
||||
}
|
||||
|
||||
ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
store := &checker.module.types
|
||||
@@ -1958,6 +2029,7 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
||||
container_place := INVALID_CT_PLACE
|
||||
container_start := 0
|
||||
container_len := 0
|
||||
bounded := true
|
||||
item: types.Node
|
||||
item_ok := false
|
||||
if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) {
|
||||
@@ -1967,6 +2039,25 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
||||
container_start = int(base.start)
|
||||
container_len = int(base.count)
|
||||
item, item_ok = types.node(store, base.type)
|
||||
} else if base.kind == .Array && checker.ast_module.exprs[expr.left].kind == .Array && expr.right != ast.INVALID_EXPR {
|
||||
item, item_ok = types.node(store, base.type)
|
||||
container_place = ct_add_place(state, ct_add_cell(state, base_value, false), base.type, false)
|
||||
container_len = int(base.count)
|
||||
item.mutable = false
|
||||
} else if base.kind == .String && expr.right != ast.INVALID_EXPR && base.index < u64(len(checker.ast_module.strings)) {
|
||||
text := checker.ast_module.strings[base.index]
|
||||
array_type := types.array(store, types.U8, u64(len(text)), false, true, 0)
|
||||
children := make([]Ct_Value_Id, len(text), checker.allocator)
|
||||
defer delete(children, checker.allocator)
|
||||
for byte, index in transmute([]u8)text {
|
||||
children[index] = ct_add_value(state, Ct_Value{kind=.Integer, type=types.U8, integer=i128(byte)})
|
||||
}
|
||||
child_start := u32(len(state.children))
|
||||
append(&state.children, ..children)
|
||||
array_value := ct_add_value(state, Ct_Value{kind=.Array, type=array_type, start=child_start, count=u32(len(text))})
|
||||
container_place = ct_add_place(state, ct_add_cell(state, array_value, false), array_type, false)
|
||||
container_len = len(text)
|
||||
item, item_ok = types.container(base.type, store)
|
||||
} else if base.kind == .Pointer {
|
||||
pointer_item, pointer_ok := types.node(store, base.type)
|
||||
array_item: types.Node
|
||||
@@ -1980,6 +2071,15 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
||||
item = array_item
|
||||
item.mutable = pointer_item.mutable && array_item.mutable
|
||||
item_ok = true
|
||||
} else if pointer_ok && pointer_item.many {
|
||||
if expr.args[1] == ast.INVALID_EXPR {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "many-item pointer slicing requires an explicit end bound")
|
||||
}
|
||||
container_place = Ct_Place_Id(base.index)
|
||||
container_start = int(base.active)
|
||||
item = pointer_item
|
||||
item_ok = true
|
||||
bounded = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2031,11 +2131,31 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
||||
end = int(integer)
|
||||
}
|
||||
}
|
||||
if start < 0 || end < start || end > container_len {
|
||||
if start < 0 || end < start || bounded && end > container_len || container_start < 0 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice bounds out of range")
|
||||
}
|
||||
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel)
|
||||
sentinel := item.sentinel
|
||||
if expr.right != ast.INVALID_EXPR {
|
||||
constant, sentinel_ok := checked_slice_sentinel(state, expr.right, item.child)
|
||||
if !sentinel_ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
if bounded && (end == container_len && !item.has_sentinel) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime sentinel slice endpoint out of range")
|
||||
}
|
||||
endpoint := ct_extend_place(state, container_place, Ct_Path_Elem{kind=.Index, index=u32(container_start+end)}, item.child, false)
|
||||
value, value_ok := ct_place_get(state, endpoint)
|
||||
if !value_ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime sentinel slice endpoint out of range")
|
||||
}
|
||||
actual, actual_ok := ct_integer_value(state, value)
|
||||
if !actual_ok || actual != constant {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice sentinel mismatch")
|
||||
}
|
||||
sentinel = u64(constant)
|
||||
}
|
||||
preserve_sentinel := expr.right != ast.INVALID_EXPR || item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, sentinel)
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Slice, type=result_type, index=u64(container_place),
|
||||
start=u32(container_start+start), count=u32(end-start),
|
||||
@@ -2582,6 +2702,30 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
if distinct_operation && (!compatible || !types.is_float(operation.representation, state.checker.target)) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
||||
}
|
||||
result_type := operation.result
|
||||
if !distinct_operation {
|
||||
result_type = types.widest(left.type, right.type)
|
||||
if left.kind != .Float {
|
||||
result_type = right.type
|
||||
} else if right.kind != .Float {
|
||||
result_type = left.type
|
||||
}
|
||||
if !types.is_float(result_type, state.checker.target) {
|
||||
result_type = types.F64
|
||||
}
|
||||
}
|
||||
representation := types.runtime_representation(result_type, store)
|
||||
operands := [2]Ct_Value_Id{left_id, right_id}
|
||||
for operand_id in operands {
|
||||
operand := &state.values[operand_id]
|
||||
if operand.kind != .Float &&
|
||||
!integer_fits_float(operand.integer, representation, state.checker.target) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, span, "integer constant %d is not exactly representable in %s",
|
||||
operand.integer, type_label(state.checker, result_type),
|
||||
)
|
||||
}
|
||||
}
|
||||
lf := left.float if left.kind == .Float else f64(left.integer)
|
||||
rf := right.float if right.kind == .Float else f64(right.integer)
|
||||
if is_compare {
|
||||
@@ -2605,14 +2749,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
case:
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
||||
}
|
||||
result_type := operation.result
|
||||
if !distinct_operation {
|
||||
result_type = types.widest(left.type, right.type)
|
||||
if !types.is_float(result_type, state.checker.target) {
|
||||
result_type = types.F64
|
||||
}
|
||||
}
|
||||
if types.bits(result_type, state.checker.target) == 32 {
|
||||
if types.bits(representation, state.checker.target) == 32 {
|
||||
result = f64(f32(result))
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
|
||||
@@ -3047,13 +3184,20 @@ ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, sp
|
||||
if value.kind == .Float {
|
||||
if types.is_float(target, state.checker.target) {
|
||||
value.type = target
|
||||
if types.bits(target, state.checker.target) == 32 {
|
||||
value.float = f64(f32(value.float))
|
||||
}
|
||||
return ct_add_value(state, value), ct_flow(.Normal), true
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=target, integer=i128(value.float)}), ct_flow(.Normal), true
|
||||
}
|
||||
if ct_is_integer_like(value) {
|
||||
if types.is_float(target, state.checker.target) {
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=target, float=f64(value.integer)}), ct_flow(.Normal), true
|
||||
result := f64(value.integer)
|
||||
if types.bits(target, state.checker.target) == 32 {
|
||||
result = f64(f32(result))
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=target, float=result}), ct_flow(.Normal), true
|
||||
}
|
||||
value.kind = .Integer
|
||||
value.type = target
|
||||
@@ -5973,7 +6117,7 @@ eval_integer_constant_in_context :: proc(
|
||||
return eval_integer_constant_in_state(&state, expr_id, depth)
|
||||
}
|
||||
|
||||
eval_integer_constant_in_state :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, depth := 0) -> Constant {
|
||||
eval_integer_constant_in_state :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, depth := 0, allow_bool := true) -> Constant {
|
||||
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(state.checker.ast_module.exprs) {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
@@ -5994,6 +6138,9 @@ eval_integer_constant_in_state :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, d
|
||||
}
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
if !allow_bool && state.values[value].kind != .Integer {
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
integer, integer_ok := ct_integer_value(state, value)
|
||||
if !integer_ok {
|
||||
return Constant{kind=.Not_Constant}
|
||||
@@ -6137,6 +6284,11 @@ build_comptime_expr :: proc(
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> hir.Expr_Id {
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
mark_expr_imports_used(checker, expr.left, file)
|
||||
} else {
|
||||
mark_block_imports_used(checker, expr.body, file)
|
||||
}
|
||||
state := ct_state_make(checker, pkg, file)
|
||||
defer ct_state_destroy(&state)
|
||||
value := INVALID_CT_VALUE
|
||||
|
||||
@@ -86,6 +86,9 @@ Opcode :: enum u8 {
|
||||
Store,
|
||||
Fill,
|
||||
Slice,
|
||||
// Private native-entry operations: own descriptors, borrow argument strings.
|
||||
Prepare_Args,
|
||||
Free_Args,
|
||||
Length,
|
||||
Slice_Ptr,
|
||||
Extract,
|
||||
|
||||
@@ -411,6 +411,35 @@ lex :: proc(
|
||||
case:
|
||||
if value >= '0' && value <= '9' {
|
||||
start := cursor
|
||||
base: byte
|
||||
if value == '0' && cursor+1 < len(bytes) {
|
||||
switch bytes[cursor+1] {
|
||||
case 'b', 'B': base = 2
|
||||
case 'o', 'O': base = 8
|
||||
case 'x', 'X': base = 16
|
||||
}
|
||||
}
|
||||
if base != 0 {
|
||||
cursor += 2
|
||||
digits_start := cursor
|
||||
valid := true
|
||||
for cursor < len(bytes) && is_identifier_continue(bytes[cursor]) {
|
||||
digit := bytes[cursor]
|
||||
valid = valid && is_hex_digit(digit) && (base == 16 || digit-'0' < base)
|
||||
cursor += 1
|
||||
}
|
||||
if !valid || cursor == digits_start {
|
||||
id := source.add(
|
||||
diagnostics,
|
||||
source.Span{file=source_file.id, start=source.Offset(start), end=source.Offset(cursor)},
|
||||
"invalid digits in prefixed integer literal",
|
||||
)
|
||||
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
|
||||
} else {
|
||||
append_token(&stream, source_file, .Integer, start, cursor)
|
||||
}
|
||||
continue
|
||||
}
|
||||
for cursor < len(bytes) && bytes[cursor] >= '0' && bytes[cursor] <= '9' {
|
||||
cursor += 1
|
||||
}
|
||||
|
||||
+98
-3
@@ -20,6 +20,7 @@ Emitter :: struct {
|
||||
builder: strings.Builder,
|
||||
messages: [dynamic]Trap_Message,
|
||||
allocator: mem.Allocator,
|
||||
prepares_args: bool,
|
||||
}
|
||||
|
||||
C_Record_ABI_Kind :: enum u8 {
|
||||
@@ -284,7 +285,7 @@ valid_value :: proc(
|
||||
}
|
||||
switch instructions[value_id].op {
|
||||
case .Param, .Const, .Poison, .String, .Aggregate, .Null, .Optional_Some,
|
||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Prepare_Args, .Length, .Slice_Ptr,
|
||||
.Fallible_Error, .Extract, .Select, .Unwrap,
|
||||
.Optional_Is_Some, .Optional_Value, .Orelse,
|
||||
.Widen, .Sum_Widen, .Sum_Project, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Const_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||
@@ -295,7 +296,7 @@ valid_value :: proc(
|
||||
.Shift_Left, .Shift_Right, .Shift_Left_Saturating, .Compare, .Call:
|
||||
return true
|
||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||
.Store, .Fill, .Mem_Copy, .Mem_Set, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
||||
.Store, .Fill, .Free_Args, .Mem_Copy, .Mem_Set, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
@@ -1332,6 +1333,7 @@ emit_instruction_stream :: proc(
|
||||
if types.is_pointer(base_type, &emitter.module.types) {
|
||||
base_type = types.child_type(base_type, &emitter.module.types)
|
||||
}
|
||||
base_type = types.runtime_representation(base_type, &emitter.module.types)
|
||||
if types.kind(base_type, &emitter.module.types) == .Fallible {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, types.fallible_payload_offset(base_type, &emitter.module.types, emitter.module.target))
|
||||
continue
|
||||
@@ -1544,6 +1546,16 @@ emit_instruction_stream :: proc(
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", ptr %%memset_element%d\n", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_next%d = add i64 %%memset_index%d, 1\n store i64 %%memset_next%d, ptr %%memset_index_slot%d\n br label %%memset_loop%d\nmemset_done%d:\n", instruction_index, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
case .Prepare_Args:
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = call %s @bro.prepare_args(i32 ", instruction_index, llvm_type(instruction.type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.a, types.C_INT, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ", ptr ")
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instructions[instruction.b].type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ")\n")
|
||||
case .Free_Args:
|
||||
fmt.sbprintf(&emitter.builder, " %%args_data%d = extractvalue %s ", instruction_index, llvm_type(instructions[instruction.a].type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instructions[instruction.a].type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", 0\n call void @free(ptr %%args_data%d)\n", instruction_index)
|
||||
case .Slice:
|
||||
if !valid_instruction(instructions, instruction.a) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||
@@ -1555,6 +1567,11 @@ emit_instruction_stream :: proc(
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||
continue
|
||||
}
|
||||
check_sentinel := instruction.b != ir.INVALID_INSTRUCTION
|
||||
if check_sentinel && !valid_value(instructions, instruction.b, item.child, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice sentinel")
|
||||
continue
|
||||
}
|
||||
pointer_name := fmt.tprintf("%%v%d", instruction.a)
|
||||
length_name := fmt.tprintf("%d", item.count)
|
||||
if item.kind == .Array {
|
||||
@@ -1596,13 +1613,25 @@ emit_instruction_stream :: proc(
|
||||
if item.kind == .Pointer {
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = or i1 false, %%slice_order%d\n", instruction_index, instruction_index)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp ule i64 %s, %s\n", instruction_index, end_name, length_name)
|
||||
comparison := "ult" if check_sentinel && !item.has_sentinel else "ule"
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp %s i64 %s, %s\n", instruction_index, comparison, end_name, length_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = and i1 %%slice_order%d, %%slice_end_ok%d\n", instruction_index, instruction_index, instruction_index)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%slice_ok%d, label %%slice_continue%d, label %%slice_trap%d\nslice_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice bounds out of range")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\nslice_continue%d:\n", instruction_index)
|
||||
if check_sentinel {
|
||||
element_type := llvm_type(item.child, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_sentinel_ptr%d = getelementptr %s, ptr %s, i64 %s\n", instruction_index, element_type, pointer_name, end_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_sentinel_value%d = load %s, ptr %%slice_sentinel_ptr%d\n", instruction_index, element_type, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_sentinel_ok%d = icmp eq %s %%slice_sentinel_value%d, ", instruction_index, element_type, instruction_index)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, item.child, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, "\n br i1 %%slice_sentinel_ok%d, label %%slice_sentinel_continue%d, label %%slice_sentinel_trap%d\nslice_sentinel_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
sentinel_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice sentinel mismatch")
|
||||
emit_trap_call(emitter, sentinel_message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\nslice_sentinel_continue%d:\n", instruction_index)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_start%d = getelementptr %s, ptr %s, i64 %s\n", instruction_index, llvm_type(item.child, &emitter.module.types), pointer_name, start_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_result%d = insertvalue %s poison, ptr %%slice_start%d, 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_result_len%d = sub i64 %s, %s\n", instruction_index, end_name, start_name)
|
||||
@@ -2728,6 +2757,11 @@ emit_functions :: proc(emitter: ^Emitter) {
|
||||
if function.implementation == .Declaration && function.link_name == "write" {
|
||||
continue
|
||||
}
|
||||
if emitter.prepares_args && function.implementation == .Declaration &&
|
||||
(function.link_name == "malloc" || function.link_name == "free" ||
|
||||
function.link_name == "strlen" || function.link_name == "exit") {
|
||||
continue
|
||||
}
|
||||
if function.implementation == .Declaration {
|
||||
duplicate := false
|
||||
for previous in emitter.module.functions[:function_index] {
|
||||
@@ -2862,6 +2896,59 @@ emit_declarations :: proc(emitter: ^Emitter) {
|
||||
&emitter.builder,
|
||||
"\ndefine internal void @bro.trap(ptr %message, i64 %length) noreturn {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
|
||||
)
|
||||
if emitter.prepares_args {
|
||||
emit_argument_runtime(emitter)
|
||||
}
|
||||
}
|
||||
|
||||
// Only native entry wrappers own these descriptors; argument bytes stay borrowed.
|
||||
emit_argument_runtime :: proc(emitter: ^Emitter) {
|
||||
strings.write_string(&emitter.builder, `declare ptr @malloc(i64)
|
||||
declare void @free(ptr)
|
||||
declare i64 @strlen(ptr)
|
||||
declare void @exit(i32) noreturn
|
||||
|
||||
define internal { ptr, i64 } @bro.prepare_args(i32 %argc, ptr %argv) {
|
||||
entry:
|
||||
%negative = icmp slt i32 %argc, 0
|
||||
br i1 %negative, label %failure, label %check_empty
|
||||
check_empty:
|
||||
%count = zext i32 %argc to i64
|
||||
%empty = icmp eq i64 %count, 0
|
||||
br i1 %empty, label %empty_result, label %check_size
|
||||
check_size:
|
||||
%allocation = call { i64, i1 } @llvm.umul.with.overflow.i64(i64 %count, i64 16)
|
||||
%size = extractvalue { i64, i1 } %allocation, 0
|
||||
%overflow = extractvalue { i64, i1 } %allocation, 1
|
||||
br i1 %overflow, label %failure, label %allocate
|
||||
allocate:
|
||||
%data = call ptr @malloc(i64 %size)
|
||||
%failed = icmp eq ptr %data, null
|
||||
br i1 %failed, label %failure, label %loop
|
||||
loop:
|
||||
%index = phi i64 [ 0, %allocate ], [ %next, %loop ]
|
||||
%native = getelementptr ptr, ptr %argv, i64 %index
|
||||
%string = load ptr, ptr %native
|
||||
%length = call i64 @strlen(ptr %string)
|
||||
%descriptor = getelementptr { ptr, i64 }, ptr %data, i64 %index
|
||||
%with_pointer = insertvalue { ptr, i64 } poison, ptr %string, 0
|
||||
%argument = insertvalue { ptr, i64 } %with_pointer, i64 %length, 1
|
||||
store { ptr, i64 } %argument, ptr %descriptor
|
||||
%next = add i64 %index, 1
|
||||
%more = icmp ult i64 %next, %count
|
||||
br i1 %more, label %loop, label %done
|
||||
done:
|
||||
%result_pointer = insertvalue { ptr, i64 } poison, ptr %data, 0
|
||||
%result = insertvalue { ptr, i64 } %result_pointer, i64 %count, 1
|
||||
ret { ptr, i64 } %result
|
||||
empty_result:
|
||||
ret { ptr, i64 } zeroinitializer
|
||||
failure:
|
||||
call void @exit(i32 2)
|
||||
unreachable
|
||||
}
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
emit :: proc(
|
||||
@@ -2877,6 +2964,14 @@ emit :: proc(
|
||||
builder=strings.builder_make(allocator),
|
||||
allocator=allocator,
|
||||
}
|
||||
for function in module.functions {
|
||||
for instruction in function.instructions {
|
||||
if instruction.op == .Prepare_Args {
|
||||
emitter.prepares_args = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
emitter.messages.allocator = allocator
|
||||
defer {
|
||||
for message in emitter.messages {
|
||||
|
||||
@@ -1731,6 +1731,9 @@ canonical_type :: proc(
|
||||
}
|
||||
if item.kind == .Alias {
|
||||
resolved := canonical_type(module, item.child, mapping, visiting)
|
||||
if types.is_valid(resolved) {
|
||||
module.type_store.nodes[index].child = resolved
|
||||
}
|
||||
mapping[index] = value if !types.is_valid(resolved) else resolved
|
||||
return mapping[index]
|
||||
}
|
||||
|
||||
@@ -306,9 +306,13 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
||||
args[index] = lower_nested_expr(state, arg)
|
||||
}
|
||||
}
|
||||
checked_sentinel := ir.INVALID_INSTRUCTION
|
||||
if expr.right != hir.INVALID_EXPR {
|
||||
checked_sentinel = lower_nested_expr(state, expr.right)
|
||||
}
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Slice, span=expr.span, type=expr.type, args=args,
|
||||
target=ir.INVALID_REF, a=container, b=ir.INVALID_INSTRUCTION,
|
||||
target=ir.INVALID_REF, a=container, b=checked_sentinel,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Unwrap:
|
||||
@@ -1778,8 +1782,48 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
||||
|
||||
instructions: [dynamic]ir.Instruction
|
||||
instructions.allocator = allocator
|
||||
param_types: []types.Type
|
||||
args: []ir.Instruction_Id
|
||||
prepared_args := ir.INVALID_INSTRUCTION
|
||||
if param_index >= 0 {
|
||||
init_type := main_function.locals[param_index].type
|
||||
init_fields := types.fields_for(&module.types, init_type)
|
||||
io_index := 0
|
||||
if !types.equal(
|
||||
types.resolve_alias(init_fields[0].type, &module.types),
|
||||
types.resolve_alias(hir_module.functions[provider_index].result, &module.types),
|
||||
) {
|
||||
io_index = 1
|
||||
}
|
||||
args_index := 1 - io_index
|
||||
args_type := types.resolve_alias(init_fields[args_index].type, &module.types)
|
||||
values_type := types.resolve_alias(types.fields_for(&module.types, args_type)[0].type, &module.types)
|
||||
argument_pointer := types.pointer(&module.types, types.U8, false, true, true, 0)
|
||||
argv_type := types.pointer(&module.types, argument_pointer, false, true)
|
||||
param_types = make([]types.Type, 2, allocator)
|
||||
param_types[0] = types.C_INT
|
||||
param_types[1] = argv_type
|
||||
for param_type, index in param_types {
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Param, type=param_type, target=ir.local_ref(ir.Local_Id(index)),
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
prepared_args = ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Prepare_Args, type=values_type, target=ir.INVALID_REF,
|
||||
a=ir.Instruction_Id(0), b=ir.Instruction_Id(1),
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
values_args := make([]ir.Instruction_Id, 1, allocator)
|
||||
values_args[0] = prepared_args
|
||||
process_args := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Aggregate, type=args_type, args=values_args, target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
provider_call := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Call,
|
||||
@@ -1789,12 +1833,13 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
init_args := make([]ir.Instruction_Id, 1, allocator)
|
||||
init_args[0] = provider_call
|
||||
init_args := make([]ir.Instruction_Id, len(init_fields), allocator)
|
||||
init_args[io_index] = provider_call
|
||||
init_args[args_index] = process_args
|
||||
init_value := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Aggregate,
|
||||
type=main_function.locals[param_index].type,
|
||||
type=init_type,
|
||||
args=init_args,
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
@@ -1814,6 +1859,13 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
if prepared_args != ir.INVALID_INSTRUCTION {
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Free_Args, type=types.VOID, target=ir.INVALID_REF,
|
||||
a=prepared_args, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
if types.kind(main_function.result, &hir_module.types) == .Fallible {
|
||||
success := types.fallible_success(main_function.result, &hir_module.types)
|
||||
@@ -1919,6 +1971,7 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
||||
implementation=.Definition,
|
||||
linkage=.External,
|
||||
is_main=true,
|
||||
param_types=param_types,
|
||||
result=types.I32,
|
||||
instructions=instructions[:],
|
||||
problematic=problematic,
|
||||
|
||||
+45
-10
@@ -239,6 +239,7 @@ parse_type_constant :: proc(parser: ^Parser) -> (u64, bool) {
|
||||
advance(parser)
|
||||
value, ok := parse_integer_magnitude(token_text(parser, tok))
|
||||
if !ok {
|
||||
source.add(parser.diagnostics, tok.span, "integer literal magnitude does not fit in u64")
|
||||
return 0, false
|
||||
}
|
||||
if negative {
|
||||
@@ -429,7 +430,8 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
return types.BOOL
|
||||
case .Left_Paren:
|
||||
advance(parser)
|
||||
grouped := parse_type(parser)
|
||||
skip_newlines(parser)
|
||||
grouped := parse_type_pipe_tail(parser, parse_type_atom(parser), true)
|
||||
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected ')' after grouped type")
|
||||
}
|
||||
@@ -533,10 +535,19 @@ sum_syntax_requires_resolution :: proc(parser: ^Parser, value: types.Type, depth
|
||||
return false
|
||||
}
|
||||
|
||||
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type_Syntax {
|
||||
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax, multiline := false) -> ast.Type_Syntax {
|
||||
result := left
|
||||
for current(parser).kind == .Pipe {
|
||||
for {
|
||||
if multiline {
|
||||
skip_newlines(parser)
|
||||
}
|
||||
if current(parser).kind != .Pipe {
|
||||
break
|
||||
}
|
||||
operator := advance(parser)
|
||||
if multiline {
|
||||
skip_newlines(parser)
|
||||
}
|
||||
right := parse_type_atom(parser)
|
||||
composed, compose_error := types.compose_sum(&parser.module.type_store, result, right)
|
||||
if compose_error == .Unsupported {
|
||||
@@ -896,21 +907,37 @@ parse_anonymous_struct_type_expr :: proc(parser: ^Parser) -> ast.Expr_Id {
|
||||
}
|
||||
|
||||
parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
|
||||
base := u64(10)
|
||||
digits := text
|
||||
if len(text) >= 2 && text[0] == '0' {
|
||||
switch text[1] {
|
||||
case 'b', 'B': base = 2
|
||||
case 'o', 'O': base = 8
|
||||
case 'x', 'X': base = 16
|
||||
}
|
||||
if base != 10 {
|
||||
digits = text[2:]
|
||||
}
|
||||
}
|
||||
value: u64
|
||||
for byte in transmute([]byte)text {
|
||||
if byte < '0' || byte > '9' {
|
||||
for byte in transmute([]byte)digits {
|
||||
if !(byte >= '0' && byte <= '9' || byte >= 'a' && byte <= 'f' || byte >= 'A' && byte <= 'F') {
|
||||
return 0, false
|
||||
}
|
||||
next, overflow := intrinsics.overflow_mul(value, u64(10))
|
||||
digit := u64(decode_hex_digit(byte))
|
||||
if digit >= base {
|
||||
return 0, false
|
||||
}
|
||||
next, overflow := intrinsics.overflow_mul(value, base)
|
||||
if overflow {
|
||||
return 0, false
|
||||
}
|
||||
value, overflow = intrinsics.overflow_add(next, u64(byte-'0'))
|
||||
value, overflow = intrinsics.overflow_add(next, digit)
|
||||
if overflow {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
return value, len(text) > 0
|
||||
return value, len(digits) > 0
|
||||
}
|
||||
|
||||
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
@@ -1440,6 +1467,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
skip_newlines(parser)
|
||||
start_expr := ast.INVALID_EXPR
|
||||
end_expr := ast.INVALID_EXPR
|
||||
sentinel_expr := ast.INVALID_EXPR
|
||||
slicing := false
|
||||
if _, ok := allow(parser, .Range); ok {
|
||||
slicing = true
|
||||
@@ -1453,12 +1481,19 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
}
|
||||
}
|
||||
skip_newlines(parser)
|
||||
if slicing && current(parser).kind != .Right_Bracket {
|
||||
if slicing && current(parser).kind != .Right_Bracket && current(parser).kind != .Semicolon {
|
||||
parser.range_disabled += 1
|
||||
end_expr = parse_expression_bp(parser, 0, nesting+1)
|
||||
parser.range_disabled -= 1
|
||||
skip_newlines(parser)
|
||||
}
|
||||
if slicing {
|
||||
if _, ok := allow(parser, .Semicolon); ok {
|
||||
skip_newlines(parser)
|
||||
sentinel_expr = parse_expression_bp(parser, 0, nesting+1)
|
||||
skip_newlines(parser)
|
||||
}
|
||||
}
|
||||
end_token, ok := allow(parser, .Right_Bracket)
|
||||
if !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected ']' after index or slice")
|
||||
@@ -1475,7 +1510,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
span=span_from(left_expr.span, end_token.span),
|
||||
args=args,
|
||||
left=left,
|
||||
right=ast.INVALID_EXPR,
|
||||
right=sentinel_expr,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -453,7 +453,8 @@ sum_syntax :: proc(store: ^Store, left, right: Type) -> Type {
|
||||
}
|
||||
|
||||
append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Variant) -> bool {
|
||||
item, ok := node(store, value)
|
||||
resolved := resolve_alias(value, store)
|
||||
item, ok := node(store, resolved)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -461,7 +462,7 @@ append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Varia
|
||||
if item.explicit_backing {
|
||||
return false
|
||||
}
|
||||
for member in enum_members_for(store, value) {
|
||||
for member in enum_members_for(store, resolved) {
|
||||
if member.value <= 0 || member.value > 0xffff {
|
||||
return false
|
||||
}
|
||||
@@ -473,7 +474,7 @@ append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Varia
|
||||
return false
|
||||
}
|
||||
tag_members := enum_members_for(store, item.child)
|
||||
for field in fields_for(store, value) {
|
||||
for field in fields_for(store, resolved) {
|
||||
found := false
|
||||
for member in tag_members {
|
||||
if member.name == field.name {
|
||||
|
||||
+1084
-36
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ mem :: import "@std/mem"
|
||||
|
||||
@hide probe_count func(context ?@mut anyopaque) void {
|
||||
if context |raw| {
|
||||
count @mut usize :: ptrcast!(usize, raw)
|
||||
count @mut usize :: ptrcast!(raw)
|
||||
count^ += 1
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ writer func(file File) Writer {
|
||||
.read_write: flags = c.O_RDWR
|
||||
}
|
||||
while true {
|
||||
fd c_int :: c.open(ptrcast!(c_char, path.ptr), flags)
|
||||
fd c_int :: c.open(ptrcast!(path.ptr), flags)
|
||||
if fd >= 0 {
|
||||
return Handle {file_desc = fd}
|
||||
}
|
||||
|
||||
+5
-5
@@ -317,7 +317,7 @@ print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError
|
||||
}
|
||||
}
|
||||
@hide distinct_value func($Backing, $Distinct type, value Distinct) Backing {
|
||||
return ptrcast!(Backing, &value)^
|
||||
return ptrcast!(@Backing, &value)^
|
||||
}
|
||||
|
||||
@hide scalar_or_distinct_type func($T type) bool {
|
||||
@@ -357,14 +357,14 @@ print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError
|
||||
count c_int := 0
|
||||
if sizeof!(T) == 4 {
|
||||
if scientific {
|
||||
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.8e", value)
|
||||
count = c.snprintf(ptrcast!((&buffer).ptr), c_ulong(buffer.len), "%.8e", value)
|
||||
} else {
|
||||
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.9g", value)
|
||||
count = c.snprintf(ptrcast!((&buffer).ptr), c_ulong(buffer.len), "%.9g", value)
|
||||
}
|
||||
} else if scientific {
|
||||
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.16e", value)
|
||||
count = c.snprintf(ptrcast!((&buffer).ptr), c_ulong(buffer.len), "%.16e", value)
|
||||
} else {
|
||||
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.17g", value)
|
||||
count = c.snprintf(ptrcast!((&buffer).ptr), c_ulong(buffer.len), "%.17g", value)
|
||||
}
|
||||
if count < 0 or usize(count) >= buffer.len {
|
||||
return .write_failed
|
||||
|
||||
+7
-8
@@ -48,7 +48,7 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
|
||||
memory ?*mut u8 := raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
if memory |bytes| {
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
pointer *mut T :: ptrcast!(bytes)
|
||||
return pointer[..count]
|
||||
}
|
||||
return .out_of_memory
|
||||
@@ -78,7 +78,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
old_memory ?*mut u8 := null
|
||||
old_size usize := 0
|
||||
if memory.len != 0 {
|
||||
old_memory = ptrcast!(u8, memory.ptr)
|
||||
old_memory = ptrcast!(memory.ptr)
|
||||
old_size = memory.len * element_size
|
||||
}
|
||||
resized ?*mut u8 := raw_realloc(
|
||||
@@ -89,7 +89,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
alignof!(T),
|
||||
)
|
||||
if resized |bytes| {
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
pointer *mut T :: ptrcast!(bytes)
|
||||
return pointer[..new_count]
|
||||
}
|
||||
|
||||
@@ -101,7 +101,6 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
free func($T type, allocator Allocator, memory []T) void {
|
||||
if (memory.len == 0 or sizeof!(T) == 0) return
|
||||
raw_free(allocator, ptrcast!(
|
||||
u8,
|
||||
constcast!(memory).ptr),
|
||||
memory.len * sizeof!(T),
|
||||
alignof!(T),
|
||||
@@ -110,7 +109,7 @@ free func($T type, allocator Allocator, memory []T) void {
|
||||
|
||||
#! get an empty slice of type `T` with `count` elements.
|
||||
empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
pointer *mut T :: ptrcast!((&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
@@ -138,14 +137,14 @@ empty func($T type) []mut T {
|
||||
if (power_of_two(alignment) == false) return null
|
||||
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
return ptrcast!(c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque := [null]
|
||||
status c_int := c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
||||
if (status != 0) return null
|
||||
|
||||
return ptrcast!(u8, memory[0])
|
||||
return ptrcast!(memory[0])
|
||||
}
|
||||
|
||||
@hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
@@ -158,7 +157,7 @@ empty func($T type) []mut T {
|
||||
|
||||
if memory |old_memory| {
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
return ptrcast!(c.realloc(old_memory, c_ulong(new_size)))
|
||||
}
|
||||
|
||||
new_memory ?*mut u8 := c_alloc(null, new_size, alignment)
|
||||
|
||||
@@ -2,4 +2,9 @@ import "@std/io"
|
||||
|
||||
Init :: struct {
|
||||
io io.Io
|
||||
args Args
|
||||
}
|
||||
|
||||
Args :: struct {
|
||||
values [][;0]u8
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ eql func($T type, left, right []T) bool {
|
||||
@hide empty_storage [1]mut u64 := [0]
|
||||
|
||||
@hide empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
pointer *mut T :: ptrcast!((&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
|
||||
memory ?*mut u8 := raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
if memory |bytes| {
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
pointer *mut T :: ptrcast!(bytes)
|
||||
return pointer[..count]
|
||||
}
|
||||
return .out_of_memory
|
||||
@@ -93,7 +93,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
old_memory ?*mut u8 := null
|
||||
old_size usize := 0
|
||||
if memory.len != 0 {
|
||||
old_memory = ptrcast!(u8, memory.ptr)
|
||||
old_memory = ptrcast!(memory.ptr)
|
||||
old_size = memory.len * element_size
|
||||
}
|
||||
resized ?*mut u8 := raw_realloc(
|
||||
@@ -104,7 +104,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
alignof!(T),
|
||||
)
|
||||
if resized |bytes| {
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
pointer *mut T :: ptrcast!(bytes)
|
||||
return pointer[..new_count]
|
||||
}
|
||||
return .out_of_memory
|
||||
@@ -112,7 +112,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
|
||||
free func($T type, allocator Allocator, memory []mut T) void {
|
||||
if memory.len != 0 and sizeof!(T) != 0 {
|
||||
raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
raw_free(allocator, ptrcast!(memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
||||
}
|
||||
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
return ptrcast!(c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque := [null]
|
||||
@@ -150,7 +150,7 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
||||
return null
|
||||
}
|
||||
|
||||
return ptrcast!(u8, memory[0])
|
||||
return ptrcast!(memory[0])
|
||||
}
|
||||
|
||||
@hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
@@ -165,7 +165,7 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
||||
|
||||
if memory |old_memory| {
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
return ptrcast!(c.realloc(old_memory, c_ulong(new_size)))
|
||||
}
|
||||
|
||||
new_memory ?*mut u8 := c_alloc(null, new_size, alignment)
|
||||
|
||||
@@ -30,7 +30,7 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
|
||||
@hide empty_storage [1]mut u64 := [0]
|
||||
|
||||
@hide empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
pointer *mut T :: ptrcast!((&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
|
||||
memory ?*mut u8 := raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
if memory |bytes| {
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
pointer *mut T :: ptrcast!(bytes)
|
||||
return pointer[..count]
|
||||
}
|
||||
return .out_of_memory
|
||||
@@ -57,7 +57,7 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
|
||||
free func($T type, allocator Allocator, memory []mut T) void {
|
||||
if memory.len != 0 and sizeof!(T) != 0 {
|
||||
raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
raw_free(allocator, ptrcast!(memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
||||
}
|
||||
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
return ptrcast!(c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque := [null]
|
||||
@@ -95,7 +95,7 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
||||
return null
|
||||
}
|
||||
|
||||
return ptrcast!(u8, memory[0])
|
||||
return ptrcast!(memory[0])
|
||||
}
|
||||
|
||||
@hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
@@ -110,7 +110,7 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
||||
|
||||
if memory |old_memory| {
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
return ptrcast!(c.realloc(old_memory, c_ulong(new_size)))
|
||||
}
|
||||
|
||||
new_memory ?*mut u8 := c_alloc(null, new_size, alignment)
|
||||
|
||||
Reference in New Issue
Block a user