feat: execute nested compile-time type matches
This commit is contained in:
@@ -2040,6 +2040,12 @@ replace_node proc(builder @mut Builder, target, source NodeId) void {
|
||||
builder.storage.nodes.items[usize(target)] = get_node(&builder.storage, source)
|
||||
}
|
||||
|
||||
#! selects a source node, retaining its literal spelling and diagnostic provenance.
|
||||
replace_node_from_source proc(builder @mut Builder, target, source NodeId) void {
|
||||
replace_node(builder, target, source)
|
||||
builder.storage.spans.items[usize(target)] = get_span(&builder.storage, source)
|
||||
}
|
||||
|
||||
#! shallow-copies kind, payload, and span; extra storage remains shared.
|
||||
copy_node proc(builder @mut Builder, id NodeId) NodeId ! mem.AllocError {
|
||||
node :: get_node(&builder.storage, id)
|
||||
|
||||
@@ -94,7 +94,13 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
||||
` length = length + 1
|
||||
` }
|
||||
` return match length {
|
||||
` 2: struct { values [length]Element },
|
||||
` 2: struct {
|
||||
` values [match length { 2: length, else: usize(1) }](match true {
|
||||
` true: Element,
|
||||
` false: i8(128),
|
||||
` })
|
||||
` bias i16 = match true { true: 0, false: i8(128) }
|
||||
` },
|
||||
` else: i8(128),
|
||||
` }
|
||||
`}
|
||||
@@ -105,7 +111,7 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
||||
` box Box(i16) :: { value = identity(i16, values[0]) }
|
||||
` fixed Fixed(count()) :: { values = [box.value, 2] }
|
||||
` same Fixed(2) :: fixed
|
||||
` return identity(same.values[0]) + same.values[$(usize(1))]
|
||||
` return identity(same.values[0]) + same.values[$(usize(1))] + same.bias
|
||||
`}
|
||||
,
|
||||
)
|
||||
|
||||
+11
-3
@@ -282,6 +282,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
or next_n_is(state, 1, .struct)
|
||||
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
|
||||
or next_n_is(state, 1, .distinct)
|
||||
or next_n_is(state, 1, .match)
|
||||
or next_n_is(state, 1, .at)
|
||||
or next_n_is(state, 1, .question)
|
||||
or (next_n_is(state, 1, .open_paren) and pointer_binding_starts(state))
|
||||
@@ -800,6 +801,7 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
|
||||
if next_is(state, .struct) return try parse_record_type(state)
|
||||
if next_is(state, .enum) return try parse_enum_type(state)
|
||||
if next_is(state, .union) return try parse_union_type(state)
|
||||
if next_is(state, .match) return try parse_match(state)
|
||||
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
|
||||
token :: try consume(state, .ident)
|
||||
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
@@ -846,7 +848,7 @@ parse_function_type proc(state @mut State, start ast.Span, invalid bool) ast.Nod
|
||||
starts_type proc(kind lexer.TokenKind) bool {
|
||||
return (
|
||||
kind == .ident or kind == .open_bracket or kind == .struct
|
||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
|
||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias or kind == .match
|
||||
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc
|
||||
)
|
||||
}
|
||||
@@ -1498,6 +1500,7 @@ parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! Pars
|
||||
@hide:file
|
||||
array_starts_type proc(state @State) bool {
|
||||
depth usize := 0
|
||||
curly_depth := usize(0)
|
||||
for (state.next_token)..(state.tokens.len) |i| {
|
||||
kind :: state.tokens[i].kind
|
||||
if (kind == .open_bracket) depth += 1
|
||||
@@ -1509,7 +1512,12 @@ array_starts_type proc(state @State) bool {
|
||||
return starts_type(next) or next == .mut
|
||||
}
|
||||
}
|
||||
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
|
||||
if (kind == .open_curly) curly_depth += 1
|
||||
if (kind == .close_curly) {
|
||||
if (curly_depth == 0) return false
|
||||
curly_depth -= 1
|
||||
}
|
||||
if (kind == .eof) return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1587,7 +1595,7 @@ named_binding_starts proc(state @State) bool {
|
||||
next_is(state, .ident)
|
||||
and (
|
||||
next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
|
||||
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc)
|
||||
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc) or next_n_is(state, 1, .match)
|
||||
or next_n_is(state, 1, .at)
|
||||
or next_n_is(state, 1, .question)
|
||||
or (next_n_is(state, 1, .open_paren) and pointer_binding_starts(state))
|
||||
|
||||
@@ -67,7 +67,7 @@ deinit proc(result @mut Result) void {
|
||||
while (index > 0) {
|
||||
index -= 1
|
||||
change :: result.changes.items[index]
|
||||
ast.replace_node(result.builder, change.target, change.saved)
|
||||
ast.replace_node_from_source(result.builder, change.target, change.saved)
|
||||
}
|
||||
arraylist.deinit(&result.changes)
|
||||
arraylist.deinit(&result.constraints)
|
||||
@@ -583,11 +583,14 @@ instantiate proc(state @mut State, index usize, call ast.NodeId) void ! Error {
|
||||
}
|
||||
}
|
||||
if (is_type(state, view.return_type)) {
|
||||
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
||||
state.type_execution_depth += 1
|
||||
defer state.type_execution_depth -= 1
|
||||
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||
constraints_start :: state.rewrites.constraints.items.len
|
||||
returned :: try return_type_value(state, view.body, ast.NodeId(clone_start))
|
||||
if (returned == no_node) {
|
||||
try reject_evaluation(state, call)
|
||||
try reject_type_result(state, call, returned, constraints_start)
|
||||
return
|
||||
}
|
||||
result := returned
|
||||
@@ -603,9 +606,20 @@ instantiate proc(state @mut State, index usize, call ast.NodeId) void ! Error {
|
||||
state.instances.items[index].type = returned
|
||||
state.bindings.items[usize(declaration)] = sema.Binding{ kind = .declaration, data = sema.BindingData{ declaration = declaration } }
|
||||
try arraylist.append(&state.declarations, declaration)
|
||||
if (!try expand_type_matches(state, declaration)) {
|
||||
try reject_type_result(state, call, result, constraints_start)
|
||||
return
|
||||
}
|
||||
try expand(state, declaration)
|
||||
constraint_count :: state.rewrites.constraints.items.len
|
||||
for (constraints_start..constraint_count) |i| try expand(state, state.rewrites.constraints.items[i])
|
||||
for (constraints_start..constraint_count) |i| {
|
||||
constraint :: state.rewrites.constraints.items[i]
|
||||
if (!try expand_type_matches(state, constraint)) {
|
||||
try reject_type_result(state, call, result, constraints_start)
|
||||
return
|
||||
}
|
||||
try expand(state, constraint)
|
||||
}
|
||||
} else {
|
||||
runtime := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&runtime)
|
||||
@@ -637,6 +651,16 @@ reject_evaluation proc(state @mut State, call ast.NodeId) void ! Error {
|
||||
state.bindings.items[usize(call)] = sema.Binding{ kind = .invalid, data = sema.BindingData{ diagnostic = diagnostic } }
|
||||
}
|
||||
|
||||
# Failed deferred elaboration must not leave unchecked type expressions in the package.
|
||||
@hide:file
|
||||
reject_type_result proc(state @mut State, call, result ast.NodeId, constraints_start usize) void ! Error {
|
||||
try reject_evaluation(state, call)
|
||||
state.evaluation_diagnostic = binding(state, call).data.diagnostic
|
||||
if (result != no_node) try reject_evaluation(state, result)
|
||||
for (constraints_start..(state.rewrites.constraints.items.len)) |i|
|
||||
try reject_evaluation(state, state.rewrites.constraints.items[i])
|
||||
}
|
||||
|
||||
@hide:file
|
||||
evaluation_dependencies proc(
|
||||
state @mut State, id ast.NodeId, flags []mut u8, declarations @mut arraylist.ArrayList(ast.NodeId),
|
||||
@@ -976,9 +1000,6 @@ TypeFlow :: enum { next, returned, break, continue, invalid }
|
||||
return_type_value proc(state @mut State, block, first_owned ast.NodeId) ast.NodeId ! Error {
|
||||
# ponytail: snapshots recheck scalar IR; cap type-control work at 4096 steps
|
||||
# until type values can share one persistent checked execution frame.
|
||||
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
||||
state.type_execution_depth += 1
|
||||
defer state.type_execution_depth -= 1
|
||||
execution := TypeExecution{
|
||||
locals = arraylist.init(state.allocator),
|
||||
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
||||
@@ -1060,6 +1081,7 @@ nominal_type_value proc(state @State, id ast.NodeId) bool {
|
||||
# the same rules as runtime matching, without checking unexecuted type bodies.
|
||||
@hide:file
|
||||
type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
if (!try expand_type_matches(state, ast.match_expr(&state.tree, id).subject)) return no_index
|
||||
arms := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&arms)
|
||||
patterns := arraylist.init(ast.NodeId, state.allocator)
|
||||
@@ -1074,8 +1096,11 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
return no_index
|
||||
}
|
||||
patterns.items = patterns.items[..0]
|
||||
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j|
|
||||
try arraylist.append(&patterns, ast.match_pattern(&state.tree, arm_id, j))
|
||||
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j| {
|
||||
pattern :: ast.match_pattern(&state.tree, arm_id, j)
|
||||
if (!try expand_type_matches(state, pattern)) return no_index
|
||||
try arraylist.append(&patterns, pattern)
|
||||
}
|
||||
span :: ast.node_span(&state.tree, arm_id)
|
||||
index :: try ast.add_integer(state.builder, span, u64(i))
|
||||
selector :: try ast.add_match_arm(state.builder, span, patterns.items,
|
||||
@@ -1105,10 +1130,31 @@ type_value_expression proc(state @mut State, input ast.NodeId) ast.NodeId ! Erro
|
||||
return expression
|
||||
}
|
||||
|
||||
# Normalize only selected expression arms before ordinary expansion can visit them.
|
||||
# Nominal results enter here after their instance declaration is cached.
|
||||
@hide:file
|
||||
expand_type_matches proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .match_expr) {
|
||||
selected :: try type_value_expression(state, id)
|
||||
if (selected == no_node) return false
|
||||
resolved :: binding(state, selected)
|
||||
try remember(state, id)
|
||||
ast.replace_node_from_source(state.builder, id, selected)
|
||||
try refresh(state)
|
||||
state.bindings.items[usize(id)] = resolved
|
||||
}
|
||||
for (0..ast.child_count(&state.tree, id)) |i| {
|
||||
if (!try expand_type_matches(state, ast.child(&state.tree, id, i))) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@hide:file
|
||||
type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.NodeId ! Error {
|
||||
if (annotation == no_node) return no_node
|
||||
expression :: try type_value_expression(state, input)
|
||||
if (expression == no_node) return no_node
|
||||
if (!try expand_type_matches(state, annotation)) return no_node
|
||||
# A recursive record needs its cached declaration before expanding its fields.
|
||||
if (is_type(state, annotation) and nominal_type_value(state, expression)) {
|
||||
concrete :: try materialize(state, Value{ kind = .type, node = expression })
|
||||
@@ -1117,6 +1163,7 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
|
||||
try arraylist.append(&state.rewrites.constraints, constraint)
|
||||
return concrete.node
|
||||
}
|
||||
if (!try expand_type_matches(state, expression)) return no_node
|
||||
try expand(state, expression)
|
||||
resolved :: binding(state, expression)
|
||||
if (resolved.kind == .invalid) {
|
||||
@@ -1178,6 +1225,7 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
if (declaration.type) |type| annotation = try type_snapshot(state, execution, type)
|
||||
else if (nominal_type_value(state, expression)) annotation = try builtin(state, expression, .type)
|
||||
else {
|
||||
if (!try expand_type_matches(state, expression)) return .invalid
|
||||
try expand(state, expression)
|
||||
annotation = try expression_type(state, expression, 0)
|
||||
if (annotation == no_node) {
|
||||
|
||||
Reference in New Issue
Block a user