feat: construct uninitialized compile-time type arrays

This commit is contained in:
2026-10-27 18:01:00 +01:00
parent e00a2abe27
commit ca53dab86b
12 changed files with 307 additions and 13 deletions
+6 -1
View File
@@ -39,6 +39,7 @@ LiteralKind :: enum {
bool
null
void
undefined
}
ExprKind :: enum {
@@ -78,6 +79,7 @@ ExprKind :: enum {
typed_constant
comptime_expr
literal_void
literal_undefined
}
DeclKind :: enum {
@@ -184,6 +186,7 @@ NodeKind :: enum {
expr_comptime
literal_float_value
literal_void
literal_undefined
}
@hide:file
@@ -499,6 +502,7 @@ add_literal proc(builder @mut Builder, kind LiteralKind, span Span) NodeId ! mem
.bool: .literal_bool
.null: .literal_null
.void: .literal_void
.undefined: .literal_undefined
}
return try add_node(builder, Node{ kind = node_kind }, span)
}
@@ -1433,6 +1437,7 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_comptime: .comptime_expr
.literal_float_value: .literal_float
.literal_void: .literal_void
.literal_undefined: .literal_undefined
else: unreachable
}
}
@@ -1828,7 +1833,7 @@ node_layout proc(node Node) NodeLayout {
layout := NodeLayout{}
match node.kind {
.literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null,
.literal_void, .expr_identifier, .invalid_expr: {}
.literal_void, .literal_undefined, .expr_identifier, .invalid_expr: {}
.expr_unary: layout.direct_children = 2
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant,
.expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type:
+18 -1
View File
@@ -195,6 +195,17 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
`read_type_rows proc($rows [2]mut [1]mut type, value rows[0][0]) rows[1][0] {
` return value + rows[1][0](rows.len)
`}
`UninitializedRows proc() [2]mut type {
` types [2]mut type := undefined
` for (usize(0)..types.len) |index| {
` if (index == 0) { types[index] = struct { value i16 } }
` else { types[index] = types[0] }
` }
` return types
`}
`UninitializedType proc(types [2]mut type) type {
` return struct { first types[0], second types[1] }
`}
`read_selected proc($choice Choice, value i16) i16 {
` match choice {
` .payload |detail|: {
@@ -236,6 +247,12 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` if (copy_type_rows(TypeRows())[0][0] != u8 or copy_type_rows(TypeRows())[1][0] != i16) return 0
` if (TypeRows()[0][0] != i16) return 0
` if (read_type_rows(TypeRows(), 40) != 42 or read_type_rows(TypeRows(), 40) != 42) return 0
` initialized UninitializedType(UninitializedRows()) :: {
` first = { value = 19 }, second = { value = 21 },
` }
` same_initialized UninitializedType(UninitializedRows()) :: initialized
` copied_initialized := same_initialized.first
` copied_initialized = same_initialized.second
` payload :: packet.choices[0].payload
` if (payload.values[0] != 40 or payload.values[1] != 2 or payload.text.len != 2) return 0
` match packet.choices[1] { .empty: {}, .payload: return 0 }
@@ -244,7 +261,7 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` .failed |detail|: detail.code + i16(detail.text.len),
` }
` if (error_code != 7) return 0
` return read_selected(packet.choices[0], identity(same.values[0]) + same.values[$(usize(1))] + same.bias - 2)
` return read_selected(packet.choices[0], identity(initialized.first.value) + copied_initialized.value + same.values[$(usize(1))] + same.bias - 2)
`}
,
)
+1
View File
@@ -23,6 +23,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
{ "proc", .proc },
{ "c_func", .c_func },
{ "null", .null },
{ "undefined", .undefined },
{ "return", .return },
{ "break", .break },
{ "continue", .continue },
+1
View File
@@ -33,6 +33,7 @@ TokenKind :: enum {
mut
c_func
null
undefined
# literals
ident, string
+1 -1
View File
@@ -1893,7 +1893,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
}
.alias_expr: return try lower_expression(state, ast.type_operand(tree, id))
.enum_literal: {
.enum_literal, .literal_undefined: {
if (!trap_type(state, id)) unreachable
}
.match_expr: return try lower_match(state, id)
+6 -2
View File
@@ -299,7 +299,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
.defer, .errdefer: return try parse_cleanup(state, allow_else)
.open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state))
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .struct,
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null, .try, .dollar: {
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null, .undefined, .try, .dollar: {
expression :: try parse_expr(state)
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
return try ast.add_expression_stmt(state.builder, expression)
@@ -852,7 +852,7 @@ member_name proc(kind lexer.TokenKind) bool {
return match kind {
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
.alias, .match, .import, .test, .c_func, .null, .try, .catch, .defer, .errdefer: true
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
else: false
}
}
@@ -1682,6 +1682,10 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
state.next_token += 1
return try ast.add_literal(state.builder, .null, token.span)
}
.undefined: {
state.next_token += 1
return try ast.add_literal(state.builder, .undefined, token.span)
}
.ident: {
state.next_token += 1
return try ast.add_identifier(state.builder, token.span, token.data.str_id)
+1
View File
@@ -1201,6 +1201,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
.literal_string: state.cells[usize(id)].type = Type{ kind = .string }
.literal_null: state.cells[usize(id)].type = Type{ kind = .null }
.literal_void: state.cells[usize(id)].type = Type{ kind = .void }
.literal_undefined: try fail(state, id, .comptime_argument_required, ast.node_span(tree, id))
.builtin_call: try collect_builtin(state, id)
.slice: {
view :: ast.slice_expr(tree, id)
+2 -1
View File
@@ -522,7 +522,8 @@ resolve_expr proc(
try resolve_expr(state, scope, value.type)
try resolve_expr(state, scope, value.value)
}
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void: {}
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
.literal_undefined: {}
.enum_literal: {}
.distinct_type, .alias_expr, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
.enum_type: {
+73 -1
View File
@@ -938,7 +938,7 @@ argument_annotation proc(
evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Error {
if (type_array_annotation(&state.tree, state.bindings.items, annotation)) {
constant :: try type_execution_value(state, id, annotation)
if (constant == no_node) return Value{}
if (constant == no_node or !try type_array_initialized(state, constant, id)) return Value{}
value :: ast.typed_constant(&state.tree, constant)
return Value{ kind = .aggregate, node = value.value, annotation = value.type }
}
@@ -1484,6 +1484,11 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
ast.array_element_count(&state.tree, array.value))
if (index == no_index) return false
selected = ast.array_element(&state.tree, array.value, index)
if (ast.expr_kind(&state.tree, selected) == .literal_undefined) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, id))
return false
}
} else {
member :: ast.selection_expr(&state.tree, id).member
if (!mem.eql(ast.literal_text(&state.tree, member), "len")) {
@@ -1562,8 +1567,68 @@ type_array_compatible proc(state @mut State, expected, actual ast.NodeId) bool !
return try type_array_compatible(state, a.element, b.element)
}
# undefined leaves are initialization state, not type values. snapshots may copy
# that state locally; procedure boundaries require every leaf to be initialized.
@hide:file
type_array_initialized proc(state @mut State, value, at ast.NodeId) bool ! Error {
kind :: ast.expr_kind(&state.tree, value)
if (kind == .literal_undefined) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, at))
return false
}
if (kind == .typed_constant)
return try type_array_initialized(state, ast.typed_constant(&state.tree, value).value, at)
if (kind == .array_literal) {
for (0..ast.array_element_count(&state.tree, value)) |i| {
if (!try type_array_initialized(state, ast.array_element(&state.tree, value, i), at)) return false
}
}
return true
}
# bound the expanded snapshot, not just its dimensions, so later reads and
# initialization checks cannot traverse an exponentially shared empty shape.
@hide:file
type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
if (is_type(state, annotation)) return expression
type :: try canonical_type(state, annotation)
descriptor :: ast.array_type(&state.tree, type)
length := Value{}
if (descriptor.count) |count| length = try evaluate(state, count)
if (length.kind != .integer or length.negative) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_array_length, ast.node_span(&state.tree, annotation))
return no_node
}
# validate child dimensions even when this dimension is empty.
child :: try type_array_undefined(state, expression, descriptor.element)
if (child == no_node) return no_node
elements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&elements)
for (0..length.magnitude) |i| {
if (!try type_step(state, expression)) return no_node
element := child
if (i != 0) element = try type_array_undefined(state, expression, descriptor.element)
if (element == no_node) return no_node
try arraylist.append(&elements, element)
}
span :: ast.node_span(&state.tree, expression)
type_span :: ast.node_span(&state.tree, annotation)
element_type :: if (ast.expr_kind(&state.tree, child) == .typed_constant)
ast.typed_constant(&state.tree, child).type else descriptor.element
count :: try ast.add_integer(state.builder, span, length.magnitude)
concrete :: try ast.add_array_type(state.builder, type_span, count, element_type, descriptor.mutable)
literal :: try ast.add_array_literal(state.builder, span, elements.items)
result :: try ast.add_typed_constant(state.builder, span, concrete, literal)
try refresh(state)
return result
}
@hide:file
type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
if (ast.expr_kind(&state.tree, expression) == .literal_undefined)
return try type_array_undefined(state, expression, annotation)
type :: try canonical_type(state, annotation)
array :: ast.array_type(&state.tree, type)
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
@@ -1695,6 +1760,11 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
}
if (try type_array_storage(state, annotation)) return try type_array_value(state, expression, annotation)
if (is_type(state, annotation)) {
if (ast.expr_kind(&state.tree, expression) == .literal_undefined) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, expression))
return no_node
}
value :: try evaluate(state, expression)
if (value.kind != .type) {
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
@@ -1832,6 +1902,8 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
if (ast.return_stmt(&state.tree, statement).value) |value| {
expression :: try type_snapshot(state, execution, value)
execution.returned = try type_execution_value(state, expression, execution.result_type)
if (execution.returned != no_node
and !try type_array_initialized(state, execution.returned, value)) return .invalid
if (execution.returned != no_node) return .returned
}
return .invalid