feat: execute nested compile-time type matches
This commit is contained in:
@@ -100,7 +100,9 @@ the specialization checkpoint supports type arguments and constant integer, bool
|
|||||||
|
|
||||||
type-producing bodies support mutable type and value locals, direct binding assignment, scoped blocks, conditional/early returns, integer range and while loops, break/continue and nested/recursive type-producing calls. for example, start with `Result := T`, repeat `Result = [2]Result` in a loop, then `return Result` to generate nested array storage. snapshots retain each local's current value without changing the source AST; scalar operations reuse checking, typed IR and interpreter execution, preserving exact types and overflow diagnostics. type-control execution is limited to 4096 statement/loop-guard steps shared with nested producers; individual scalar evaluations retain the interpreter limits below. only executed bodies are elaborated, while every body is still parsed and name-resolved. field/index assignment, optional captures, cleanup and type-valued aggregate storage in type-producing bodies remain outside this checkpoint.
|
type-producing bodies support mutable type and value locals, direct binding assignment, scoped blocks, conditional/early returns, integer range and while loops, break/continue and nested/recursive type-producing calls. for example, start with `Result := T`, repeat `Result = [2]Result` in a loop, then `return Result` to generate nested array storage. snapshots retain each local's current value without changing the source AST; scalar operations reuse checking, typed IR and interpreter execution, preserving exact types and overflow diagnostics. type-control execution is limited to 4096 statement/loop-guard steps shared with nested producers; individual scalar evaluations retain the interpreter limits below. only executed bodies are elaborated, while every body is still parsed and name-resolved. field/index assignment, optional captures, cleanup and type-valued aggregate storage in type-producing bodies remain outside this checkpoint.
|
||||||
|
|
||||||
type-producing bodies also execute scalar/enum `match` statements and direct type-valued match expressions in initializers, assignments and returns, such as `return match wide { true: i16, false: i8 }`. selected expression arms may themselves be matches. an ordinary checked IR match returns the selected arm index, preserving subject/pattern typing, range boundaries, overlap, unreachable-arm and exhaustiveness diagnostics without checking unexecuted bodies. statement arms retain scoped locals, assignments, early returns and enclosing-loop break/continue; expression selection shares the type-control step bound. matches nested inside type constructors/defaults, tagged-union payload captures and broader generic-body match specialization remain open.
|
type-producing bodies also execute scalar/enum `match` statements and type-valued match expressions in initializers, assignments and returns, such as `return match wide { true: i16, false: i8 }`. selected expression arms may themselves be matches. an ordinary checked IR match returns the selected arm index, preserving subject/pattern typing, range boundaries, overlap, unreachable-arm and exhaustiveness diagnostics without checking unexecuted bodies. statement arms retain scoped locals, assignments, early returns and enclosing-loop break/continue; expression selection shares the type-control step bound.
|
||||||
|
|
||||||
|
matches also select nested types, array lengths, generic arguments, callback signatures and field defaults in type-producing bodies. for example, `return struct { values [2](match wide { true: i16, false: i8 }) }` generates concrete storage without elaborating the unselected arm. snapshots preserve loop-local type/value bindings, selected literals retain their spelling and source provenance, and nominal results are cached before their recursive fields are expanded. deferred type selection shares the producer's execution bound; failed elaboration retains its original diagnostic without leaving unchecked generated types behind. tagged-union payload captures, general type-valued storage and broader generic-body match specialization remain open.
|
||||||
|
|
||||||
ordinary procedures may compute integer, boolean, float and string compile-time arguments and return nested records/tuples and fixed arrays from constant inputs, such as `Fixed(i16, length(2))`. calls reuse the ordinary checker, typed IR and interpreter, preserving local type inference, checked arithmetic, mutable locals, range/while loops, break/continue and recursive or specialized helper calls. aggregate arguments receive their declared context, including dependent `[N]T` parameters; cached specializations compare normalized values, not literal spelling or evaluation identity. returned aggregates become owned source literals before temporary interpreter storage is released, preserving by-value copies, exact integer types, string bytes, enum fields, optional fields and IEEE float bits.
|
ordinary procedures may compute integer, boolean, float and string compile-time arguments and return nested records/tuples and fixed arrays from constant inputs, such as `Fixed(i16, length(2))`. calls reuse the ordinary checker, typed IR and interpreter, preserving local type inference, checked arithmetic, mutable locals, range/while loops, break/continue and recursive or specialized helper calls. aggregate arguments receive their declared context, including dependent `[N]T` parameters; cached specializations compare normalized values, not literal spelling or evaluation identity. returned aggregates become owned source literals before temporary interpreter storage is released, preserving by-value copies, exact integer types, string bytes, enum fields, optional fields and IEEE float bits.
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ the same constant evaluation path. compile-time `if` selection and type comparis
|
|||||||
specialize only the selected body. type-producing procedures also accept ordinary
|
specialize only the selected body. type-producing procedures also accept ordinary
|
||||||
constant inputs, cached and erased like explicit compile-time arguments. mutable
|
constant inputs, cached and erased like explicit compile-time arguments. mutable
|
||||||
type/value locals, direct assignments, branches, scalar/enum matching and bounded
|
type/value locals, direct assignments, branches, scalar/enum matching and bounded
|
||||||
integer-range/while loops now generate types and storage; general type-valued
|
integer-range/while loops now generate types and storage. matches select nested
|
||||||
execution, reflection and generic library acceptance remain open.
|
constructors, lengths, signatures and defaults; general type-valued execution,
|
||||||
|
reflection and generic library acceptance remain open.
|
||||||
the driver retains frontend storage and source provenance through
|
the driver retains frontend storage and source provenance through
|
||||||
`@source/compilation`, with compilation-wide AST node identities.
|
`@source/compilation`, with compilation-wide AST node identities.
|
||||||
file commands retain single-file behavior; directory commands compile the
|
file commands retain single-file behavior; directory commands compile the
|
||||||
@@ -69,7 +70,8 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [x] conditional specialization checkpoint: explicit compile-time `if` guards and side-effect-free constant guards in concrete generic procedures select a body before specialization/checking. type equality retains aliases, nominal identity and structural signatures; composed error-set identity/cache keys ignore ordering, grouping and repetition. guard checking, selected-body diagnostics, scoped cleanup, runtime guards and repeatable compilation work through interpreter/native execution; both arms remain parsed and name-resolved.
|
- [x] conditional specialization checkpoint: explicit compile-time `if` guards and side-effect-free constant guards in concrete generic procedures select a body before specialization/checking. type equality retains aliases, nominal identity and structural signatures; composed error-set identity/cache keys ignore ordering, grouping and repetition. guard checking, selected-body diagnostics, scoped cleanup, runtime guards and repeatable compilation work through interpreter/native execution; both arms remain parsed and name-resolved.
|
||||||
- [x] constant-input type-procedure checkpoint: type-producing procedures accept ordinary parameters alongside `$` parameters, require every argument explicitly and evaluate/cache/erase all inputs. generated record/array storage, nominal cache identity, nested calls, dependent contextual aggregate inputs, exact erased-argument checking, source diagnostics, imported producers and repeatable compilation work through interpreter/native execution.
|
- [x] constant-input type-procedure checkpoint: type-producing procedures accept ordinary parameters alongside `$` parameters, require every argument explicitly and evaluate/cache/erase all inputs. generated record/array storage, nominal cache identity, nested calls, dependent contextual aggregate inputs, exact erased-argument checking, source diagnostics, imported producers and repeatable compilation work through interpreter/native execution.
|
||||||
- [x] mutable type-execution checkpoint: type-producing bodies execute mutable type/value locals, direct binding assignments, scoped blocks, if/else and early returns, integer-range/while loops, break/continue and nested/recursive producers. generated nested types, scalar-computed lengths/defaults, nominal cache identity, exact value types, overflow/guard/assignment diagnostics, imported producers and repeatable compilation work through interpreter/native execution. type-control execution shares a 4096-step bound across nested producers; scalar operations reuse checked IR execution. field/index updates, optional captures, cleanup and type-valued aggregate storage in these bodies remain open.
|
- [x] mutable type-execution checkpoint: type-producing bodies execute mutable type/value locals, direct binding assignments, scoped blocks, if/else and early returns, integer-range/while loops, break/continue and nested/recursive producers. generated nested types, scalar-computed lengths/defaults, nominal cache identity, exact value types, overflow/guard/assignment diagnostics, imported producers and repeatable compilation work through interpreter/native execution. type-control execution shares a 4096-step bound across nested producers; scalar operations reuse checked IR execution. field/index updates, optional captures, cleanup and type-valued aggregate storage in these bodies remain open.
|
||||||
- [x] type-match checkpoint: type-producing bodies execute scalar/enum match statements and direct type-valued match expressions in initializers, assignments and returns, including nested selected arms. ordinary checking/IR execution select the arm and validate subject/pattern types, ranges, overlap, unreachable arms and exhaustiveness; only the selected body is elaborated. generated storage, scoped locals, loop transfers, early returns, nominal cache identity, selected-body diagnostics, dependency provenance and repeatable compilation work through interpreter/native execution. all bodies remain parsed/name-resolved and execution shares the type-control bound. matches nested inside type constructors/defaults and tagged-union payload captures remain open.
|
- [x] type-match checkpoint: type-producing bodies execute scalar/enum match statements and direct type-valued match expressions in initializers, assignments and returns, including nested selected arms. ordinary checking/IR execution select the arm and validate subject/pattern types, ranges, overlap, unreachable arms and exhaustiveness; only the selected body is elaborated. generated storage, scoped locals, loop transfers, early returns, nominal cache identity, selected-body diagnostics, dependency provenance and repeatable compilation work through interpreter/native execution. all bodies remain parsed/name-resolved and execution shares the type-control bound. tagged-union payload captures remain open.
|
||||||
|
- [x] nested type-match checkpoint: matches select types inside constructors, array lengths, generic arguments, local annotations, callback signatures and field defaults in type-producing bodies. selection precedes ordinary expansion, preserves literal spelling/source provenance and loop snapshots, and caches nominal results before expanding recursive fields. selected-body and pattern diagnostics, failed deferred elaboration, imported producers and repeatable compilation work through interpreter/native execution; deferred selection shares the producer's type-control bound.
|
||||||
- [ ] remaining gate: general type-valued compile-time execution, broader compile-time match/inline expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), broader constant materialization (including unions, fallible results and storage-bearing values), plus existing-library acceptance. required collection/index iteration, expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
|
- [ ] remaining gate: general type-valued compile-time execution, broader compile-time match/inline expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), broader constant materialization (including unions, fallible results and storage-bearing values), plus existing-library acceptance. required collection/index iteration, expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
|
||||||
- [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
||||||
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
||||||
|
|||||||
@@ -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)
|
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.
|
#! shallow-copies kind, payload, and span; extra storage remains shared.
|
||||||
copy_node proc(builder @mut Builder, id NodeId) NodeId ! mem.AllocError {
|
copy_node proc(builder @mut Builder, id NodeId) NodeId ! mem.AllocError {
|
||||||
node :: get_node(&builder.storage, id)
|
node :: get_node(&builder.storage, id)
|
||||||
|
|||||||
@@ -94,7 +94,13 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
|||||||
` length = length + 1
|
` length = length + 1
|
||||||
` }
|
` }
|
||||||
` return match length {
|
` 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),
|
` else: i8(128),
|
||||||
` }
|
` }
|
||||||
`}
|
`}
|
||||||
@@ -105,7 +111,7 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
|||||||
` box Box(i16) :: { value = identity(i16, values[0]) }
|
` box Box(i16) :: { value = identity(i16, values[0]) }
|
||||||
` fixed Fixed(count()) :: { values = [box.value, 2] }
|
` fixed Fixed(count()) :: { values = [box.value, 2] }
|
||||||
` same Fixed(2) :: fixed
|
` 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, .struct)
|
||||||
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
|
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, .distinct)
|
||||||
|
or next_n_is(state, 1, .match)
|
||||||
or next_n_is(state, 1, .at)
|
or next_n_is(state, 1, .at)
|
||||||
or next_n_is(state, 1, .question)
|
or next_n_is(state, 1, .question)
|
||||||
or (next_n_is(state, 1, .open_paren) and pointer_binding_starts(state))
|
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, .struct) return try parse_record_type(state)
|
||||||
if next_is(state, .enum) return try parse_enum_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, .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)
|
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
|
||||||
token :: try consume(state, .ident)
|
token :: try consume(state, .ident)
|
||||||
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
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 {
|
starts_type proc(kind lexer.TokenKind) bool {
|
||||||
return (
|
return (
|
||||||
kind == .ident or kind == .open_bracket or kind == .struct
|
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
|
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
|
@hide:file
|
||||||
array_starts_type proc(state @State) bool {
|
array_starts_type proc(state @State) bool {
|
||||||
depth usize := 0
|
depth usize := 0
|
||||||
|
curly_depth := usize(0)
|
||||||
for (state.next_token)..(state.tokens.len) |i| {
|
for (state.next_token)..(state.tokens.len) |i| {
|
||||||
kind :: state.tokens[i].kind
|
kind :: state.tokens[i].kind
|
||||||
if (kind == .open_bracket) depth += 1
|
if (kind == .open_bracket) depth += 1
|
||||||
@@ -1509,7 +1512,12 @@ array_starts_type proc(state @State) bool {
|
|||||||
return starts_type(next) or next == .mut
|
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
|
return false
|
||||||
}
|
}
|
||||||
@@ -1587,7 +1595,7 @@ named_binding_starts proc(state @State) bool {
|
|||||||
next_is(state, .ident)
|
next_is(state, .ident)
|
||||||
and (
|
and (
|
||||||
next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
|
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, .at)
|
||||||
or next_n_is(state, 1, .question)
|
or next_n_is(state, 1, .question)
|
||||||
or (next_n_is(state, 1, .open_paren) and pointer_binding_starts(state))
|
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) {
|
while (index > 0) {
|
||||||
index -= 1
|
index -= 1
|
||||||
change :: result.changes.items[index]
|
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.changes)
|
||||||
arraylist.deinit(&result.constraints)
|
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 (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
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||||
constraints_start :: state.rewrites.constraints.items.len
|
constraints_start :: state.rewrites.constraints.items.len
|
||||||
returned :: try return_type_value(state, view.body, ast.NodeId(clone_start))
|
returned :: try return_type_value(state, view.body, ast.NodeId(clone_start))
|
||||||
if (returned == no_node) {
|
if (returned == no_node) {
|
||||||
try reject_evaluation(state, call)
|
try reject_type_result(state, call, returned, constraints_start)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result := returned
|
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.instances.items[index].type = returned
|
||||||
state.bindings.items[usize(declaration)] = sema.Binding{ kind = .declaration, data = sema.BindingData{ declaration = declaration } }
|
state.bindings.items[usize(declaration)] = sema.Binding{ kind = .declaration, data = sema.BindingData{ declaration = declaration } }
|
||||||
try arraylist.append(&state.declarations, 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)
|
try expand(state, declaration)
|
||||||
constraint_count :: state.rewrites.constraints.items.len
|
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 {
|
} else {
|
||||||
runtime := arraylist.init(ast.NodeId, state.allocator)
|
runtime := arraylist.init(ast.NodeId, state.allocator)
|
||||||
defer arraylist.deinit(&runtime)
|
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 } }
|
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
|
@hide:file
|
||||||
evaluation_dependencies proc(
|
evaluation_dependencies proc(
|
||||||
state @mut State, id ast.NodeId, flags []mut u8, declarations @mut arraylist.ArrayList(ast.NodeId),
|
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 {
|
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
|
# ponytail: snapshots recheck scalar IR; cap type-control work at 4096 steps
|
||||||
# until type values can share one persistent checked execution frame.
|
# 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{
|
execution := TypeExecution{
|
||||||
locals = arraylist.init(state.allocator),
|
locals = arraylist.init(state.allocator),
|
||||||
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
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.
|
# the same rules as runtime matching, without checking unexecuted type bodies.
|
||||||
@hide:file
|
@hide:file
|
||||||
type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
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)
|
arms := arraylist.init(ast.NodeId, state.allocator)
|
||||||
defer arraylist.deinit(&arms)
|
defer arraylist.deinit(&arms)
|
||||||
patterns := arraylist.init(ast.NodeId, state.allocator)
|
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
|
return no_index
|
||||||
}
|
}
|
||||||
patterns.items = patterns.items[..0]
|
patterns.items = patterns.items[..0]
|
||||||
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j|
|
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j| {
|
||||||
try arraylist.append(&patterns, ast.match_pattern(&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)
|
span :: ast.node_span(&state.tree, arm_id)
|
||||||
index :: try ast.add_integer(state.builder, span, u64(i))
|
index :: try ast.add_integer(state.builder, span, u64(i))
|
||||||
selector :: try ast.add_match_arm(state.builder, span, patterns.items,
|
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
|
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
|
@hide:file
|
||||||
type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.NodeId ! Error {
|
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)
|
expression :: try type_value_expression(state, input)
|
||||||
if (expression == no_node) return no_node
|
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.
|
# A recursive record needs its cached declaration before expanding its fields.
|
||||||
if (is_type(state, annotation) and nominal_type_value(state, expression)) {
|
if (is_type(state, annotation) and nominal_type_value(state, expression)) {
|
||||||
concrete :: try materialize(state, Value{ kind = .type, node = 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)
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
||||||
return concrete.node
|
return concrete.node
|
||||||
}
|
}
|
||||||
|
if (!try expand_type_matches(state, expression)) return no_node
|
||||||
try expand(state, expression)
|
try expand(state, expression)
|
||||||
resolved :: binding(state, expression)
|
resolved :: binding(state, expression)
|
||||||
if (resolved.kind == .invalid) {
|
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)
|
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 (nominal_type_value(state, expression)) annotation = try builtin(state, expression, .type)
|
||||||
else {
|
else {
|
||||||
|
if (!try expand_type_matches(state, expression)) return .invalid
|
||||||
try expand(state, expression)
|
try expand(state, expression)
|
||||||
annotation = try expression_type(state, expression, 0)
|
annotation = try expression_type(state, expression, 0)
|
||||||
if (annotation == no_node) {
|
if (annotation == no_node) {
|
||||||
|
|||||||
+104
-5
@@ -11,6 +11,100 @@ HONEY = ROOT / "build/honey"
|
|||||||
|
|
||||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||||
CASES = {
|
CASES = {
|
||||||
|
"nested_type_match_failure_preserves_shared_return_types": (
|
||||||
|
"""
|
||||||
|
Original :: alias [2]i16
|
||||||
|
Broken proc() type {
|
||||||
|
Unused :: struct { value match true { true: i16 } }
|
||||||
|
return Original
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
if (false) {
|
||||||
|
bad Broken() :: [0, 0]
|
||||||
|
return bad[0]
|
||||||
|
}
|
||||||
|
good Original :: [40, 2]
|
||||||
|
return good[0] + good[1]
|
||||||
|
}
|
||||||
|
""", b"42\n", b"match is not exhaustive",
|
||||||
|
),
|
||||||
|
"nested_type_matches_in_storage_calls_and_defaults": (
|
||||||
|
"""
|
||||||
|
Box proc(T type) type { return struct { value T } }
|
||||||
|
increment proc(value i16) i16 { return value + 2 }
|
||||||
|
Storage proc(wide bool) type {
|
||||||
|
return struct {
|
||||||
|
values [match wide { true: usize(2), false: usize(1) }]mut (match wide { true: i16, false: i8 })
|
||||||
|
boxed Box(match wide { true: i16, false: i8 })
|
||||||
|
callback @proc(value match wide { true: i16, false: i8 }) match wide { true: i16, false: i8 }
|
||||||
|
bias i16 = match (match wide { true: true, false: false }) { true: 0, false: i8(128) }
|
||||||
|
enabled bool = match wide { true: true, false: false }
|
||||||
|
label []u8 = match wide { true: "ok", false: "bad" }
|
||||||
|
fraction f64 = match wide { true: 0.5, false: 0.0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
value Storage(true) :: { values = [20, 20], boxed = { value = 0 }, callback = increment }
|
||||||
|
cached Storage(!false) :: value
|
||||||
|
if (!cached.enabled or cached.label[0] != 111 or cached.fraction != 0.5) return -1
|
||||||
|
return cached.callback(cached.values[0] + cached.values[1]) + cached.boxed.value + cached.bias
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"nested_type_matches_preserve_recursive_nominal_identity": (
|
||||||
|
"""
|
||||||
|
Node proc(wide bool) type {
|
||||||
|
return struct {
|
||||||
|
value match wide { true: i16, false: i8 }
|
||||||
|
next ?@match wide { true: Node(wide), false: bool } = match wide { true: null, false: null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
leaf Node(true) :: { value = 40 }
|
||||||
|
head Node(!false) :: { value = 2, next = &leaf }
|
||||||
|
small Node(false) :: { value = 0 }
|
||||||
|
return head.value + head.next?.value + i16(small.value)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"nested_type_matches_retain_loop_snapshots": (
|
||||||
|
"""
|
||||||
|
Nest proc(depth usize) type {
|
||||||
|
Result match true { true: type, false: i8 } := i16
|
||||||
|
count match true { true: usize, false: i8 } := depth
|
||||||
|
for (0..count) |index| {
|
||||||
|
Result = [match index { 0: usize(2), else: usize(1) }](match index {
|
||||||
|
0: Result,
|
||||||
|
else: match true { true: Result, false: i8(128) },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Result
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
values Nest(2) :: [[40, 2]]
|
||||||
|
return values[0][0] + values[0][1]
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"nested_type_matches_check_patterns_before_selecting": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
return struct { value [1](match true { true: i16 }) }
|
||||||
|
}
|
||||||
|
main proc() Bad() { return {} }
|
||||||
|
""", b"", b"match is not exhaustive",
|
||||||
|
),
|
||||||
|
"nested_type_matches_check_selected_defaults": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
return struct { value i8 = match true { true: 128, false: 0 } }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
value Bad() :: {}
|
||||||
|
return i16(value.value)
|
||||||
|
}
|
||||||
|
""", b"", b"numeric literal is outside",
|
||||||
|
),
|
||||||
"type_match_enum_storage_and_cache_identity": (
|
"type_match_enum_storage_and_cache_identity": (
|
||||||
"""
|
"""
|
||||||
Mode :: enum { tiny, pair, wide }
|
Mode :: enum { tiny, pair, wide }
|
||||||
@@ -161,9 +255,12 @@ CASES = {
|
|||||||
"type_match_resolves_untaken_bodies": (
|
"type_match_resolves_untaken_bodies": (
|
||||||
"""
|
"""
|
||||||
Bad proc() type {
|
Bad proc() type {
|
||||||
return match true { true: i16, false: missing }
|
return struct { value match true { true: i16, false: missing } }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
value Bad() :: { value = 42 }
|
||||||
|
return value.value
|
||||||
}
|
}
|
||||||
main proc() Bad() { return 42 }
|
|
||||||
""", b"42\n", b"unknown name",
|
""", b"42\n", b"unknown name",
|
||||||
),
|
),
|
||||||
"type_match_remains_bounded": (
|
"type_match_remains_bounded": (
|
||||||
@@ -3401,7 +3498,9 @@ def check_specialization_packages(temporary, executable):
|
|||||||
app.mkdir(parents=True)
|
app.mkdir(parents=True)
|
||||||
library.mkdir()
|
library.mkdir()
|
||||||
(library / "types.hon").write_text(textwrap.dedent("""
|
(library / "types.hon").write_text(textwrap.dedent("""
|
||||||
Box proc(T type) type { return struct { value T } }
|
Box proc(T type) type {
|
||||||
|
return struct { value match true { true: T, false: i8(128) } }
|
||||||
|
}
|
||||||
Selected proc(wide bool) type {
|
Selected proc(wide bool) type {
|
||||||
return match wide { true: i16, false: u8 }
|
return match wide { true: i16, false: u8 }
|
||||||
}
|
}
|
||||||
@@ -3412,7 +3511,7 @@ def check_specialization_packages(temporary, executable):
|
|||||||
Element = T
|
Element = T
|
||||||
length = length + 1
|
length = length + 1
|
||||||
}
|
}
|
||||||
return struct { values [length]mut Element }
|
return struct { values [match length { 0: usize(0), else: length }]mut Element }
|
||||||
}
|
}
|
||||||
""").strip() + "\n")
|
""").strip() + "\n")
|
||||||
(library / "functions.hon").write_text(textwrap.dedent("""
|
(library / "functions.hon").write_text(textwrap.dedent("""
|
||||||
@@ -3472,7 +3571,7 @@ def check_specialization_packages(temporary, executable):
|
|||||||
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
|
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
|
||||||
sizing.write_text(textwrap.dedent("""
|
sizing.write_text(textwrap.dedent("""
|
||||||
Bad proc() type {
|
Bad proc() type {
|
||||||
return match true { true: i16, true: i8, else: bool }
|
return struct { value match true { true: i16, true: i8, else: bool } }
|
||||||
}
|
}
|
||||||
""").strip() + "\n")
|
""").strip() + "\n")
|
||||||
source.write_text(textwrap.dedent("""
|
source.write_text(textwrap.dedent("""
|
||||||
|
|||||||
Reference in New Issue
Block a user