feat: infer compile-time metadata locals
This commit is contained in:
@@ -35,7 +35,7 @@ main proc() int {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
this prints `42`. both interpreted and Honey-compiled `main` must be a top-level function with no parameters; nonvoid results are printed, not used as process exit statuses.
|
this prints `42`. both interpreted and Honey-compiled `main` must be a top-level runtime function with no parameters; compile-time-only procedures cannot be entry points. nonvoid results are printed, not used as process exit statuses.
|
||||||
|
|
||||||
compile the same supported language subset to a standalone Apple Silicon macOS executable:
|
compile the same supported language subset to a standalone Apple Silicon macOS executable:
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ procedures returning fixed arrays or records/tuples containing `type`, such as `
|
|||||||
|
|
||||||
ordinary parameters containing `type` also make a procedure compile-time-only, even when its result is a runtime value. for example, `count proc(types []type) usize { return types.len }` computes `count([i16, bool])` without runtime metadata. every input must be explicitly supplied and constant; use `$` parameters when later signature annotations depend on them. helpers can return owned scalar, aggregate and string values, recurse over metadata and use checked `try`/`catch` with scoped cleanup. plain and fallible `void` support bare returns and successful fallthrough. ordinary runtime dependencies and incomplete metadata arguments remain diagnostic; mutable metadata borrowing and general type-valued execution remain open.
|
ordinary parameters containing `type` also make a procedure compile-time-only, even when its result is a runtime value. for example, `count proc(types []type) usize { return types.len }` computes `count([i16, bool])` without runtime metadata. every input must be explicitly supplied and constant; use `$` parameters when later signature annotations depend on them. helpers can return owned scalar, aggregate and string values, recurse over metadata and use checked `try`/`catch` with scoped cleanup. plain and fallible `void` support bare returns and successful fallthrough. ordinary runtime dependencies and incomplete metadata arguments remain diagnostic; mutable metadata borrowing and general type-valued execution remain open.
|
||||||
|
|
||||||
explicit local annotations containing `type`, such as `T type := i16`, also make a procedure compile-time-only, even with ordinary scalar inputs and results. calls evaluate with owned local metadata and are erased. nested metadata records/arrays, partial initialization, checked updates, loops, recursion and scoped cleanup reuse type-control execution and its shared 4096-step bound. every input remains explicitly supplied and constant; this is not partial evaluation of runtime inputs. ordinary local type aliases without metadata storage annotations keep their runtime-call contract. inferred metadata-only locals and mutable metadata borrowing remain open.
|
local metadata storage also makes a procedure compile-time-only, even with ordinary scalar inputs and results. explicit annotations (`T type := i16`) and inferred mutable type bindings (`T := i16`), metadata array/record/tuple literals, helper results, checked projections and yielding blocks use owned snapshots and erased calls. nested storage, partial initialization, checked updates, loops, recursion and scoped cleanup reuse type-control execution and its shared 4096-step bound. inferred arrays retain readonly elements unless their type explicitly permits element updates; whole-value replacement remains available for mutable bindings. every input remains explicitly supplied and constant; this is not partial evaluation of runtime inputs. ordinary local `alias` declarations and scalar projections from metadata helpers keep their runtime-call contract. mixed runtime/type-valued execution and mutable metadata borrowing remain open.
|
||||||
|
|
||||||
readonly type-valued slices (`[]type` and slices of metadata records/arrays) also cross compile-time procedure boundaries as owned snapshots. contextual array literals, including `[]`, construct them; explicit views of immutable readonly arrays and existing metadata slices support checked `usize` bounds, empty end views, indexing and `.len`. nested slices, ordinary/inline by-value iteration, erased `$` inputs and optional/fallible channels retain exact nominal leaves and content-based specialization identity. snapshots generate runtime types/defaults, but metadata itself has no runtime storage. mutable slice types, sentinels and views of mutable array places remain rejected rather than silently losing borrowing semantics; copy an array into an immutable binding before taking an owned view. construction shares the type-control step bound, and uninitialized leaves cannot cross procedure boundaries.
|
readonly type-valued slices (`[]type` and slices of metadata records/arrays) also cross compile-time procedure boundaries as owned snapshots. contextual array literals, including `[]`, construct them; explicit views of immutable readonly arrays and existing metadata slices support checked `usize` bounds, empty end views, indexing and `.len`. nested slices, ordinary/inline by-value iteration, erased `$` inputs and optional/fallible channels retain exact nominal leaves and content-based specialization identity. snapshots generate runtime types/defaults, but metadata itself has no runtime storage. mutable slice types, sentinels and views of mutable array places remain rejected rather than silently losing borrowing semantics; copy an array into an immutable binding before taking an owned view. construction shares the type-control step bound, and uninitialized leaves cannot cross procedure boundaries.
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ checked immutable-array/slice views, by-value iteration and erased arguments;
|
|||||||
mutable metadata borrowing remains open.
|
mutable metadata borrowing remains open.
|
||||||
ordinary metadata-input procedures also compute owned runtime values, including
|
ordinary metadata-input procedures also compute owned runtime values, including
|
||||||
reflected field names/counts, recursive helpers and fallible cleanup.
|
reflected field names/counts, recursive helpers and fallible cleanup.
|
||||||
explicitly annotated local metadata storage also makes ordinary procedures
|
explicit and inferred local metadata storage also make ordinary procedures
|
||||||
constant-input producers, preserving owned updates, loops, recursion and cleanup.
|
constant-input producers, preserving owned updates, loops, recursion and cleanup.
|
||||||
`field!` selects record/tuple/enum and sequence members using compile-time names,
|
`field!` selects record/tuple/enum and sequence members using compile-time names,
|
||||||
including runtime places and type-control updates without runtime reflection.
|
including runtime places and type-control updates without runtime reflection.
|
||||||
@@ -174,7 +174,8 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [x] readonly type-slice checkpoint: `[]type` and slices of type-valued records/arrays use owned compile-time snapshots. contextual literals, immutable readonly array views, nested slice views, checked `usize` bounds/indexing, `.len`, empty shapes and ordinary/inline by-value iteration preserve exact nominal leaves. constant-input procedure parameters/results, erased generic inputs, optional/fallible nesting and normalized cache keys generate runtime storage/defaults through interpreter/native execution. mutable annotations/backing, metadata sentinels, invalid ranges/indices, readonly writes, incomplete leaves and runtime dependencies retain diagnostics. imported private types, source/session disposal, shared construction bounds and repeat compilation preserve ownership. mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open.
|
- [x] readonly type-slice checkpoint: `[]type` and slices of type-valued records/arrays use owned compile-time snapshots. contextual literals, immutable readonly array views, nested slice views, checked `usize` bounds/indexing, `.len`, empty shapes and ordinary/inline by-value iteration preserve exact nominal leaves. constant-input procedure parameters/results, erased generic inputs, optional/fallible nesting and normalized cache keys generate runtime storage/defaults through interpreter/native execution. mutable annotations/backing, metadata sentinels, invalid ranges/indices, readonly writes, incomplete leaves and runtime dependencies retain diagnostics. imported private types, source/session disposal, shared construction bounds and repeat compilation preserve ownership. mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open.
|
||||||
- [x] tagged type-storage checkpoint: tagged unions containing type leaves own active payload snapshots through typed/contextual construction, direct and nested field/index updates, immutable by-value matches and whole-value variant replacement. constant-input procedure arguments/results, erased generic inputs and normalized cache keys preserve nominal leaves and saved copies while generating runtime storage/defaults through interpreter/native execution. undefined union locals and nested record/array storage require whole-value initialization; inactive reads/writes, malformed explicit tags, nominal initializer conflicts, readonly updates, incomplete results and narrow overflow retain diagnostics. explicit sparse tag enums may contain unused members without adding match cases. imported private types, source/session disposal and repeat compilation preserve ownership. untagged metadata layouts remain invalid; mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open.
|
- [x] tagged type-storage checkpoint: tagged unions containing type leaves own active payload snapshots through typed/contextual construction, direct and nested field/index updates, immutable by-value matches and whole-value variant replacement. constant-input procedure arguments/results, erased generic inputs and normalized cache keys preserve nominal leaves and saved copies while generating runtime storage/defaults through interpreter/native execution. undefined union locals and nested record/array storage require whole-value initialization; inactive reads/writes, malformed explicit tags, nominal initializer conflicts, readonly updates, incomplete results and narrow overflow retain diagnostics. explicit sparse tag enums may contain unused members without adding match cases. imported private types, source/session disposal and repeat compilation preserve ownership. untagged metadata layouts remain invalid; mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open.
|
||||||
- [x] metadata-input execution checkpoint: procedures with ordinary type-valued parameters execute entirely at compile time even when returning runtime-representable values. all inputs remain explicit constants; owned scalar/record/array/string results, recursive readonly metadata iteration, reflected field names/counts and private nominal leaves work through interpreter/native execution. plain/fallible `void` retain bare-return/fallthrough semantics; ordinary fallible results propagate through `try`/`catch` and run scoped `defer`/`errdefer`. runtime dependencies and incomplete payloads retain diagnostics; source/session disposal and repeat compilation preserve ownership. dependent signature parameters still require `$`; general type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
- [x] metadata-input execution checkpoint: procedures with ordinary type-valued parameters execute entirely at compile time even when returning runtime-representable values. all inputs remain explicit constants; owned scalar/record/array/string results, recursive readonly metadata iteration, reflected field names/counts and private nominal leaves work through interpreter/native execution. plain/fallible `void` retain bare-return/fallthrough semantics; ordinary fallible results propagate through `try`/`catch` and run scoped `defer`/`errdefer`. runtime dependencies and incomplete payloads retain diagnostics; source/session disposal and repeat compilation preserve ownership. dependent signature parameters still require `$`; general type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
||||||
- [x] explicit metadata-local execution checkpoint: procedures with explicitly annotated type-valued locals execute entirely at compile time without metadata inputs or results. scalar inputs remain explicit constants; nested metadata records/arrays, undefined initialization, owned copies, checked field/index updates, loops, captures, recursion and scoped fallible cleanup reuse existing type-control execution through interpreter/native paths. runtime inputs and uninitialized leaves diagnose; ordinary unannotated local type aliases retain runtime parameters. imported private nominal leaves, source/session disposal and repeat compilation preserve ownership. inferred metadata-only locals, mixed runtime/type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
- [x] explicit metadata-local execution checkpoint: procedures with explicitly annotated type-valued locals execute entirely at compile time without metadata inputs or results. scalar inputs remain explicit constants; nested metadata records/arrays, undefined initialization, owned copies, checked field/index updates, loops, captures, recursion and scoped fallible cleanup reuse existing type-control execution through interpreter/native paths. runtime inputs and uninitialized leaves diagnose; ordinary unannotated local type aliases retain runtime parameters. imported private nominal leaves, source/session disposal and repeat compilation preserve ownership. inferred metadata-only locals are covered below; mixed runtime/type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
||||||
|
- [x] inferred metadata-local execution checkpoint: inferred mutable type bindings and metadata array/record/tuple locals select fully constant-input execution without metadata inputs, results or local annotations. literal/helper results, checked field/index/optional projections, yielding blocks and conditional/fallible selection preserve owned copies, nominal leaves, whole-value replacement, loops, recursion and scoped cleanup through interpreter/native execution. inferred array elements remain readonly; runtime inputs diagnose. ordinary local aliases and scalar metadata projections retain runtime parameters. ordinary fallible success/error snapshots use checked channel discrimination rather than assuming every payload carries a nominal type. imported private types, source/session disposal and repeat compilation preserve ownership. erased compile-time-only procedures cannot become runtime entries. mixed runtime/type-valued execution, mutable metadata borrowing and broader materialization remain open.
|
||||||
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates.
|
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates.
|
||||||
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. 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.
|
||||||
|
|||||||
@@ -1470,7 +1470,7 @@ tagged_type_metadata_recompiles_owned_private_nominal_payloads test {
|
|||||||
` return i16(saved.payload.count) + bias
|
` return i16(saved.payload.count) + bias
|
||||||
`}
|
`}
|
||||||
`score_local proc(bias i16) i16 {
|
`score_local proc(bias i16) i16 {
|
||||||
` config Config := { children = [Child, null], count = 2 }
|
` config := Config { children = [Child, null], count = 2 }
|
||||||
` saved :: config
|
` saved :: config
|
||||||
` config.children[0] = bool
|
` config.children[0] = bool
|
||||||
` if (saved.children[0]? != Child) return -1
|
` if (saved.children[0]? != Child) return -1
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
recursive_record = "record has a recursive by-value layout",
|
recursive_record = "record has a recursive by-value layout",
|
||||||
invalid_record_field_type = "record fields require a concrete value type",
|
invalid_record_field_type = "record fields require a concrete value type",
|
||||||
missing_entry = "program requires a main function",
|
missing_entry = "program requires a main function",
|
||||||
invalid_entry = "main must be a function with no parameters",
|
invalid_entry = "main must be a runtime function with no parameters",
|
||||||
literal_out_of_range = "numeric literal is outside its runtime type's range",
|
literal_out_of_range = "numeric literal is outside its runtime type's range",
|
||||||
inexact_integer_literal = "integer literal is not exactly representable as float; use a floating literal to allow rounding",
|
inexact_integer_literal = "integer literal is not exactly representable as float; use a floating literal to allow rounding",
|
||||||
invalid_escape = "invalid string escape",
|
invalid_escape = "invalid string escape",
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ find_entry proc(state @mut State, root_package ast.NodeId) void ! (mem.AllocErro
|
|||||||
.import, .invalid: continue
|
.import, .invalid: continue
|
||||||
}
|
}
|
||||||
if (!mem.eql(ast.source_text(tree, name), "main")) continue
|
if (!mem.eql(ast.source_text(tree, name), "main")) continue
|
||||||
if (ast.decl_kind(tree, id) != .function) {
|
if (ast.decl_kind(tree, id) != .function or state.storage[usize(id)].kind != .function) {
|
||||||
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -664,7 +664,7 @@ expand_expected proc(state @mut State, id, expected ast.NodeId) void ! Error {
|
|||||||
) {
|
) {
|
||||||
for (0..ast.record_field_count(&state.tree, id)) |i| {
|
for (0..ast.record_field_count(&state.tree, id)) |i| {
|
||||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
|
||||||
slot :: if (field.name != null) type_record_slot(state, shape, field.name?) else i
|
slot :: if (field.name != null) type_record_slot(&state.tree, shape, field.name?) else i
|
||||||
field_type := no_node
|
field_type := no_node
|
||||||
if (slot < ast.record_field_count(&state.tree, shape)) {
|
if (slot < ast.record_field_count(&state.tree, shape)) {
|
||||||
if (ast.field(&state.tree, ast.record_field(&state.tree, shape, slot)).type) |type|
|
if (ast.field(&state.tree, ast.record_field(&state.tree, shape, slot)).type) |type|
|
||||||
@@ -1688,7 +1688,7 @@ specialize_match proc(state @mut State, id, statement, expected ast.NodeId) bool
|
|||||||
if (ast.expr_kind(&state.tree, annotation) != .union_type) break
|
if (ast.expr_kind(&state.tree, annotation) != .union_type) break
|
||||||
pattern :: ast.match_pattern(&state.tree, arm_id, 0)
|
pattern :: ast.match_pattern(&state.tree, arm_id, 0)
|
||||||
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) continue
|
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) continue
|
||||||
slot :: type_record_slot(state, annotation, ast.enum_literal(&state.tree, pattern))
|
slot :: type_record_slot(&state.tree, annotation, ast.enum_literal(&state.tree, pattern))
|
||||||
if (slot == no_index) continue
|
if (slot == no_index) continue
|
||||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot))
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot))
|
||||||
if (field.type) |type| payload = type
|
if (field.type) |type| payload = type
|
||||||
@@ -1860,8 +1860,181 @@ type_storage_annotation proc(tree @ast.Tree, bindings []sema.Binding, input ast.
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
# explicit metadata locals have no runtime storage, just like metadata parameters.
|
@hide:file
|
||||||
# do not classify ordinary local type aliases as constant-input procedures.
|
storage_schema proc(tree @ast.Tree, bindings []sema.Binding, input ast.NodeId, depth usize) ast.NodeId {
|
||||||
|
if (depth >= 128 or usize(input) >= bindings.len) return no_node
|
||||||
|
resolved :: bindings[usize(input)]
|
||||||
|
if (resolved.kind == .declaration) {
|
||||||
|
target :: resolved.data.declaration
|
||||||
|
if (ast.node_class(tree, target) == .function) return target
|
||||||
|
if (ast.node_class(tree, target) == .statement)
|
||||||
|
return storage_schema(tree, bindings, ast.statement_decl(tree, target).initializer, depth + 1)
|
||||||
|
}
|
||||||
|
if (ast.expr_kind(tree, input) == .alias_expr)
|
||||||
|
return storage_schema(tree, bindings, ast.type_operand(tree, input), depth + 1)
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
# read only existing annotations: discovery must not specialize recursive calls.
|
||||||
|
@hide:file
|
||||||
|
storage_expression_annotation proc(
|
||||||
|
tree @ast.Tree,
|
||||||
|
bindings []sema.Binding,
|
||||||
|
input ast.NodeId,
|
||||||
|
depth usize,
|
||||||
|
) ast.NodeId {
|
||||||
|
if (depth >= 128 or usize(input) >= bindings.len) return no_node
|
||||||
|
resolved :: bindings[usize(input)]
|
||||||
|
if (resolved.kind == .declaration) {
|
||||||
|
target :: resolved.data.declaration
|
||||||
|
class :: ast.node_class(tree, target)
|
||||||
|
if (class == .parameter) return ast.param(tree, target).type
|
||||||
|
if (class == .statement) {
|
||||||
|
declaration :: ast.statement_decl(tree, target)
|
||||||
|
if (declaration.type) |annotation| return annotation
|
||||||
|
return storage_expression_annotation(tree, bindings, declaration.initializer, depth + 1)
|
||||||
|
}
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
kind :: ast.expr_kind(tree, input)
|
||||||
|
if (kind == .typed_constant) return ast.typed_constant(tree, input).type
|
||||||
|
if (kind == .union_constant) return ast.union_constant(tree, input).type
|
||||||
|
if (kind == .slice_constant) return ast.slice_constant(tree, input).type
|
||||||
|
if (kind == .record_literal) {
|
||||||
|
if (ast.record_literal(tree, input).type) |type| return type
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
if (kind == .call) {
|
||||||
|
callee :: storage_schema(tree, bindings, ast.call_expr(tree, input).callee, depth + 1)
|
||||||
|
if (callee != no_node and ast.node_class(tree, callee) == .function)
|
||||||
|
return ast.function_decl(tree, callee).return_type
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
base := no_node
|
||||||
|
if (kind == .selection) base = ast.selection_expr(tree, input).base
|
||||||
|
else if (kind == .index) base = ast.index_expr(tree, input).base
|
||||||
|
else if (kind == .slice) base = ast.slice_expr(tree, input).base
|
||||||
|
else if (kind == .unary) base = ast.unary_expr(tree, input).operand
|
||||||
|
else if (kind == .try_expr) base = ast.type_operand(tree, input)
|
||||||
|
else if (kind == .catch_expr) base = ast.catch_expr(tree, input).operand
|
||||||
|
else if (kind == .comptime_expr) return storage_expression_annotation(tree, bindings,
|
||||||
|
ast.comptime_operand(tree, input), depth + 1)
|
||||||
|
if (base == no_node) return no_node
|
||||||
|
annotation :: storage_schema(tree, bindings,
|
||||||
|
storage_expression_annotation(tree, bindings, base, depth + 1), depth + 1)
|
||||||
|
if (annotation == no_node) return no_node
|
||||||
|
shape :: ast.expr_kind(tree, annotation)
|
||||||
|
if (kind == .slice) return annotation
|
||||||
|
if (kind == .try_expr or kind == .catch_expr)
|
||||||
|
return if (shape == .fallible_type) ast.binary_expr(tree, annotation).left else no_node
|
||||||
|
if (kind == .unary)
|
||||||
|
return if (shape == .optional_type and ast.unary_expr(tree, input).operator == .unwrap)
|
||||||
|
ast.type_operand(tree, annotation) else no_node
|
||||||
|
if (kind == .index) {
|
||||||
|
if (shape == .array_type) return ast.array_type(tree, annotation).element
|
||||||
|
if (shape == .slice_type) return ast.slice_type(tree, annotation).element
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
if (shape != .record_type and shape != .union_type) return no_node
|
||||||
|
member :: ast.selection_expr(tree, input).member
|
||||||
|
slot :: type_record_slot(tree, annotation, member)
|
||||||
|
if (slot == no_index) return no_node
|
||||||
|
if (ast.field(tree, ast.record_field(tree, annotation, slot)).type) |type| return type
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
type_storage_yields proc(
|
||||||
|
tree @ast.Tree,
|
||||||
|
bindings []sema.Binding,
|
||||||
|
id ast.NodeId,
|
||||||
|
scalar_type bool,
|
||||||
|
depth usize,
|
||||||
|
) bool {
|
||||||
|
if (depth >= 128) return false
|
||||||
|
class :: ast.node_class(tree, id)
|
||||||
|
if (class != .block and class != .statement) return false
|
||||||
|
if (class == .statement and ast.stmt_kind(tree, id) == .yield_stmt) {
|
||||||
|
value :: ast.yield_stmt(tree, id).value
|
||||||
|
return value != null and type_storage_initializer(tree, bindings, value?, scalar_type, depth + 1)
|
||||||
|
}
|
||||||
|
for (0..ast.child_count(tree, id)) |i| {
|
||||||
|
if (type_storage_yields(tree, bindings, ast.child(tree, id, i), scalar_type, depth + 1)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
# discover inferred storage without elaborating calls or executing initializers.
|
||||||
|
# immutable scalar type bindings remain aliases in ordinary runtime procedures.
|
||||||
|
@hide:file
|
||||||
|
type_storage_initializer proc(
|
||||||
|
tree @ast.Tree, bindings []sema.Binding, input ast.NodeId, scalar_type bool, depth usize,
|
||||||
|
) bool {
|
||||||
|
if (depth >= 128 or usize(input) >= bindings.len) return false
|
||||||
|
annotation :: storage_schema(tree, bindings,
|
||||||
|
storage_expression_annotation(tree, bindings, input, depth + 1), depth + 1)
|
||||||
|
if (annotation != no_node) {
|
||||||
|
if (!scalar_type and bindings[usize(annotation)].kind == .builtin
|
||||||
|
and bindings[usize(annotation)].data.builtin == .type) return false
|
||||||
|
return type_storage_annotation(tree, bindings, annotation, depth + 1)
|
||||||
|
}
|
||||||
|
resolved :: bindings[usize(input)]
|
||||||
|
if (resolved.kind == .builtin) return scalar_type
|
||||||
|
if (resolved.kind == .declaration) {
|
||||||
|
target :: resolved.data.declaration
|
||||||
|
class :: ast.node_class(tree, target)
|
||||||
|
if (class == .statement) {
|
||||||
|
declaration :: ast.statement_decl(tree, target)
|
||||||
|
if (declaration.type != null) return false
|
||||||
|
return type_storage_initializer(tree, bindings, declaration.initializer, scalar_type, depth + 1)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
kind :: ast.expr_kind(tree, input)
|
||||||
|
match kind {
|
||||||
|
.record_type, .array_type, .slice_type, .pointer_type, .function_type, .optional_type,
|
||||||
|
.enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type, .alias_expr:
|
||||||
|
return scalar_type
|
||||||
|
.record_literal: {
|
||||||
|
for (0..ast.record_field_count(tree, input)) |i| {
|
||||||
|
field :: ast.field(tree, ast.record_field(tree, input, i))
|
||||||
|
if (field.value != null
|
||||||
|
and type_storage_initializer(tree, bindings, field.value?, true, depth + 1)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
.array_literal: {
|
||||||
|
for (0..ast.array_element_count(tree, input)) |i| {
|
||||||
|
if (type_storage_initializer(tree, bindings, ast.array_element(tree, input, i), true, depth + 1))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
.comptime_expr:
|
||||||
|
return type_storage_initializer(tree, bindings, ast.comptime_operand(tree, input), scalar_type, depth + 1)
|
||||||
|
.match_expr: {
|
||||||
|
for (0..ast.match_arm_count(tree, input)) |i| {
|
||||||
|
arm :: ast.match_arm_view(tree, ast.match_arm(tree, input, i))
|
||||||
|
if (arm.expression) {
|
||||||
|
if (type_storage_initializer(tree, bindings, arm.body, scalar_type, depth + 1)) return true
|
||||||
|
} else if (type_storage_yields(tree, bindings, arm.body, scalar_type, depth + 1)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
.block_expr:
|
||||||
|
return type_storage_yields(tree, bindings, ast.block_expr(tree, input), scalar_type, depth + 1)
|
||||||
|
.builtin_call: {
|
||||||
|
name :: ast.literal_text(tree, ast.builtin_call(tree, input).name)
|
||||||
|
if (mem.eql(name, "typeinfo")) return true
|
||||||
|
if (mem.eql(name, "struct_type")) return scalar_type
|
||||||
|
if (mem.eql(name, "some") and ast.call_arg_count(tree, input) == 1)
|
||||||
|
return type_storage_initializer(tree, bindings, ast.call_arg(tree, input, 0), true, depth + 1)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
else: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
type_local_storage proc(tree @ast.Tree, bindings []sema.Binding, id ast.NodeId, depth usize) bool {
|
type_local_storage proc(tree @ast.Tree, bindings []sema.Binding, id ast.NodeId, depth usize) bool {
|
||||||
if (depth >= 128) return false
|
if (depth >= 128) return false
|
||||||
@@ -1870,6 +2043,8 @@ type_local_storage proc(tree @ast.Tree, bindings []sema.Binding, id ast.NodeId,
|
|||||||
if (declaration.type) |annotation| {
|
if (declaration.type) |annotation| {
|
||||||
if (type_storage_annotation(tree, bindings, annotation, 0)) return true
|
if (type_storage_annotation(tree, bindings, annotation, 0)) return true
|
||||||
}
|
}
|
||||||
|
else if (type_storage_initializer(tree, bindings, declaration.initializer,
|
||||||
|
declaration.mutability == .variable, 0)) return true
|
||||||
}
|
}
|
||||||
for (0..ast.child_count(tree, id)) |i| {
|
for (0..ast.child_count(tree, id)) |i| {
|
||||||
if (type_local_storage(tree, bindings, ast.child(tree, id, i), depth + 1)) return true
|
if (type_local_storage(tree, bindings, ast.child(tree, id, i), depth + 1)) return true
|
||||||
@@ -1939,7 +2114,7 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
|
|||||||
state.depth += 1
|
state.depth += 1
|
||||||
defer state.depth -= 1
|
defer state.depth -= 1
|
||||||
count :: ast.function_param_count(&state.tree, function)
|
count :: ast.function_param_count(&state.tree, function)
|
||||||
# metadata inputs, results and explicit locals require a fully constant call.
|
# metadata inputs, results and locals require a fully constant call.
|
||||||
type_result :: compiletime_call(state, function)
|
type_result :: compiletime_call(state, function)
|
||||||
runtime := usize(0)
|
runtime := usize(0)
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
@@ -3331,9 +3506,41 @@ type_snapshot_expected proc(
|
|||||||
return try type_snapshot(state, execution, id)
|
return try type_snapshot(state, execution, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
type_fallible_failed proc(
|
||||||
|
state @mut State,
|
||||||
|
owned, type, at ast.NodeId,
|
||||||
|
) ?bool ! Error {
|
||||||
|
missing ?bool :: null
|
||||||
|
if (type_record_storage(state, type, 0)) {
|
||||||
|
if (!try type_array_initialized(state, owned, at)) return missing
|
||||||
|
return !try same_type(state,
|
||||||
|
ast.typed_constant(&state.tree, ast.typed_constant(&state.tree, owned).value).type,
|
||||||
|
ast.binary_expr(&state.tree, type).left)
|
||||||
|
}
|
||||||
|
span :: ast.node_span(&state.tree, at)
|
||||||
|
boolean :: try builtin(state, at, .bool)
|
||||||
|
yes :: try ast.add_boolean(state.builder, span, true)
|
||||||
|
no :: try ast.add_boolean(state.builder, span, false)
|
||||||
|
returned :: try ast.add_return_stmt(state.builder, span, yes)
|
||||||
|
failure_statements [1]ast.NodeId :: [returned]
|
||||||
|
handler :: try ast.add_block(state.builder, span, failure_statements[..])
|
||||||
|
caught :: try ast.add_catch_expr(state.builder, span, owned, null, handler, true)
|
||||||
|
discarded :: try ast.add_expression_stmt(state.builder, caught)
|
||||||
|
success :: try ast.add_return_stmt(state.builder, span, no)
|
||||||
|
statements [2]ast.NodeId :: [discarded, success]
|
||||||
|
body :: try ast.add_block(state.builder, span, statements[..])
|
||||||
|
try refresh(state)
|
||||||
|
guard :: try evaluate_ir_body(state, at, body, boolean)
|
||||||
|
if (guard.kind != .boolean) return missing
|
||||||
|
return guard.boolean
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
type_snapshot_catch proc(
|
type_snapshot_catch proc(
|
||||||
state @mut State, execution @mut TypeExecution, id, annotation ast.NodeId,
|
state @mut State,
|
||||||
|
execution @mut TypeExecution,
|
||||||
|
id, annotation ast.NodeId,
|
||||||
) ast.NodeId ! Error {
|
) ast.NodeId ! Error {
|
||||||
view :: ast.catch_expr(&state.tree, id)
|
view :: ast.catch_expr(&state.tree, id)
|
||||||
operand :: try type_snapshot(state, execution, view.operand)
|
operand :: try type_snapshot(state, execution, view.operand)
|
||||||
@@ -3344,29 +3551,9 @@ type_snapshot_catch proc(
|
|||||||
channels :: ast.binary_expr(&state.tree, operand_type)
|
channels :: ast.binary_expr(&state.tree, operand_type)
|
||||||
owned :: try type_execution_value(state, operand, operand_type)
|
owned :: try type_execution_value(state, operand, operand_type)
|
||||||
if (owned == no_node) return no_node
|
if (owned == no_node) return no_node
|
||||||
span :: ast.node_span(&state.tree, id)
|
failure :: try type_fallible_failed(state, owned, operand_type, id)
|
||||||
failed := false
|
if (failure == null) return no_node
|
||||||
if (type_record_storage(state, operand_type, 0)) {
|
failed :: failure?
|
||||||
if (!try type_array_initialized(state, owned, id)) return no_node
|
|
||||||
failed = !try same_type(state,
|
|
||||||
ast.typed_constant(&state.tree, ast.typed_constant(&state.tree, owned).value).type, channels.left)
|
|
||||||
} else {
|
|
||||||
boolean :: try builtin(state, id, .bool)
|
|
||||||
yes :: try ast.add_boolean(state.builder, span, true)
|
|
||||||
no :: try ast.add_boolean(state.builder, span, false)
|
|
||||||
returned :: try ast.add_return_stmt(state.builder, span, yes)
|
|
||||||
failure_statements [1]ast.NodeId :: [returned]
|
|
||||||
handler :: try ast.add_block(state.builder, span, failure_statements[..])
|
|
||||||
caught :: try ast.add_catch_expr(state.builder, span, owned, null, handler, true)
|
|
||||||
discarded :: try ast.add_expression_stmt(state.builder, caught)
|
|
||||||
success :: try ast.add_return_stmt(state.builder, span, no)
|
|
||||||
statements [2]ast.NodeId :: [discarded, success]
|
|
||||||
body :: try ast.add_block(state.builder, span, statements[..])
|
|
||||||
try refresh(state)
|
|
||||||
guard :: try evaluate_ir_body(state, id, body, boolean)
|
|
||||||
if (guard.kind != .boolean) return no_node
|
|
||||||
failed = guard.boolean
|
|
||||||
}
|
|
||||||
payload :: ast.typed_constant(&state.tree, owned).value
|
payload :: ast.typed_constant(&state.tree, owned).value
|
||||||
if (!failed) return try type_execution_value(state, payload, channels.left)
|
if (!failed) return try type_execution_value(state, payload, channels.left)
|
||||||
saved :: execution.locals.items.len
|
saved :: execution.locals.items.len
|
||||||
@@ -3791,7 +3978,7 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
shape :: try inline_match_shape(state, annotation)
|
shape :: try inline_match_shape(state, annotation)
|
||||||
slot :: type_record_slot(state, shape, ast.enum_literal(&state.tree, pattern))
|
slot :: type_record_slot(&state.tree, shape, ast.enum_literal(&state.tree, pattern))
|
||||||
if (slot == no_index) {
|
if (slot == no_index) {
|
||||||
valid = false
|
valid = false
|
||||||
continue
|
continue
|
||||||
@@ -4002,7 +4189,7 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
|
|||||||
if (record) {
|
if (record) {
|
||||||
if (kind != .selection) return true
|
if (kind != .selection) return true
|
||||||
member :: ast.selection_expr(&state.tree, id).member
|
member :: ast.selection_expr(&state.tree, id).member
|
||||||
slot :: type_record_slot(state, type, member)
|
slot :: type_record_slot(&state.tree, type, member)
|
||||||
if (slot == no_index) {
|
if (slot == no_index) {
|
||||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
.unknown_field, ast.node_span(&state.tree, member))
|
.unknown_field, ast.node_span(&state.tree, member))
|
||||||
@@ -4087,24 +4274,24 @@ type_record_storage proc(state @State, annotation ast.NodeId, depth usize) bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
type_record_slot proc(state @State, record, member ast.NodeId) usize {
|
type_record_slot proc(tree @ast.Tree, record, member ast.NodeId) usize {
|
||||||
if (ast.expr_kind(&state.tree, member) != .literal_int) {
|
if (ast.expr_kind(tree, member) != .literal_int) {
|
||||||
for (0..ast.record_field_count(&state.tree, record)) |i| {
|
for (0..ast.record_field_count(tree, record)) |i| {
|
||||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, record, i))
|
field :: ast.field(tree, ast.record_field(tree, record, i))
|
||||||
if (field.name != null and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, member))
|
if (field.name != null and ast.name_id(tree, field.name?) == ast.name_id(tree, member))
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
return no_index
|
return no_index
|
||||||
}
|
}
|
||||||
ordinal := usize(0)
|
ordinal := usize(0)
|
||||||
for (ast.literal_text(&state.tree, member)) |byte| {
|
for (ast.literal_text(tree, member)) |byte| {
|
||||||
if (byte < '0' or byte > '9') return no_index
|
if (byte < '0' or byte > '9') return no_index
|
||||||
digit :: usize(byte - '0')
|
digit :: usize(byte - '0')
|
||||||
if (ordinal > divtrunc!(maxval!(usize) - digit, usize(10))) return no_index
|
if (ordinal > divtrunc!(maxval!(usize) - digit, usize(10))) return no_index
|
||||||
ordinal = ordinal * 10 + digit
|
ordinal = ordinal * 10 + digit
|
||||||
}
|
}
|
||||||
if (ordinal >= ast.record_field_count(&state.tree, record)) return no_index
|
if (ordinal >= ast.record_field_count(tree, record)) return no_index
|
||||||
if (ast.field(&state.tree, ast.record_field(&state.tree, record, ordinal)).name != null) return no_index
|
if (ast.field(tree, ast.record_field(tree, record, ordinal)).name != null) return no_index
|
||||||
return ordinal
|
return ordinal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4157,7 +4344,7 @@ type_union_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId
|
|||||||
if (field.value) |value| input = value
|
if (field.value) |value| input = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slot :: if (name != no_node) type_record_slot(state, type, name) else no_index
|
slot :: if (name != no_node) type_record_slot(&state.tree, type, name) else no_index
|
||||||
if (slot == no_index) {
|
if (slot == no_index) {
|
||||||
code diagnostics.Code :: if (name != no_node) .unknown_field else .union_initializer_count
|
code diagnostics.Code :: if (name != no_node) .unknown_field else .union_initializer_count
|
||||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
@@ -4309,7 +4496,7 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (field.name != null and type_record_slot(state, type, field.name?) != i) {
|
if (field.name != null and type_record_slot(&state.tree, type, field.name?) != i) {
|
||||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -4362,7 +4549,7 @@ type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.
|
|||||||
for (0..ast.record_field_count(&state.tree, expression)) |i| {
|
for (0..ast.record_field_count(&state.tree, expression)) |i| {
|
||||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, i))
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, i))
|
||||||
slot := i
|
slot := i
|
||||||
if (field.name) |name| slot = type_record_slot(state, type, name)
|
if (field.name) |name| slot = type_record_slot(&state.tree, type, name)
|
||||||
if (slot >= count or values[slot] != no_node) {
|
if (slot >= count or values[slot] != no_node) {
|
||||||
code :: if (slot < count) diagnostics.Code.duplicate_field
|
code :: if (slot < count) diagnostics.Code.duplicate_field
|
||||||
else if (field.name != null) diagnostics.Code.unknown_field else diagnostics.Code.tuple_arity_mismatch
|
else if (field.name != null) diagnostics.Code.unknown_field else diagnostics.Code.tuple_arity_mismatch
|
||||||
@@ -4376,7 +4563,7 @@ type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.
|
|||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
||||||
if (field.name) |name| {
|
if (field.name) |name| {
|
||||||
if (type_record_slot(state, type, name) != i) {
|
if (type_record_slot(&state.tree, type, name) != i) {
|
||||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
||||||
return no_node
|
return no_node
|
||||||
}
|
}
|
||||||
@@ -4750,7 +4937,7 @@ type_array_update proc(
|
|||||||
previous := no_node
|
previous := no_node
|
||||||
if (record) {
|
if (record) {
|
||||||
if (ast.expr_kind(&state.tree, first) == .selection)
|
if (ast.expr_kind(&state.tree, first) == .selection)
|
||||||
index = type_record_slot(state, type, ast.selection_expr(&state.tree, first).member)
|
index = type_record_slot(&state.tree, type, ast.selection_expr(&state.tree, first).member)
|
||||||
if (index == no_index) {
|
if (index == no_index) {
|
||||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
.unknown_field, ast.node_span(&state.tree, first))
|
.unknown_field, ast.node_span(&state.tree, first))
|
||||||
@@ -4860,7 +5047,7 @@ type_variant proc(state @mut State, annotation, name ast.NodeId) bool ! Error {
|
|||||||
members :: ast.binary_expr(&state.tree, type)
|
members :: ast.binary_expr(&state.tree, type)
|
||||||
return try type_variant(state, members.left, name) or try type_variant(state, members.right, name)
|
return try type_variant(state, members.left, name) or try type_variant(state, members.right, name)
|
||||||
}
|
}
|
||||||
if (kind == .union_type) return type_record_slot(state, type, name) != no_index
|
if (kind == .union_type) return type_record_slot(&state.tree, type, name) != no_index
|
||||||
if (kind != .enum_type) return false
|
if (kind != .enum_type) return false
|
||||||
for (0..ast.enum_member_count(&state.tree, type)) |i| {
|
for (0..ast.enum_member_count(&state.tree, type)) |i| {
|
||||||
member :: ast.field(&state.tree, ast.enum_member(&state.tree, type, i)).name?
|
member :: ast.field(&state.tree, ast.enum_member(&state.tree, type, i)).name?
|
||||||
@@ -5294,9 +5481,9 @@ execute_type_statement_body proc(state @mut State, execution @mut TypeExecution,
|
|||||||
if (execution.returned != no_node) {
|
if (execution.returned != no_node) {
|
||||||
type :: try canonical_type(state, execution.result_type)
|
type :: try canonical_type(state, execution.result_type)
|
||||||
if (type != no_node and ast.expr_kind(&state.tree, type) == .fallible_type) {
|
if (type != no_node and ast.expr_kind(&state.tree, type) == .fallible_type) {
|
||||||
payload :: ast.typed_constant(&state.tree, execution.returned).value
|
failed :: try type_fallible_failed(state, execution.returned, type, value)
|
||||||
execution.failed = !try same_type(state, ast.typed_constant(&state.tree, payload).type,
|
if (failed == null) return .invalid
|
||||||
ast.binary_expr(&state.tree, type).left)
|
execution.failed = failed?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (execution.returned != no_node) return .returned
|
if (execution.returned != no_node) return .returned
|
||||||
@@ -6425,7 +6612,7 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (base_kind != .record_type and base_kind != .union_type) return no_node
|
if (base_kind != .record_type and base_kind != .union_type) return no_node
|
||||||
slot :: type_record_slot(state, base, view.member)
|
slot :: type_record_slot(&state.tree, base, view.member)
|
||||||
if (slot == no_index) return no_node
|
if (slot == no_index) return no_node
|
||||||
return try canonical_type(state, ast.field(&state.tree, ast.record_field(&state.tree, base, slot)).type?)
|
return try canonical_type(state, ast.field(&state.tree, ast.record_field(&state.tree, base, slot)).type?)
|
||||||
}
|
}
|
||||||
|
|||||||
+175
-22
@@ -11,6 +11,161 @@ 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 = {
|
||||||
|
"compiletime_only_main_cannot_select_another_runtime_function": (
|
||||||
|
"""
|
||||||
|
fallback proc() i16 { return -99 }
|
||||||
|
main proc() i16 {
|
||||||
|
types := [i16, bool]
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""", b"", b"main must be a runtime function with no parameters",
|
||||||
|
),
|
||||||
|
"inferred_metadata_locals_preserve_owned_copies_and_updates": (
|
||||||
|
"""
|
||||||
|
Config :: struct { children [2]mut ?type, count usize }
|
||||||
|
compute proc(count usize) i16 {
|
||||||
|
config := Config { children = [i8, null], count = 0 }
|
||||||
|
saved :: config
|
||||||
|
while (config.count < count) {
|
||||||
|
config.children[0] = i16
|
||||||
|
config.children[1] = config.children[0]
|
||||||
|
config.count += 1
|
||||||
|
}
|
||||||
|
types := [i8, bool]
|
||||||
|
before :: types
|
||||||
|
types = [i16, bool]
|
||||||
|
tuple := { types[0], config.count }
|
||||||
|
tuple.0 = i8
|
||||||
|
if (saved.children[0]? != i8 or saved.count != 0 or before[0] != i8) return -1
|
||||||
|
if (tuple.0 != i8 or tuple.1 != 2) return -2
|
||||||
|
for config.children |child| { if child |T| { if (T != i16) return -3 } }
|
||||||
|
Element :: types[0]
|
||||||
|
values [count]Element :: [19, 23]
|
||||||
|
return values[0] + values[1]
|
||||||
|
}
|
||||||
|
main proc() i16 { return compute(2) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"inferred_type_locals_recurse_and_preserve_cleanup": (
|
||||||
|
"""
|
||||||
|
Error :: enum { failed }
|
||||||
|
count proc(depth usize) i16 {
|
||||||
|
T := i8
|
||||||
|
T = i16
|
||||||
|
defer if (T != i16) unreachable
|
||||||
|
if (depth == 0) return 21
|
||||||
|
return count(depth - 1) + 21
|
||||||
|
}
|
||||||
|
fail proc() i16 ! Error {
|
||||||
|
T := ?i16
|
||||||
|
T = ?i8
|
||||||
|
errdefer if (T != ?i8) unreachable
|
||||||
|
return .failed
|
||||||
|
}
|
||||||
|
main proc() i16 { return fail() catch { yield count(1) } }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"inferred_metadata_call_results_and_checked_projections": (
|
||||||
|
"""
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
Schema :: alias Config
|
||||||
|
create proc() Schema { return { child = i8, count = 2 } }
|
||||||
|
children proc() [2]type { return [i8, bool] }
|
||||||
|
optional proc() ?type { return i8 }
|
||||||
|
pair proc() struct { type, usize } { return {i8, 2} }
|
||||||
|
field_type proc(value i16) i16 {
|
||||||
|
T := create().child
|
||||||
|
T = i16
|
||||||
|
result T :: value
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
indexed_type proc(value i16) i16 {
|
||||||
|
T := children()[0]
|
||||||
|
T = i16
|
||||||
|
result T :: value
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
unwrapped_type proc(value i16) i16 {
|
||||||
|
T := optional()?
|
||||||
|
T = i16
|
||||||
|
result T :: value
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
tuple_type proc(value i16) i16 {
|
||||||
|
T := pair().0
|
||||||
|
T = i16
|
||||||
|
result T :: value
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
compute proc() i16 {
|
||||||
|
config := create()
|
||||||
|
saved :: config
|
||||||
|
config.child = i16
|
||||||
|
if (saved.child != i8) return -1
|
||||||
|
return field_type(19) + indexed_type(19) + unwrapped_type(2) + tuple_type(2)
|
||||||
|
}
|
||||||
|
main proc() i16 { return compute() }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"inferred_metadata_value_blocks_matches_and_fallible_calls": (
|
||||||
|
"""
|
||||||
|
Error :: enum { failed }
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
create proc() Config ! Error { return { child = i8, count = 2 } }
|
||||||
|
compute proc(flag bool) i16 ! Error {
|
||||||
|
errdefer unreachable
|
||||||
|
config := try create()
|
||||||
|
saved :: config
|
||||||
|
config.child = i16
|
||||||
|
fields := { yield [i8, bool] }
|
||||||
|
selected := if (flag) { yield { child = i16, count = config.count } }
|
||||||
|
else { yield { child = i8, count = usize(1) } }
|
||||||
|
if (saved.child != i8 or fields[1] != bool or selected.child != i16) return -1
|
||||||
|
Element :: selected.child
|
||||||
|
values [selected.count]Element :: [19, 23]
|
||||||
|
return values[0] + values[1]
|
||||||
|
}
|
||||||
|
main proc() i16 { return compute(true) catch -2 }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"inferred_metadata_locals_require_constant_inputs": (
|
||||||
|
"""
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
count proc(value usize) usize {
|
||||||
|
config := Config { child = i16, count = value }
|
||||||
|
return config.count
|
||||||
|
}
|
||||||
|
runtime proc(value usize) usize { return count(value) }
|
||||||
|
main proc() usize { return runtime(42) }
|
||||||
|
""", b"", b"generic argument must be a compile-time value",
|
||||||
|
),
|
||||||
|
"inferred_metadata_arrays_keep_readonly_elements": (
|
||||||
|
"""
|
||||||
|
compute proc() usize {
|
||||||
|
types := [i8, bool]
|
||||||
|
types[0] = i16
|
||||||
|
return types.len
|
||||||
|
}
|
||||||
|
main proc() usize { return compute() }
|
||||||
|
""", b"", b"indexed assignment requires a writable array element",
|
||||||
|
),
|
||||||
|
"immutable_type_aliases_and_scalar_metadata_projections_stay_runtime": (
|
||||||
|
"""
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
create proc() Config { return { child = i16, count = 2 } }
|
||||||
|
Child proc() type { return i16 }
|
||||||
|
add proc(value i16) i16 {
|
||||||
|
Local :: alias i16
|
||||||
|
Other :: alias Child()
|
||||||
|
amount := create().count
|
||||||
|
argument Local :: value
|
||||||
|
result Other :: argument + i16(amount)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
forward proc(value i16) i16 { return add(value) }
|
||||||
|
main proc() i16 { return forward(40) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
"explicit_metadata_locals_compute_without_metadata_inputs": (
|
"explicit_metadata_locals_compute_without_metadata_inputs": (
|
||||||
"""
|
"""
|
||||||
Config :: struct { children [2]mut ?type, count usize }
|
Config :: struct { children [2]mut ?type, count usize }
|
||||||
@@ -3919,15 +4074,6 @@ CASES = {
|
|||||||
main proc() Selected() { return 42 }
|
main proc() Selected() { return 42 }
|
||||||
""", b"", b"compile-time execution exceeds its recursion or step limit",
|
""", b"", b"compile-time execution exceeds its recursion or step limit",
|
||||||
),
|
),
|
||||||
"type_record_procedure_results_do_not_enable_runtime_storage": (
|
|
||||||
"""
|
|
||||||
config proc() struct { child type, count i16 } { return { child = i16, count = 42 } }
|
|
||||||
main proc() i16 {
|
|
||||||
input :: config()
|
|
||||||
return input.count
|
|
||||||
}
|
|
||||||
""", b"", b"",
|
|
||||||
),
|
|
||||||
"type_records_reject_unknown_initializer_field": (
|
"type_records_reject_unknown_initializer_field": (
|
||||||
"""
|
"""
|
||||||
Bad proc() type {
|
Bad proc() type {
|
||||||
@@ -4020,14 +4166,6 @@ CASES = {
|
|||||||
main proc() Bad() { return 42 }
|
main proc() Bad() { return 42 }
|
||||||
""", b"", b"outside its runtime type",
|
""", b"", b"outside its runtime type",
|
||||||
),
|
),
|
||||||
"type_records_do_not_enable_runtime_storage": (
|
|
||||||
"""
|
|
||||||
main proc() i16 {
|
|
||||||
config :: { child = i16, count = 42 }
|
|
||||||
return config.count
|
|
||||||
}
|
|
||||||
""", b"", b"type name cannot be used as a value",
|
|
||||||
),
|
|
||||||
"field_reflection_runtime_places_callbacks_and_single_evaluation": (
|
"field_reflection_runtime_places_callbacks_and_single_evaluation": (
|
||||||
"""
|
"""
|
||||||
Point :: struct { value i16, callback @proc(value i16) i16 }
|
Point :: struct { value i16, callback @proc(value i16) i16 }
|
||||||
@@ -11964,13 +12102,28 @@ def check_typeinfo_packages(temporary, executable):
|
|||||||
""",
|
""",
|
||||||
b"indexed assignment requires a writable array element",
|
b"indexed assignment requires a writable array element",
|
||||||
),
|
),
|
||||||
(
|
|
||||||
"runtime_metadata_escape",
|
|
||||||
"main proc() i16 { info :: typeinfo!(i16)\nreturn 42 }",
|
|
||||||
b"record fields require a concrete value type",
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
cases[name] = ('import "@std/meta"\n' + text, b"", error)
|
cases[name] = ('import "@std/meta"\n' + text, b"", error)
|
||||||
|
cases["inferred_reflection_locals_compute_without_metadata_parameters"] = (
|
||||||
|
"""
|
||||||
|
import "@std/meta"
|
||||||
|
Record :: struct { child i16, enabled bool }
|
||||||
|
compute proc(bias i16) i16 {
|
||||||
|
info :: typeinfo!(Record)
|
||||||
|
match info {
|
||||||
|
.record |record|: {
|
||||||
|
saved :: record.fields[0]
|
||||||
|
field := saved
|
||||||
|
field.type = i8
|
||||||
|
if (saved.type != i16 or field.type != i8) return -1
|
||||||
|
return bias + i16(record.fields.len)
|
||||||
|
}
|
||||||
|
else: return -2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() i16 { return compute(40) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
)
|
||||||
cases["missing_meta_import"] = (
|
cases["missing_meta_import"] = (
|
||||||
"main proc() i16 { typeinfo!(i16)\nreturn 42 }",
|
"main proc() i16 { typeinfo!(i16)\nreturn 42 }",
|
||||||
b"", b"typeinfo! requires importing @std/meta",
|
b"", b"typeinfo! requires importing @std/meta",
|
||||||
|
|||||||
Reference in New Issue
Block a user