array size inference from value

This commit is contained in:
2026-07-02 20:16:14 +02:00
parent 7cda126924
commit a98b26446d
4 changed files with 56 additions and 13 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ 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, and compile-time folding for numeric constant expressions
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
- arrays `[N]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
+47 -6
View File
@@ -607,6 +607,25 @@ resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: as
return types.with_array_count(&checker.module.types, value, u64(len(expr.args)))
}
has_inferred_array_count :: proc(checker: ^Checker, value: types.Type) -> bool {
item, ok := types.node(&checker.module.types, value)
return ok && item.kind == .Array && item.inferred_count
}
resolve_inferred_array_from_type :: proc(checker: ^Checker, value, inferred: types.Type) -> types.Type {
item, ok := types.node(&checker.module.types, value)
actual, actual_ok := types.node(&checker.module.types, inferred)
if !ok || !actual_ok || item.kind != .Array || actual.kind != .Array || !item.inferred_count {
return value
}
if item.child != actual.child || item.mutable != actual.mutable ||
item.has_sentinel != actual.has_sentinel ||
(item.has_sentinel && item.sentinel != actual.sentinel) {
return value
}
return types.with_array_count(&checker.module.types, value, actual.count)
}
function_index_less :: proc(left, right: Function_Index_Entry) -> bool {
if left.scope != right.scope {
return left.scope < right.scope
@@ -2249,8 +2268,9 @@ infer_statements :: proc(
value_type := types.INVALID
if !is_undefined_expr(checker, statement.expr) {
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
declared_local = resolve_inferred_array_from_type(checker, declared_local, value_type)
}
if is_runtime_type(checker, declared_local) {
if is_runtime_type(checker, declared_local) && !has_inferred_array_count(checker, declared_local) {
value_type = declared_local
} else if types.is_constraint(declared_local) {
// Seed the binding in-family (INVALID on mismatch, which
@@ -2823,8 +2843,12 @@ infer_all :: proc(checker: ^Checker) {
// Demands accumulate in global_demands so the default never blocks a later
// cross-family demand (e.g. integer literal -> unsigned or float).
for global, index in checker.ast_module.globals {
declared := type_from_syntax(checker, global.type, global.pkg, global.file)
if is_runtime_type(checker, declared) {
declared := resolve_inferred_array(
checker,
type_from_syntax(checker, global.type, global.pkg, global.file),
global.expr,
)
if is_runtime_type(checker, declared) && !has_inferred_array_count(checker, declared) {
checker.global_types[index] = declared
continue
}
@@ -2871,7 +2895,20 @@ infer_all :: proc(checker: ^Checker) {
continue
}
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
if is_runtime_type(checker, type_from_syntax(checker, global.type, global.pkg, global.file)) {
declared := resolve_inferred_array(
checker,
type_from_syntax(checker, global.type, global.pkg, global.file),
global.expr,
)
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
resolved != declared {
if !types.equal(checker.global_types[index], resolved) {
checker.global_types[index] = resolved
changed = true
}
continue
}
if is_runtime_type(checker, declared) && !has_inferred_array_count(checker, declared) {
continue
}
if is_runtime_type(checker, checker.global_demands[index]) {
@@ -4935,7 +4972,7 @@ build_block :: proc(
if statement_id != ast.INVALID_STMT && int(statement_id) < len(ctx.local_types) &&
is_runtime_type(checker, ctx.local_types[statement_id]) &&
(types.is_constraint(declared) || is_undefined_expr(checker, statement.expr) ||
open_const_decl || numeric_arithmetic_decl) {
open_const_decl || numeric_arithmetic_decl || has_inferred_array_count(checker, declared)) {
declared = ctx.local_types[statement_id]
}
// A still-unresolved constraint means the initializer's numeric
@@ -4974,7 +5011,7 @@ build_block :: proc(
ctx.problematic^ = true
continue
}
if !is_runtime_type(checker, declared) {
if !is_runtime_type(checker, declared) || has_inferred_array_count(checker, declared) {
id := source.addf(
checker.diagnostics,
statement.span,
@@ -7477,6 +7514,10 @@ build_globals :: proc(checker: ^Checker) {
calls: [dynamic]hir.Function_Id
calls.allocator = checker.allocator
declared := resolve_inferred_array(checker, type_from_syntax(checker, global.type, global.pkg, global.file), global.expr)
if has_inferred_array_count(checker, declared) &&
is_runtime_type(checker, checker.global_types[global_index]) {
declared = checker.global_types[global_index]
}
expected := types.INVALID
if is_runtime_type(checker, declared) {
expected = declared
+5 -3
View File
@@ -1981,12 +1981,14 @@ comptime_value_params_specialize_by_value_and_omit_runtime_args :: proc(t: ^test
return data
}
main func() void {
four [4]u8 :: make_array(4)
eight [8]u8 :: make_array(8)
again [4]u8 :: make_array(4)
four [_]u8 :: make_array(4)
eight [_]u8 :: make_array(8)
again [_]u8 :: make_array(4)
literal [_]u8 :: [1, 2, 3, 4]
_ = four
_ = eight
_ = again
_ = literal
}
`
source_file := source.Source{path="test.bro", text=text}
@@ -8,7 +8,7 @@ value func($N usize) usize {
}
main func() i32 {
four [4]u8 :: make_array(4)
four [_]u8 :: make_array(4)
if four.len != 4 {
return 1
}
@@ -16,7 +16,7 @@ main func() i32 {
return 2
}
eight [8]u8 :: make_array(8)
eight [_]u8 :: make_array(8)
if eight.len != 8 {
return 3
}
@@ -24,7 +24,7 @@ main func() i32 {
return 4
}
again [4]u8 :: make_array(4)
again [_]u8 :: [1, 2, 3, 4]
if again.len != 4 {
return 5
}