feat: contextually type empty mutable slice literals

This commit is contained in:
2026-10-25 01:09:27 +02:00
parent 3a90a90428
commit ecb7b2fc64
3 changed files with 142 additions and 10 deletions
+35 -10
View File
@@ -1488,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
@@ -3152,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,
)
@@ -5220,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:
@@ -8758,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
@@ -8770,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
@@ -8784,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,
})
@@ -8794,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,
})
}
@@ -10389,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,