feat: construct partial compile-time type records
This commit is contained in:
@@ -231,6 +231,17 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
||||
`}
|
||||
`BoundaryConfig :: struct { child type, counts [2]mut usize, bias i16 = 2 }
|
||||
`record_config proc() BoundaryConfig { return { child = i16, counts = [1, 2] } }
|
||||
`partial_record_config proc() BoundaryConfig {
|
||||
` config BoundaryConfig := undefined
|
||||
` config.child = i8
|
||||
` config.counts[0] = 1
|
||||
` copied := config
|
||||
` config.child = bool
|
||||
` copied.child = i16
|
||||
` copied.counts[1] = 2
|
||||
` copied.bias = 2
|
||||
` return copied
|
||||
`}
|
||||
`change_record_config proc(input BoundaryConfig) BoundaryConfig {
|
||||
` copied := input
|
||||
` copied.child = u8
|
||||
@@ -310,6 +321,9 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
||||
` same_record_boundary RecordBoundary({ child = i16, counts = [1, 2] }) :: record_boundary
|
||||
` if (same_record_boundary.values[0] + same_record_boundary.values[1] + same_record_boundary.bias != 42) return 0
|
||||
` if (read_record_config(record_config(), 40) != 42) return 0
|
||||
` partial_boundary RecordBoundary(partial_record_config()) :: record_boundary
|
||||
` if (partial_boundary.values[0] + partial_boundary.values[1] + partial_boundary.bias != 42) return 0
|
||||
` if (read_record_config(partial_record_config(), 40) != 42) return 0
|
||||
` if (read_record_pair(record_pair(), 40) != 42 or read_record_pair(record_pair(), 40) != 42) return 0
|
||||
` copied_initialized = same_initialized.second
|
||||
` payload :: packet.choices[0].payload
|
||||
|
||||
@@ -3679,14 +3679,14 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
if (ast.expr_kind(&state.tree, base) != .typed_constant) return true
|
||||
array :: ast.typed_constant(&state.tree, base)
|
||||
type :: try canonical_type(state, array.type)
|
||||
record :: (type != no_node and type_record_storage(state, type, 0)
|
||||
and (ast.expr_kind(&state.tree, type) == .record_type
|
||||
or ast.expr_kind(&state.tree, type) == .union_type))
|
||||
record :: (type != no_node and (ast.expr_kind(&state.tree, type) == .record_type
|
||||
or (type_record_storage(state, type, 0) and ast.expr_kind(&state.tree, type) == .union_type)))
|
||||
if (
|
||||
!record and !try execution_array_storage(state, array.type)
|
||||
and !try metadata_sequence(state, array.type)
|
||||
) return true
|
||||
selected := no_node
|
||||
selected_type := no_node
|
||||
if (record) {
|
||||
if (kind != .selection) return true
|
||||
member :: ast.selection_expr(&state.tree, id).member
|
||||
@@ -3705,23 +3705,15 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
}
|
||||
selected = active.value?
|
||||
} else selected = ast.field(&state.tree, ast.record_field(&state.tree, array.value, slot)).value?
|
||||
if (!type_record_storage(state, array.type, 0))
|
||||
selected_type = ast.field(&state.tree, ast.record_field(&state.tree, type, slot)).type?
|
||||
} else if (kind == .index) {
|
||||
index :: try type_array_index(state, ast.index_expr(&state.tree, id).index,
|
||||
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
|
||||
}
|
||||
if (!type_record_storage(state, array.type, 0)
|
||||
and ast.expr_kind(&state.tree, selected) != .typed_constant) {
|
||||
descriptor :: ast.array_type(&state.tree, try canonical_type(state, array.type))
|
||||
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
|
||||
descriptor.element, selected)
|
||||
try refresh(state)
|
||||
}
|
||||
if (!type_record_storage(state, array.type, 0))
|
||||
selected_type = ast.array_type(&state.tree, type).element
|
||||
} else {
|
||||
member :: ast.selection_expr(&state.tree, id).member
|
||||
if (!type_record_storage(state, array.type, 0)
|
||||
@@ -3737,6 +3729,16 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
try builtin(state, id, .usize), count)
|
||||
try refresh(state)
|
||||
}
|
||||
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
|
||||
}
|
||||
if (selected_type != no_node and ast.expr_kind(&state.tree, selected) != .typed_constant) {
|
||||
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
|
||||
selected_type, selected)
|
||||
try refresh(state)
|
||||
}
|
||||
# copying a nominal definition into the projection would create a new
|
||||
# identity on every read. retain its original node behind a type alias.
|
||||
if (nominal_type_value(state, selected)) {
|
||||
@@ -3888,13 +3890,15 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
||||
return true
|
||||
}
|
||||
|
||||
# normalize complete record snapshots in declaration order, evaluating every leaf
|
||||
# through its declared context. no record containing type values enters runtime IR.
|
||||
# normalize record snapshots in declaration order through their declared context.
|
||||
# undefined records retain per-leaf initialization state without applying defaults.
|
||||
@hide:file
|
||||
type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
||||
type :: try canonical_type(state, annotation)
|
||||
if (ast.expr_kind(&state.tree, type) == .union_type)
|
||||
return try type_union_value(state, expression, type)
|
||||
if (ast.expr_kind(&state.tree, expression) == .literal_undefined)
|
||||
return try type_storage_undefined(state, expression, annotation)
|
||||
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
|
||||
actual :: ast.typed_constant(&state.tree, expression)
|
||||
if (try same_type(state, type, actual.type)) return expression
|
||||
@@ -4023,9 +4027,28 @@ type_array_initialized proc(state @mut State, value, at ast.NodeId) bool ! Error
|
||||
# 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 {
|
||||
type_storage_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)
|
||||
if (type != no_node and ast.expr_kind(&state.tree, type) == .record_type) {
|
||||
if (!try type_record_check(state, type)) return no_node
|
||||
fields := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&fields)
|
||||
for (0..ast.record_field_count(&state.tree, type)) |i| {
|
||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
||||
if (!try type_step(state, expression)) return no_node
|
||||
value :: try type_storage_undefined(state, expression, field.type?)
|
||||
if (value == no_node) return no_node
|
||||
generated :: try ast.add_field(state.builder, field.span, field.name, null, value)
|
||||
try refresh(state)
|
||||
try arraylist.append(&fields, generated)
|
||||
}
|
||||
span :: ast.node_span(&state.tree, expression)
|
||||
literal :: try ast.add_record_literal(state.builder, span, null, fields.items)
|
||||
result :: try ast.add_typed_constant(state.builder, span, type, literal)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
if (type == no_node or ast.expr_kind(&state.tree, type) != .array_type) return expression
|
||||
descriptor :: ast.array_type(&state.tree, type)
|
||||
length := Value{}
|
||||
@@ -4036,14 +4059,14 @@ type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) a
|
||||
return no_node
|
||||
}
|
||||
# validate child dimensions even when this dimension is empty.
|
||||
child :: try type_array_undefined(state, expression, descriptor.element)
|
||||
child :: try type_storage_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 (i != 0) element = try type_storage_undefined(state, expression, descriptor.element)
|
||||
if (element == no_node) return no_node
|
||||
try arraylist.append(&elements, element)
|
||||
}
|
||||
@@ -4062,7 +4085,7 @@ type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) a
|
||||
@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)
|
||||
return try type_storage_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) {
|
||||
|
||||
Reference in New Issue
Block a user