feat: construct uninitialized compile-time type arrays
This commit is contained in:
+6
-1
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
`}
|
||||
,
|
||||
)
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -33,6 +33,7 @@ TokenKind :: enum {
|
||||
mut
|
||||
c_func
|
||||
null
|
||||
undefined
|
||||
|
||||
# literals
|
||||
ident, string
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user