Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e804f027f3 | |||
| ecb7b2fc64 | |||
| 3a90a90428 | |||
| cbbf5e7d6c | |||
| a6e688db71 | |||
| 81531208b2 |
+16
-3
@@ -31,6 +31,8 @@ roadmap and milestone history.
|
||||
- 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
|
||||
- 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 `?`
|
||||
@@ -48,6 +50,17 @@ roadmap and milestone history.
|
||||
- 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.
|
||||
@@ -257,14 +270,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
|
||||
@@ -286,7 +299,7 @@ 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` 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
|
||||
|
||||
@@ -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,11 +4,15 @@ 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
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -244,6 +248,7 @@ Current prototype features:
|
||||
- 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
|
||||
- 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
|
||||
|
||||
@@ -6,6 +6,7 @@ import "../source"
|
||||
import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "base:intrinsics"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:slice"
|
||||
@@ -1206,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)
|
||||
}
|
||||
@@ -1460,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
|
||||
@@ -3124,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,
|
||||
)
|
||||
@@ -5192,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:
|
||||
@@ -7625,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
|
||||
@@ -7640,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))
|
||||
@@ -8719,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
|
||||
@@ -8731,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
|
||||
@@ -8745,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,
|
||||
})
|
||||
@@ -8755,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,
|
||||
})
|
||||
}
|
||||
@@ -10350,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,
|
||||
|
||||
@@ -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(
|
||||
@@ -1351,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
|
||||
@@ -1376,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:
|
||||
@@ -2679,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 {
|
||||
@@ -2702,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
|
||||
@@ -3144,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
|
||||
@@ -6237,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
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1828,6 +1828,102 @@ immutable_pointer_and_slice_bindings_preserve_mutable_pointees :: proc(t: ^testi
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
fresh_empty_literals_initialize_mutable_slices_and_grow :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-contextual-empty-slices"
|
||||
main_path := "/tmp/brolang-test-contextual-empty-slices/main.bro"
|
||||
output := "/tmp/brolang-test-contextual-empty-slices-output"
|
||||
text := `mem :: import "@std/mem"
|
||||
Item :: struct { value i32 }
|
||||
Views :: struct { integers []mut i32, records []mut Item }
|
||||
integers func(values []mut i32) usize { return values.len }
|
||||
records func(values []mut Item) usize { return values.len }
|
||||
generic func($T type, values []mut T, _ T) usize { return values.len }
|
||||
main func() i32 {
|
||||
numbers []mut i32 := &[]
|
||||
items []mut Item := &[]
|
||||
views Views := {integers = &[], records = &[]}
|
||||
if numbers.len != 0 or items.len != 0 { return 1 }
|
||||
if integers(&[]) != 0 or records(&[]) != 0 { return 2 }
|
||||
if views.integers.len != 0 or views.records.len != 0 { return 3 }
|
||||
integer i32 := 7
|
||||
item Item := {value = 9}
|
||||
if generic(&[], integer) != 0 or generic(&[], item) != 0 { return 7 }
|
||||
numbers = mem.realloc(mem.c_allocator, numbers, 2) catch |_| { return 4 }
|
||||
defer mem.free(mem.c_allocator, numbers)
|
||||
items = mem.realloc(mem.c_allocator, items, 1) catch |_| { return 5 }
|
||||
defer mem.free(mem.c_allocator, items)
|
||||
numbers[0] = 17
|
||||
numbers[1] = 25
|
||||
items[0] = {value = numbers[0] + numbers[1]}
|
||||
if numbers.len != 2 or items.len != 1 or items[0].value != 42 { return 6 }
|
||||
return 0
|
||||
}
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
testing.expect_value(t, compiler_core.compile_package(
|
||||
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||
), 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
empty_literal_mutability_does_not_weaken_readonly_or_sentinel_rules :: proc(t: ^testing.T) {
|
||||
cases := [?]string {
|
||||
`main func() void {
|
||||
values [0]i32 := []
|
||||
view []mut i32 := &values
|
||||
_ = view
|
||||
}
|
||||
`,
|
||||
`main func() void {
|
||||
view []mut i32 := &[1]
|
||||
_ = view
|
||||
}
|
||||
`,
|
||||
`main func() void {
|
||||
readonly []i32 := &[]
|
||||
view []mut i32 := readonly
|
||||
_ = view
|
||||
}
|
||||
`,
|
||||
`main func() void {
|
||||
view [;0]i32 := &[]
|
||||
_ = view
|
||||
}
|
||||
`,
|
||||
`main func() void {
|
||||
view [;0]mut i32 := &[]
|
||||
_ = view
|
||||
}
|
||||
`,
|
||||
}
|
||||
for text in cases {
|
||||
source_file := source.Source{path="invalid_empty_slice.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
found_conversion := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_conversion = found_conversion ||
|
||||
strings.contains(diagnostic.message, "cannot implicitly convert")
|
||||
}
|
||||
testing.expect(t, found_conversion, text)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
many_item_pointer_slices_compile_and_run :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-pointer-slices"
|
||||
@@ -10998,6 +11094,36 @@ unused_import_is_a_warning :: proc(t: ^testing.T) {
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
imports_used_only_by_comptime_global_initializers_are_not_warned :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-comptime-global-import"
|
||||
output := "/tmp/brolang-test-comptime-global-import-output"
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
packages := [3]string{"app", "table", "helper"}
|
||||
for name in packages {
|
||||
testing.expect(t, os2.make_directory_all(fmt.tprintf("%s/%s", directory, name)) == nil)
|
||||
}
|
||||
helper_text := `make func() [1]i32 { return [42] }
|
||||
`
|
||||
table_sources := [2]string{
|
||||
"helper :: import \"../helper\"\nvalues :: $helper.make()\n",
|
||||
"helper :: import \"../helper\"\nvalues :: ${ yield helper.make() }\n",
|
||||
}
|
||||
app_text := `table :: import "../table"
|
||||
main func() i32 { return table.values[0] }
|
||||
`
|
||||
testing.expect(t, os.write_entire_file(fmt.tprintf("%s/helper/main.bro", directory), transmute([]byte)helper_text))
|
||||
testing.expect(t, os.write_entire_file(fmt.tprintf("%s/app/main.bro", directory), transmute([]byte)app_text))
|
||||
for table_text in table_sources {
|
||||
testing.expect(t, os.write_entire_file(fmt.tprintf("%s/table/main.bro", directory), transmute([]byte)table_text))
|
||||
testing.expect_value(t, compiler_core.compile_package(fmt.tprintf("%s/app", directory), output), 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 42)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
unused_missing_package_does_not_trap :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-package-missing-unused"
|
||||
@@ -15686,6 +15812,143 @@ main func() void {
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
contextual_integer_float_precision_loss_is_diagnosed :: proc(t: ^testing.T) {
|
||||
Boundary :: struct {scalar, integer: string}
|
||||
boundaries := []Boundary{
|
||||
{"f32", "16777217"},
|
||||
{"f32", "-16777217"},
|
||||
{"f64", "9007199254740993"},
|
||||
{"f64", "-9007199254740993"},
|
||||
}
|
||||
Context :: struct {declarations, body: string}
|
||||
contexts := []Context{
|
||||
{"bad Scalar :: VALUE", "_ = bad"},
|
||||
{"bad Scalar := VALUE", "_ = bad"},
|
||||
{"bad Scalar :: $VALUE", "_ = bad"},
|
||||
{"bad Scalar :: ${ yield VALUE }", "_ = bad"},
|
||||
{"", "value Scalar := VALUE\n_ = value"},
|
||||
{"", "local :: VALUE\nvalue Scalar := local\n_ = value"},
|
||||
{"", "local :: VALUE + 2 - 2\nvalue Scalar := local\n_ = value"},
|
||||
{"folded :: VALUE + 2 - 2", "value Scalar := folded\n_ = value"},
|
||||
{"", "value Scalar := G\n_ = value"},
|
||||
{"", "value Scalar := VALUE + 2 - 2\n_ = value"},
|
||||
{"", "_ = take(VALUE)"},
|
||||
{"", "return VALUE"},
|
||||
{"", "values [1]Scalar := [VALUE]\n_ = values"},
|
||||
{"", "value ?Scalar := VALUE\n_ = value"},
|
||||
{"", "value ?Scalar := some!(VALUE)\n_ = value"},
|
||||
{"", "seed Nominal := Nominal(0.0)\n_ = seed + VALUE"},
|
||||
{"", "seed Scalar := 0.0\n_ = seed + VALUE"},
|
||||
{"", "local :: VALUE\nseed Scalar := 0.0\n_ = local + seed"},
|
||||
{"", "seed Scalar := 0.0\n_ = seed == VALUE"},
|
||||
}
|
||||
for boundary in boundaries {
|
||||
for test_context in contexts {
|
||||
body, body_allocated := strings.replace_all(test_context.body, "VALUE", boundary.integer)
|
||||
declarations, declarations_allocated := strings.replace_all(test_context.declarations, "VALUE", boundary.integer)
|
||||
evaluation_modes := [2]bool{false, true}
|
||||
for comptime in evaluation_modes {
|
||||
invocation := "$exercise()" if comptime else "exercise()"
|
||||
text := fmt.aprintf(
|
||||
"Scalar :: alias %s\nNominal :: distinct %s\nG :: %s\n%s\n"+
|
||||
"take func(value Scalar) Scalar {{ return value }}\n"+
|
||||
"exercise func() Scalar {{\n%s\nreturn 0\n}}\n"+
|
||||
"main func() void {{ _ = %s }}\n",
|
||||
boundary.scalar, boundary.scalar, boundary.integer, declarations, body, invocation,
|
||||
)
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
symbols := symbol.init_table()
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, "not exactly representable")
|
||||
}
|
||||
testing.expect(t, found, text)
|
||||
hir.destroy_module(&hir_module)
|
||||
ast.destroy_module(&ast_module)
|
||||
delete(stream.items)
|
||||
symbol.destroy_table(&symbols)
|
||||
source.destroy_diagnostics(&diagnostics)
|
||||
delete(text)
|
||||
}
|
||||
if body_allocated { delete(body) }
|
||||
if declarations_allocated { delete(declarations) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
exact_contextual_integer_floats_and_explicit_rounding_compile_and_run :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-exact-integer-floats"
|
||||
main_path := "/tmp/brolang-test-exact-integer-floats/main.bro"
|
||||
output := "/tmp/brolang-test-exact-integer-floats-output"
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
testing.expect(t, os2.make_directory_all(directory) == nil)
|
||||
text := `Nominal :: distinct f32
|
||||
G :: 16777216
|
||||
H :: 9007199254740992
|
||||
take32 func(value f32) f32 { return value }
|
||||
take64 func(value f64) f64 { return value }
|
||||
give32 func() f32 { return -16777216 }
|
||||
give64 func() f64 { return -9007199254740992 }
|
||||
exercise func() i32 {
|
||||
local32 :: 16777216
|
||||
local64 :: 9007199254740992
|
||||
a f32 := local32
|
||||
b f64 := local64
|
||||
c f32 := G
|
||||
d f64 := H
|
||||
folded32 f32 := 16777215 + 1
|
||||
folded64 f64 := 9007199254740991 + 1
|
||||
power32 f32 := 9223372036854775808
|
||||
power64 f64 := -9223372036854775808
|
||||
next32 f32 := 16777218
|
||||
next64 f64 := 9007199254740994
|
||||
values32 [2]f32 := [16777216, -16777216]
|
||||
values64 [2]f64 := [9007199254740992, -9007199254740992]
|
||||
optional32 ?f32 := 16777216
|
||||
optional64 ?f64 := some!(-9007199254740992)
|
||||
nominal := Nominal(0.0) + 16777216
|
||||
literal32 f32 := 16777217.0
|
||||
literal64 f64 := 9007199254740993.0
|
||||
cast32 f32 := f32(16777217)
|
||||
cast64 f64 := f64(9007199254740993)
|
||||
negative_literal64 f64 := -9007199254740993.0
|
||||
negative_cast32 f32 := f32(-16777217)
|
||||
negative_cast64 f64 := f64(-9007199254740993)
|
||||
if a != 16777216.0 or b != 9007199254740992.0 { return 1 }
|
||||
if c != a or d != b or folded32 != a or folded64 != b { return 2 }
|
||||
if take32(16777216) != a or take64(9007199254740992) != b { return 3 }
|
||||
if give32() != -a or give64() != -b { return 4 }
|
||||
if values32[1] != -a or values64[1] != -b { return 5 }
|
||||
if (optional32 orelse 0.0) != a or (optional64 orelse 0.0) != -b { return 6 }
|
||||
if f32(nominal) != a { return 7 }
|
||||
if literal32 != a or literal64 != b { return 8 }
|
||||
if cast32 != a or cast64 != b { return 9 }
|
||||
if power32 != 9223372036854775808.0 or power64 != -9223372036854775808.0 { return 10 }
|
||||
if next32 != 16777218.0 or next64 != 9007199254740994.0 { return 11 }
|
||||
if negative_literal64 != -b { return 12 }
|
||||
if negative_cast32 != -a or negative_cast64 != -b { return 13 }
|
||||
return 0
|
||||
}
|
||||
main func() i32 {
|
||||
checked i32 :: $exercise()
|
||||
if checked != 0 { return checked }
|
||||
return exercise()
|
||||
}
|
||||
`
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
missing_qualified_signature_symbol_reports_one_root_error :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-root-diagnostic"
|
||||
@@ -16536,6 +16799,13 @@ TokenKind :: enum(u8) {
|
||||
}
|
||||
Simple :: enum { first, second }
|
||||
|
||||
LeftError :: enum { shared, left }
|
||||
RightError :: enum { shared, right }
|
||||
TailError :: enum { tail }
|
||||
BothErrors :: alias LeftError | RightError
|
||||
NestedErrors :: alias (BothErrors | LeftError) | TailError
|
||||
ErrorAlias :: alias NestedErrors
|
||||
|
||||
Names :: alias meta.EnumFieldStruct(TokenKind, ?[]u8, some!(null))
|
||||
Flags :: alias meta.EnumFieldStruct(Simple, bool, false)
|
||||
Direct :: alias struct_type!(.auto, {"x", "name"}, {i32, []u8}, {null, "bro"})
|
||||
@@ -16601,7 +16871,53 @@ get func($E, $V type, map @Map(E, V), key E) ?V {
|
||||
}
|
||||
}
|
||||
|
||||
composed_map :: $(init(ErrorAlias, i32, {
|
||||
shared = 10,
|
||||
left = 20,
|
||||
right = 30,
|
||||
tail = 40,
|
||||
}))
|
||||
|
||||
reflects_composed_keys func($E type) bool {
|
||||
keys [4]E := [.shared, .left, .right, .tail]
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: {
|
||||
if info.fields.len != 4 { return false }
|
||||
inline for info.fields |field, index| {
|
||||
if field.type != E or field.index != index { return false }
|
||||
if field!(E, field.name) != keys[index] { return false }
|
||||
}
|
||||
return (
|
||||
field!(E, "shared") == field!(NestedErrors, "shared")
|
||||
and field!(E, "left") != field!(E, "right")
|
||||
)
|
||||
}
|
||||
else: return false
|
||||
}
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
if !$(reflects_composed_keys(ErrorAlias)) { return 27 }
|
||||
keys [4]ErrorAlias := [.shared, .left, .right, .tail]
|
||||
expected [4]i32 := [10, 20, 30, 40]
|
||||
for keys |key, index| {
|
||||
if get(&composed_map, key) |value| {
|
||||
if value != expected[index] { return 28 }
|
||||
} else { return 29 }
|
||||
}
|
||||
left LeftError := .left
|
||||
right RightError := .right
|
||||
if get(&composed_map, left) |value| {
|
||||
if value != 20 { return 30 }
|
||||
} else { return 30 }
|
||||
if get(&composed_map, right) |value| {
|
||||
if value != 30 { return 31 }
|
||||
} else { return 32 }
|
||||
partial Map(ErrorAlias, i32) := init({right = 3})
|
||||
if get(&partial, .shared) |_| { return 33 }
|
||||
if get(&partial, .right) |value| {
|
||||
if value != 3 { return 34 }
|
||||
} else { return 35 }
|
||||
other Map(OtherTokenKind, []u8) := init(OtherTokenKind, []u8, {alpha = "other"})
|
||||
if !other.present[0] { return 26 }
|
||||
_ = ordered
|
||||
@@ -16706,6 +17022,54 @@ main func() i32 {
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
enum_field_struct_rejects_payload_sums_and_nominal_unions :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-enum-field-struct-rejections"
|
||||
main_path := "/tmp/brolang-test-enum-field-struct-rejections/main.bro"
|
||||
output := "/tmp/brolang-test-enum-field-struct-rejections-output"
|
||||
defer _ = os.remove(output)
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
declarations := [?]string{
|
||||
`Payload :: union(enum) { value i32 }
|
||||
Empty :: enum { empty }
|
||||
Keys :: alias Payload | Empty`,
|
||||
`Keys :: union(enum) { first void, second void }`,
|
||||
`Keys :: union { first i32, second i32 }`,
|
||||
}
|
||||
for declaration in declarations {
|
||||
text := fmt.aprint("meta :: import \"@std/meta\"\n", declaration, `
|
||||
Flags :: alias meta.EnumFieldStruct(Keys, bool, false)
|
||||
main func() void {
|
||||
flags Flags := {}
|
||||
_ = flags
|
||||
}
|
||||
`)
|
||||
defer delete(text)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
module, loaded := loader.load(
|
||||
directory, &sources, &diagnostics, &symbols, project_root_path=".",
|
||||
)
|
||||
defer ast.destroy_module(&module)
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
testing.expect(t, loaded)
|
||||
testing.expect(t, len(diagnostics.items) > 0)
|
||||
testing.expect_value(t, compiler_core.compile_package(
|
||||
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||
), 1)
|
||||
testing.expect(t, !os.exists(output))
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
std_meta_tests_compile_and_run :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-std-meta"
|
||||
|
||||
Reference in New Issue
Block a user