usize indexing only
This commit is contained in:
+4
-3
@@ -63,8 +63,9 @@ nominal result type: checked integer `+`, `-`, `*`, unary `-`, bitwise operators
|
||||
comparisons, and compound assignments; float arithmetic, unary `-`, comparisons, and compound
|
||||
assignments; and boolean equality/inequality. Integer literals and float literals are contextual.
|
||||
Typed backing values and separate distinct identities remain incompatible in ordinary operations;
|
||||
an explicit constructor is required to cross that boundary. Distinct integers also work as indices and slice bounds;
|
||||
`minval!` / `maxval!` return the distinct type. Runtime and comptime behavior match.
|
||||
an explicit constructor is required to cross that boundary. Distinct integers require an explicit
|
||||
`usize` cast for indices and slice bounds; `minval!` / `maxval!` return the distinct type.
|
||||
Runtime and comptime behavior match.
|
||||
|
||||
`typeinfo!(Distinct).backing` reports the immediate declared backing. Standard formatting peels
|
||||
distinct layers recursively, so all scalar format verbs behave like the final scalar backing.
|
||||
@@ -117,7 +118,7 @@ fields. `_` is not a keyword member name.
|
||||
- checked integer `+ - *`, unary `-`, float-only `/`, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
|
||||
- Zig-style integer bitwise complement `~`, binary `&`, `|`, `xor`, shifts `<<` / `>>`, and saturating left shift `<<|`; postfix `^` remains pointer dereference
|
||||
- assignments and compound assignments `+= -= *= /= &= |= xor= <<= >>= <<|=` with single evaluation of complex lvalues; `/=` is float-only and `xor=` is contiguous
|
||||
- field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
|
||||
- field access through struct values and pointers, exact `usize` indices and slice bounds, and contextual integer constants in those positions
|
||||
- boolean `if` / `else if` / `else` and `for` loops with braceless single-statement bodies when the preceding expression is parenthesized or a function call
|
||||
- `while` loops with conditional unwrap captures and guards plus optional post-iteration update clauses
|
||||
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures; `inline for` specializes a comptime aggregate into one checked body per element
|
||||
|
||||
@@ -234,7 +234,7 @@ Current prototype features:
|
||||
- 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
|
||||
- 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/indexing, reflection, and recursive standard formatting
|
||||
- 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
|
||||
- Relative C header imports as synthetic package namespaces
|
||||
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
|
||||
|
||||
@@ -5108,7 +5108,8 @@ infer_compound_expr :: proc(
|
||||
return types.child_type(value, store) if types.is_pointer(value, store) else types.INVALID
|
||||
case .Index:
|
||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, types.USIZE)
|
||||
_ = record_demand(checker, expr.right, types.USIZE, locals, local_types, pkg, file)
|
||||
item, ok := types.container(value, store)
|
||||
return item.child if ok else types.INVALID
|
||||
case .Slice:
|
||||
@@ -5124,7 +5125,8 @@ infer_compound_expr :: proc(
|
||||
}
|
||||
for bound in expr.args {
|
||||
if bound != ast.INVALID_EXPR {
|
||||
_ = infer_nested_expr(checker, bound, locals, pkg, file, demanded, local_types)
|
||||
_ = infer_nested_expr(checker, bound, locals, pkg, file, demanded, local_types, types.USIZE)
|
||||
_ = record_demand(checker, bound, types.USIZE, locals, local_types, pkg, file)
|
||||
}
|
||||
}
|
||||
preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||
@@ -8100,36 +8102,35 @@ build_scalar_cast :: proc(
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
normalize_index_expr :: proc(checker: ^Checker, value: hir.Expr_Id, span: source.Span) -> hir.Expr_Id {
|
||||
store := &checker.module.types
|
||||
current := value
|
||||
current_type := checker.module.exprs[current].type
|
||||
representation, distinct_ok := types.distinct_scalar_backing(current_type, store)
|
||||
if distinct_ok && types.is_concrete_integer(representation) {
|
||||
for {
|
||||
backing, ok := types.distinct_backing(current_type, store)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
current = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Retype,
|
||||
span=span,
|
||||
type=backing,
|
||||
left=current,
|
||||
target=hir.INVALID_REF,
|
||||
right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
current_type = backing
|
||||
}
|
||||
normalize_index_expr :: proc(
|
||||
checker: ^Checker,
|
||||
value: hir.Expr_Id,
|
||||
span: source.Span,
|
||||
label: string,
|
||||
) -> hir.Expr_Id {
|
||||
if value == hir.INVALID_EXPR {
|
||||
return value
|
||||
}
|
||||
current_expr := &checker.module.exprs[current]
|
||||
if _, invalid := invalid_expr_diagnostic(checker, value); invalid {
|
||||
return value
|
||||
}
|
||||
current_expr := &checker.module.exprs[value]
|
||||
if current_expr.kind == .Integer &&
|
||||
fits_integer_type(i128(current_expr.integer), types.USIZE, checker.target) {
|
||||
current_expr.type = types.USIZE
|
||||
return current
|
||||
return value
|
||||
}
|
||||
return coerce_expr(checker, current, types.USIZE, span)
|
||||
if !types.equal(current_expr.type, types.USIZE) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
span,
|
||||
"%s must have type usize, got %s",
|
||||
label,
|
||||
type_label(checker, current_expr.type),
|
||||
)
|
||||
return invalid_hir_expr(checker, span, id, types.USIZE)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@@ -8690,7 +8691,7 @@ build_compound_expr :: proc(
|
||||
case .Index:
|
||||
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
index = normalize_index_expr(checker, index, expr.span)
|
||||
index = normalize_index_expr(checker, index, expr.span, "index")
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container, index); propagated {
|
||||
return invalid
|
||||
}
|
||||
@@ -8734,7 +8735,12 @@ build_compound_expr :: proc(
|
||||
for bound, index in expr.args {
|
||||
if bound != ast.INVALID_EXPR {
|
||||
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
bounds[index] = normalize_index_expr(checker, bounds[index], checker.ast_module.exprs[bound].span)
|
||||
bounds[index] = normalize_index_expr(
|
||||
checker,
|
||||
bounds[index],
|
||||
checker.ast_module.exprs[bound].span,
|
||||
"slice bound",
|
||||
)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, bounds[index]); propagated {
|
||||
delete(bounds, checker.allocator)
|
||||
return invalid
|
||||
|
||||
@@ -291,6 +291,7 @@ Ct_Binding :: struct {
|
||||
value: Ct_Value_Id,
|
||||
cell: Ct_Cell_Id,
|
||||
mutable: bool,
|
||||
open_integer: bool,
|
||||
}
|
||||
|
||||
Ct_Error_Refinement :: struct {
|
||||
@@ -473,9 +474,23 @@ ct_extend_place :: proc(
|
||||
return ct_add_place(state, base.cell, value_type, writable, extended[:])
|
||||
}
|
||||
|
||||
ct_bind_value :: proc(state: ^Ct_State, name: symbol.Id, value_type: types.Type, value: Ct_Value_Id, mutable: bool) {
|
||||
ct_bind_value :: proc(
|
||||
state: ^Ct_State,
|
||||
name: symbol.Id,
|
||||
value_type: types.Type,
|
||||
value: Ct_Value_Id,
|
||||
mutable: bool,
|
||||
open_integer := false,
|
||||
) {
|
||||
cell := ct_add_cell(state, value, mutable)
|
||||
append(&state.bindings, Ct_Binding{name=name, type=value_type, value=value, cell=cell, mutable=mutable})
|
||||
append(&state.bindings, Ct_Binding{
|
||||
name=name,
|
||||
type=value_type,
|
||||
value=value,
|
||||
cell=cell,
|
||||
mutable=mutable,
|
||||
open_integer=open_integer,
|
||||
})
|
||||
}
|
||||
|
||||
ct_pop_bindings :: proc(state: ^Ct_State, start: int) {
|
||||
@@ -581,6 +596,32 @@ ct_binding_value :: proc(state: ^Ct_State, index: int) -> Ct_Value_Id {
|
||||
return binding.value
|
||||
}
|
||||
|
||||
ct_contextualize_open_integer_binding :: proc(state: ^Ct_State, index: int, expected: types.Type) {
|
||||
if expected != types.USIZE || index < 0 || index >= len(state.bindings) ||
|
||||
!state.bindings[index].open_integer {
|
||||
return
|
||||
}
|
||||
value_id := ct_binding_value(state, index)
|
||||
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||
return
|
||||
}
|
||||
value := state.values[value_id]
|
||||
if value.kind != .Integer ||
|
||||
!fits_integer_type(value.integer, expected, state.checker.target) {
|
||||
return
|
||||
}
|
||||
value.type = expected
|
||||
contextual := ct_add_value(state, value)
|
||||
binding := &state.bindings[index]
|
||||
binding.type = expected
|
||||
binding.value = contextual
|
||||
binding.open_integer = false
|
||||
if binding.cell != INVALID_CT_CELL && int(binding.cell) < len(state.cells) &&
|
||||
state.cells[binding.cell].live {
|
||||
state.cells[binding.cell].value = contextual
|
||||
}
|
||||
}
|
||||
|
||||
ct_binding_place :: proc(state: ^Ct_State, index: int) -> Ct_Place_Id {
|
||||
if index < 0 || index >= len(state.bindings) {
|
||||
return INVALID_CT_PLACE
|
||||
@@ -1315,6 +1356,7 @@ ct_eval_expr :: proc(
|
||||
case .Name:
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
if index, ok := ct_find_binding_index(state, expr.name); ok {
|
||||
ct_contextualize_open_integer_binding(state, index, expected)
|
||||
return ct_observe_value(state, ct_binding_value(state, index), expr.span)
|
||||
}
|
||||
if value, ok := current_comptime_value(checker, expr.name); ok {
|
||||
@@ -1452,16 +1494,19 @@ ct_eval_expr :: proc(
|
||||
}
|
||||
return ct_eval_field_value(state, base_id, expr.name, expr.span)
|
||||
case .Index:
|
||||
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.INVALID, depth+1)
|
||||
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1)
|
||||
if !index_ok || index_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, index_flow, index_ok
|
||||
}
|
||||
index_type := state.values[index_id].type
|
||||
index_representation := types.runtime_representation(index_type, store)
|
||||
index_literal := is_numeric_constant_expr(checker, expr.right)
|
||||
if !types.is_concrete_integer(index_representation) ||
|
||||
!index_literal && !can_implicitly_convert_type(checker, index_representation, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be coercible to usize")
|
||||
if !types.equal(index_type, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
.Not_Comptime,
|
||||
checker.ast_module.exprs[expr.right].span,
|
||||
"index must have type usize, got %s",
|
||||
type_label(checker, index_type),
|
||||
)
|
||||
}
|
||||
index_value, index_is_int := ct_integer_value(state, index_id)
|
||||
if !index_is_int || index_value < 0 || index_value > i128(0x7fff_ffff) {
|
||||
@@ -1962,16 +2007,19 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
||||
if bound == ast.INVALID_EXPR {
|
||||
continue
|
||||
}
|
||||
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.INVALID, depth+1)
|
||||
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.USIZE, depth+1)
|
||||
if !bound_ok || bound_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, bound_flow, bound_ok
|
||||
}
|
||||
bound_type := state.values[value].type
|
||||
bound_representation := types.runtime_representation(bound_type, store)
|
||||
bound_literal := is_numeric_constant_expr(checker, bound)
|
||||
if !types.is_concrete_integer(bound_representation) ||
|
||||
!bound_literal && !can_implicitly_convert_type(checker, bound_representation, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be coercible to usize")
|
||||
if !types.equal(bound_type, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
.Not_Comptime,
|
||||
checker.ast_module.exprs[bound].span,
|
||||
"slice bound must have type usize, got %s",
|
||||
type_label(checker, bound_type),
|
||||
)
|
||||
}
|
||||
integer, integer_ok := ct_integer_value(state, value)
|
||||
if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) {
|
||||
@@ -2352,6 +2400,16 @@ ct_eval_place :: proc(
|
||||
if !index_ok || index_flow.kind != .Normal {
|
||||
return INVALID_CT_PLACE, types.INVALID, false, index_flow, index_ok
|
||||
}
|
||||
index_type := state.values[index_value].type
|
||||
if !types.equal(index_type, types.USIZE) {
|
||||
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
.Not_Comptime,
|
||||
checker.ast_module.exprs[expr.right].span,
|
||||
"index must have type usize, got %s",
|
||||
type_label(checker, index_type),
|
||||
)
|
||||
}
|
||||
index_int, int_ok := ct_integer_value(state, index_value)
|
||||
if !int_ok || index_int < 0 || index_int > i128(0x7fff_ffff) {
|
||||
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer")
|
||||
@@ -4983,6 +5041,11 @@ ct_exec_statements :: proc(
|
||||
} else {
|
||||
declared := type_from_syntax(checker, statement.type, state.pkg, state.file, active_state=state)
|
||||
expected := declared if types.is_valid(declared) && !types.is_void(declared) else types.INVALID
|
||||
open_integer := false
|
||||
if !is_runtime_type(checker, declared) {
|
||||
constant := eval_integer_constant_in_context(checker, statement.expr, state.pkg, state.file)
|
||||
open_integer = constant.kind == .Value
|
||||
}
|
||||
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, expected, depth+1)
|
||||
ok = expr_ok
|
||||
flow = expr_flow
|
||||
@@ -4991,7 +5054,14 @@ ct_exec_statements :: proc(
|
||||
value, ok = ct_coerce_value(state, value, expected, statement.span)
|
||||
}
|
||||
if ok && statement.name != checker.sink_symbol {
|
||||
ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable)
|
||||
ct_bind_value(
|
||||
state,
|
||||
statement.name,
|
||||
state.values[value].type,
|
||||
value,
|
||||
!statement.immutable,
|
||||
open_integer=open_integer,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+87
-1
@@ -1867,6 +1867,92 @@ many_item_pointer_slices_require_end_bound :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, end_bound_errors, 2)
|
||||
}
|
||||
|
||||
@(test)
|
||||
indices_and_slice_bounds_require_usize :: proc(t: ^testing.T) {
|
||||
text := `Index :: distinct u32
|
||||
main func() void {
|
||||
values [2]i32 := [1, 2]
|
||||
index := 1
|
||||
start := 0
|
||||
end := 1
|
||||
small u32 := 0
|
||||
id Index := Index(0)
|
||||
_ = values[index]
|
||||
_ = values[start..end]
|
||||
_ = values[small]
|
||||
_ = values[id]
|
||||
_ = values[small..]
|
||||
_ = values[..id]
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
index_errors, bound_errors := 0, 0
|
||||
for diagnostic in diagnostics.items {
|
||||
index_errors += 1 if strings.contains(diagnostic.message, "index must have type usize") else 0
|
||||
bound_errors += 1 if strings.contains(diagnostic.message, "slice bound must have type usize") else 0
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 4)
|
||||
testing.expect_value(t, index_errors, 2)
|
||||
testing.expect_value(t, bound_errors, 2)
|
||||
}
|
||||
|
||||
@(test)
|
||||
comptime_indices_infer_usize_and_reject_explicit_integer_types :: proc(t: ^testing.T) {
|
||||
text := `good func($items [2]u8) u8 {
|
||||
start := 0
|
||||
end := 1
|
||||
part :: items[start..end]
|
||||
return part[start]
|
||||
}
|
||||
bad_index func($items [2]u8) u8 {
|
||||
cursor u32 := 0
|
||||
return items[cursor]
|
||||
}
|
||||
bad_bound func($items [2]u8) u8 {
|
||||
start u32 := 0
|
||||
return items[start..][0]
|
||||
}
|
||||
GOOD :: $good([7, 8])
|
||||
BAD_INDEX :: $bad_index([7, 8])
|
||||
BAD_BOUND :: $bad_bound([7, 8])
|
||||
main func() void {
|
||||
_ = GOOD
|
||||
_ = BAD_INDEX
|
||||
_ = BAD_BOUND
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 2)
|
||||
index_error, bound_error := false, false
|
||||
for diagnostic in diagnostics.items {
|
||||
index_error = index_error || strings.contains(diagnostic.message, "index must have type usize, got u32")
|
||||
bound_error = bound_error || strings.contains(diagnostic.message, "slice bound must have type usize, got u32")
|
||||
}
|
||||
testing.expect(t, index_error && bound_error)
|
||||
}
|
||||
|
||||
@(test)
|
||||
layout_builtins_compile_and_run :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-layout-builtins"
|
||||
@@ -6107,7 +6193,7 @@ milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) {
|
||||
}
|
||||
cases := [?]Case{
|
||||
{"examples/programs/index_signed_error", "/tmp/brolang-test-index-signed-error"},
|
||||
{"examples/programs/index_int_constraint_error", "/tmp/brolang-test-index-int-constraint-error"},
|
||||
{"examples/programs/index_unsigned_error", "/tmp/brolang-test-index-unsigned-error"},
|
||||
{"examples/programs/scalar_cast_error", "/tmp/brolang-test-scalar-cast-error"},
|
||||
{"examples/programs/array_const_size_error", "/tmp/brolang-test-array-const-size-error"},
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ main func() i32 {
|
||||
if bits != 255 { return 10 }
|
||||
|
||||
values [10]u8 := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
if values[LocalID(4)] != 4 { return 11 }
|
||||
section []u8 := values[LocalID(2)..LocalID(5)]
|
||||
if values[usize(LocalID(4))] != 4 { return 11 }
|
||||
section []u8 := values[usize(LocalID(2))..usize(LocalID(5))]
|
||||
if section.len != 3 or section[usize(0)] != 2 or section[usize(2)] != 4 { return 12 }
|
||||
|
||||
if u32(id) != 7 or usize(id) != 7 or f64(id) != 7.0 { return 13 }
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
main func() i32 {
|
||||
items [3]mut i32 := undefined
|
||||
i int := 1
|
||||
i u32 := 1
|
||||
items[i] = 42
|
||||
return 0
|
||||
}
|
||||
@@ -52,7 +52,7 @@ main func() i32 {
|
||||
items[0] = 10
|
||||
items[1] = 20
|
||||
|
||||
idx u8 := 2
|
||||
idx usize := 2
|
||||
items[idx] = items[0] + items[1]
|
||||
if (items[2] != 30) return 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user