feat: add compile-time type metadata reflection

This commit is contained in:
2026-10-29 02:10:04 +01:00
parent c6acff91fd
commit 6f892bbfcc
6 changed files with 1141 additions and 30 deletions
+6 -2
View File
@@ -136,11 +136,15 @@ ordinary procedures may compute integer, boolean, float and string compile-time
type `==` and `!=` comparisons in compile-time expressions and specializations preserve aliases, nominal declaration identity and structural pointer/array/callback signatures. composed error-set identity and specialization caching ignore member ordering, grouping and repetition. for example, `read proc($T type, value T) i16 { if (T == i16) return value else return value.count }` checks the scalar or record path for each concrete call without requiring both operations on every `T`. type `==` and `!=` comparisons in compile-time expressions and specializations preserve aliases, nominal declaration identity and structural pointer/array/callback signatures. composed error-set identity and specialization caching ignore member ordering, grouping and repetition. for example, `read proc($T type, value T) i16 { if (T == i16) return value else return value.count }` checks the scalar or record path for each concrete call without requiring both operations on every `T`.
`field!(base, name)` selects a member using a compile-time string, without runtime reflection metadata. names may come from constants, constant-input procedures or erased generic parameters; only the name is evaluated at compile time, while the base retains ordinary selection semantics. records, tuples (canonical decimal names such as `"0"`), enum/error-set members, tagged payloads, callbacks and sequence `"len"`/`"ptr"` members reuse exact typing, single base evaluation, address-taking and writable-place checks. type-producing bodies also snapshot selector names for nested aggregate updates, preserving earlier by-value copies. unknown members, runtime/nonstring names and invalid calls are diagnosed; tagged payload reads still check the active field. `typeinfo!` and metadata collections remain open. `field!(base, name)` selects a member using a compile-time string, without runtime reflection metadata. names may come from constants, constant-input procedures or erased generic parameters; only the name is evaluated at compile time, while the base retains ordinary selection semantics. records, tuples (canonical decimal names such as `"0"`), enum/error-set members, tagged payloads, callbacks and sequence `"len"`/`"ptr"` members reuse exact typing, single base evaluation, address-taking and writable-place checks. type-producing bodies also snapshot selector names for nested aggregate updates, preserving earlier by-value copies. unknown members, runtime/nonstring names and invalid calls are diagnosed; tagged payload reads still check the active field.
`typeinfo!(T)` returns the compile-time-only `TypeInfo` declared by `@std/meta`; import that exact path, optionally aliased, in the calling package. arrays expose `child` and `len`, distinct types expose their immediate backing type, and records/enums expose owned readonly `fields` collections. each field has `name`, exact `type` and ordinal `index`; enum indices follow declaration order, not numeric discriminants, and enum field types retain the reflected enum or composed error-set identity. records also expose their original optional `name`, `is_tuple` and `.auto`/`.c` layout; tuple names are canonical decimal ordinals. aliases preserve underlying names and nominal identities.
ordinary and inline metadata matches select bodies before runtime checking, including in nongeneric procedures. metadata collections support `.len`, checked indexing, by-value iteration and compile-time procedure inputs/results; copied field records can change without mutating the readonly collection. field names/types drive `field!`, generic calls, generated storage and defaults through interpreter/native execution, without runtime reflection objects or borrowed interpreter addresses. construction and expansion share the type-control bound. the unchanged `std/meta.EnumFieldStruct` and `std/enummap.EnumMap` type producer are verified; complete `enummap.init/get` acceptance, general storage-bearing constant materialization and runtime metadata storage are not included.
`struct_type!(layout, names, types, defaults)` generates an ordinary record from constant fixed arrays or tuples of equal length. for example, `struct_type!(.auto, ["value", "bias"], [i16, i8], [null, 2])` requires a `value` initializer and supplies an `i8` default for `bias`. literal collections retain each field's context, including heterogeneous types and defaults; computed collections can come from helpers and loop-filled producer locals. names must be unique ASCII identifiers. each default is checked as `?Field`: absent means required, present supplies an owned field value. generated fields retain ordinary exact typing, aggregate copy isolation, field selection and checked updates through interpreter/native execution. producer specializations cache nominal identity; imported producers and repeated compilation retain source provenance and fresh owned values. incomplete arrays and runtime dependencies are rejected, and construction shares the type-control execution bound. `struct_type!(layout, names, types, defaults)` generates an ordinary record from constant fixed arrays or tuples of equal length. for example, `struct_type!(.auto, ["value", "bias"], [i16, i8], [null, 2])` requires a `value` initializer and supplies an `i8` default for `bias`. literal collections retain each field's context, including heterogeneous types and defaults; computed collections can come from helpers and loop-filled producer locals. names must be unique ASCII identifiers. each default is checked as `?Field`: absent means required, present supplies an owned field value. generated fields retain ordinary exact typing, aggregate copy isolation, field selection and checked updates through interpreter/native execution. producer specializations cache nominal identity; imported producers and repeated compilation retain source provenance and fresh owned values. incomplete arrays and runtime dependencies are rejected, and construction shares the type-control execution bound.
layout accepts `.auto` or `.c`, including constant enum members such as `Layout.c`. C-layout records must be nonempty; fields may contain concrete scalars, data pointers, optional data pointers, explicitly backed enums, nonempty fixed arrays and nested C-layout records. distinct types and non-C representations are rejected. this does not add aggregate C calling conventions, storage-bearing constant materialization or metadata inspection. layout accepts `.auto` or `.c`, including constant enum members such as `Layout.c`. C-layout records must be nonempty; fields may contain concrete scalars, data pointers, optional data pointers, explicitly backed enums, nonempty fixed arrays and nested C-layout records. distinct types and non-C representations are rejected. this does not add aggregate C calling conventions or storage-bearing constant materialization.
callback signatures use `@proc(value i16) i16`, including grouped parameter names and trailing commas. compatible named functions can initialize callbacks, which may be passed, returned, replaced and stored in records/arrays; `Callback :: alias @proc(value i16) i16` names a reusable signature. parameter/result types match structurally, retaining nominal record identity and exact pointer mutability. callback fields call directly, such as `allocator.vtable.alloc(context, size, alignment)` once the remaining argument types are supported. `@mut proc` and `*proc` are invalid; `@mut Callback` instead points to writable callback storage. callback signatures use `@proc(value i16) i16`, including grouped parameter names and trailing commas. compatible named functions can initialize callbacks, which may be passed, returned, replaced and stored in records/arrays; `Callback :: alias @proc(value i16) i16` names a reusable signature. parameter/result types match structurally, retaining nominal record identity and exact pointer mutability. callback fields call directly, such as `allocator.vtable.alloc(context, size, alignment)` once the remaining argument types are supported. `@mut proc` and `*proc` are invalid; `@mut Callback` instead points to writable callback storage.
+6 -2
View File
@@ -47,7 +47,10 @@ arrays or tuples, including loop-filled producer storage and owned defaults.
value/type tuples and strings with immutable exact captures and compile-time-selected exits. value/type tuples and strings with immutable exact captures and compile-time-selected exits.
final inline match arms expand nominal enums, tagged unions and composed error sets final inline match arms expand nominal enums, tagged unions and composed error sets
with static tags and exact by-value payloads, preserving runtime dispatch and scoped control flow. with static tags and exact by-value payloads, preserving runtime dispatch and scoped control flow.
general type-valued execution, metadata reflection and generic library acceptance remain open. `typeinfo!` exposes owned compile-time array/distinct/record/enum metadata, including
readonly field collections, exact nominal types and reflected generated storage/defaults.
the existing `meta.EnumFieldStruct` and `enummap.EnumMap` type producer work;
general type-valued execution, broader materialization and full 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
@@ -110,7 +113,8 @@ test discovery and FFI remain outside these completed slices.
- [x] heterogeneous inline-tuple checkpoint: `inline for` iterates constant tuples with exact per-field value/type captures and `usize` indices through interpreter/native execution. literal/helper/aliased tuples, erased generic inputs, nested tuple projections, generated storage, empty shapes, static exits, cleanup and aggregate copy isolation retain existing semantics. type-producing bodies borrow tuple places so later fields observe updates while saved copies remain isolated. runtime dependencies and storage-bearing escapes are rejected before iteration, including discarded captures and early exits; named records and ordinary tuple loops remain invalid. imported producers, nominal type leaves and repeatable compilation are verified. inline-match syntax is covered below; metadata collections remain open. - [x] heterogeneous inline-tuple checkpoint: `inline for` iterates constant tuples with exact per-field value/type captures and `usize` indices through interpreter/native execution. literal/helper/aliased tuples, erased generic inputs, nested tuple projections, generated storage, empty shapes, static exits, cleanup and aggregate copy isolation retain existing semantics. type-producing bodies borrow tuple places so later fields observe updates while saved copies remain isolated. runtime dependencies and storage-bearing escapes are rejected before iteration, including discarded captures and early exits; named records and ordinary tuple loops remain invalid. imported producers, nominal type leaves and repeatable compilation are verified. inline-match syntax is covered below; metadata collections remain open.
- [x] nominal inline-match checkpoint: final `inline |tag|:` enum arms and `inline |payload[, tag]|:` tagged-union arms expand remaining variants through interpreter/native execution. runtime subjects evaluate once; immutable static enum/tag captures specialize nested conditions, matches and generic calls while exact by-value scalar/aggregate/string/void payloads retain runtime semantics and copy isolation. statement/expression arms, ordinary loop/optional/match captures, scoped cleanup and return/break/continue work; type-producing bodies select generated storage and update outer type/value locals, including collection captures. invalid subjects/captures, redundant/nonfinal arms, ordinary pattern checks, imported provenance, shared expansion bounds and repeatable compilation retain diagnostics and ownership. composed error-set inline arms are covered below; pointer captures and metadata reflection remain open. - [x] nominal inline-match checkpoint: final `inline |tag|:` enum arms and `inline |payload[, tag]|:` tagged-union arms expand remaining variants through interpreter/native execution. runtime subjects evaluate once; immutable static enum/tag captures specialize nested conditions, matches and generic calls while exact by-value scalar/aggregate/string/void payloads retain runtime semantics and copy isolation. statement/expression arms, ordinary loop/optional/match captures, scoped cleanup and return/break/continue work; type-producing bodies select generated storage and update outer type/value locals, including collection captures. invalid subjects/captures, redundant/nonfinal arms, ordinary pattern checks, imported provenance, shared expansion bounds and repeatable compilation retain diagnostics and ownership. composed error-set inline arms are covered below; pointer captures and metadata reflection remain open.
- [x] composed inline-match checkpoint: composed error sets expand remaining variants through interpreter/native execution, including nested/repeated/aliased enum, tagged-union and named struct members. all-void sets use one static tag capture; payload-bearing sets use exact immutable by-value payloads and optional static tags, including void members. catch captures, nested inline tag matches, generic calls, owned aggregate copies, deferred cleanup and error returns retain ordinary semantics. constant inputs and type-producing bodies select generated storage with cached nominal identity; struct variant names remain identifiers in generated constants rather than embedding declarations. invalid compositions/captures, narrow payload types, redundant arms, imported dependencies and repeatable compilation retain diagnostics and ownership. member discovery shares the type-control bound; pointer captures and metadata reflection remain open. - [x] composed inline-match checkpoint: composed error sets expand remaining variants through interpreter/native execution, including nested/repeated/aliased enum, tagged-union and named struct members. all-void sets use one static tag capture; payload-bearing sets use exact immutable by-value payloads and optional static tags, including void members. catch captures, nested inline tag matches, generic calls, owned aggregate copies, deferred cleanup and error returns retain ordinary semantics. constant inputs and type-producing bodies select generated storage with cached nominal identity; struct variant names remain identifiers in generated constants rather than embedding declarations. invalid compositions/captures, narrow payload types, redundant arms, imported dependencies and repeatable compilation retain diagnostics and ownership. member discovery shares the type-control bound; pointer captures and metadata reflection remain open.
- [ ] remaining gate: general type-valued compile-time execution and metadata reflection (`typeinfo!` and metadata collections), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates. - [x] type-metadata reflection checkpoint: `typeinfo!(T)` uses the actual `@std/meta` ABI to classify supported types and expose array child/length, immediate distinct backing, record names/tuple/layout and enum/record fields through interpreter/native execution. readonly owned metadata collections retain exact nominal/type leaves, ordinal indices, composed enum identity, checked projections, by-value iteration/copies and compile-time procedure boundaries. ordinary/inline matches select runtime bodies; reflected fields drive `field!`, generics and generated storage/defaults. arity/type/import/ABI failures, match/capture validation, bounds, readonly updates, runtime escape rejection, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and ownership. existing `meta.EnumFieldStruct` and `enummap.EnumMap` generated storage are verified without replacement library code; full `enummap.init/get`, general type-valued execution and broader constant materialization remain open.
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including untagged unions and storage-bearing values), plus full existing-library acceptance. `enummap.init/get` still expose generic-inference/fallthrough diagnostics; optional construction and runtime undefined storage also remain acceptance dependencies. required expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
- [ ] remaining source constructs: expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`. - [ ] remaining source constructs: 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.
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support. - [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
+112
View File
@@ -557,6 +557,118 @@ recompiles_generated_records_without_borrowing_previous_ir test {
try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
} }
recompiles_imported_typeinfo_without_borrowing_metadata test {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
modules [2]mut ir.Module := undefined
{
session := init(mem.c_allocator)
defer deinit(&session)
root :: try add_package(&session)
metadata :: try add_package(&session)
root_bytes := try owned_text(
`meta :: import "@std/meta"
`Id :: distinct i16
`Keys :: enum { left, right }
`Source :: struct { value Id }
`describe proc($T type) meta.TypeInfo { return typeinfo!(T) }
`identity proc($T type, value T) T { return value }
`Storage proc() type {
` match describe(Keys) {
` .enum |info|: {
` names [info.fields.len]mut []u8 := undefined
` types [info.fields.len]mut type := undefined
` defaults [info.fields.len]mut ?Id := undefined
` inline for info.fields |field, index| {
` if (field.index != index or field.type != Keys) compile_error!("invalid enum field")
` names[index] = field.name
` types[index] = Id
` defaults[index] = Id(40)
` }
` return struct_type!(.auto, names, types, defaults)
` }
` else: compile_error!("expected enum")
` }
`}
`main proc() i16 {
` match describe([2]Id) {
` .array |info|: {
` if (info.child != Id or info.len != 2) return 0
` }
` else: return 0
` }
` match describe(Id) {
` .distinct |backing|: if (backing != i16) return 0
` else: return 0
` }
` match describe(Source) {
` .record |info|: {
` if (info.fields.len != 1 or info.is_tuple or info.layout != .auto) return 0
` inline for info.fields |field, index| {
` if (field.type != Id or field.index != index or field.name.len != 5) return 0
` }
` }
` else: return 0
` }
` storage Storage() :: {}
` left Id :: identity(Id, storage.left)
` right Id :: identity(Id, storage.right)
` return i16(left) + i16(right) - 38
`}
,
)
defer arraylist.deinit(&root_bytes)
metadata_bytes := try owned_text(
`Layout :: enum { auto, c }
`ArrayInfo :: struct { child type, len usize }
`FieldInfo :: struct { name []u8, type type, index usize }
`RecordInfo :: struct { name ?[]u8, fields []FieldInfo, is_tuple bool, layout Layout }
`EnumInfo :: struct { fields []FieldInfo }
`TypeInfo :: union(enum) {
` invalid void
` void void
` noreturn void
` anyopaque void
` bool void
` integer void
` float void
` array ArrayInfo
` pointer void
` slice void
` range void
` optional void
` function void
` enum EnumInfo
` record RecordInfo
` union void
` fallible void
` distinct type
`}
,
)
defer arraylist.deinit(&metadata_bytes)
root_source :: try add_source(&session, root, "app/main.hon", &root_bytes, &diagnostic_store)
_ = try add_source(&session, metadata, "std/meta/meta.hon", &metadata_bytes, &diagnostic_store)
try arraylist.append(&session.imports, Import{
node = declaration(&session, root_source, 0).node,
target = metadata,
})
modules[0] = try compile(&session, root, &diagnostic_store)
errdefer ir.deinit(&modules[0])
modules[1] = try compile(&session, root, &diagnostic_store)
}
defer ir.deinit(&modules[0])
defer ir.deinit(&modules[1])
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
for (0..modules.len) |index| {
outcome := try interpreter.run(mem.c_allocator, &modules[index], &diagnostic_store)
defer interpreter.deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(42), outcome.value.data.integer)
}
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
dependency_traps_keep_original_spans_and_remain_demand_driven test { dependency_traps_keep_original_spans_and_remain_demand_driven test {
mains [2][]u8 :: [ mains [2][]u8 :: [
`dep :: import "../dependency" `dep :: import "../dependency"
+4
View File
@@ -119,6 +119,8 @@ Code :: enum {
invalid_generated_field_name invalid_generated_field_name
invalid_struct_layout invalid_struct_layout
invalid_c_record_field_type invalid_c_record_field_type
missing_metadata_import
invalid_metadata_abi
} }
Diagnostic :: struct { Diagnostic :: struct {
@@ -259,5 +261,7 @@ messages :: $enummap.init(Code, []u8, {
invalid_generated_field_name = "generated field name must be an ASCII identifier", invalid_generated_field_name = "generated field name must be an ASCII identifier",
invalid_struct_layout = "generated record layout must be .auto or .c; C-layout records require at least one field", invalid_struct_layout = "generated record layout must be .auto or .c; C-layout records require at least one field",
invalid_c_record_field_type = "record field is not a supported C-layout value type", invalid_c_record_field_type = "record field is not a supported C-layout value type",
missing_metadata_import = "typeinfo! requires importing @std/meta",
invalid_metadata_abi = "@std/meta declarations do not match the compiler reflection ABI",
}) })
+511 -26
View File
@@ -128,7 +128,7 @@ elaborate proc(
if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) { if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) {
name :: ast.builtin_call(&original, id).name name :: ast.builtin_call(&original, id).name
text :: ast.source_text(&original, ast.node_span(&original, name)) text :: ast.source_text(&original, ast.node_span(&original, name))
if (mem.eql(text, "field") or mem.eql(text, "struct_type")) needed = true if (mem.eql(text, "field") or mem.eql(text, "struct_type") or mem.eql(text, "typeinfo")) needed = true
} }
if (class == .function and ast.function_generic(&original, id)) needed = true if (class == .function and ast.function_generic(&original, id)) needed = true
if (class == .function) { if (class == .function) {
@@ -383,6 +383,10 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
try expand_struct_type(state, id) try expand_struct_type(state, id)
return return
} }
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "typeinfo")) {
try expand_typeinfo(state, id)
return
}
} }
if (class == .expression and ast.expr_kind(&state.tree, id) == .call) { if (class == .expression and ast.expr_kind(&state.tree, id) == .call) {
callee :: ast.call_expr(&state.tree, id).callee callee :: ast.call_expr(&state.tree, id).callee
@@ -433,6 +437,329 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
} }
} }
# reflection values are owned AST snapshots. only ordinary leaves and selected
# runtime bodies enter IR; metadata slices never retain interpreter addresses.
@hide:file
metadata_member proc(state @mut State, at ast.NodeId, text []u8) ast.NodeId ! Error {
result :: try ast.add_generated_identifier(state.builder, ast.node_span(&state.tree, at),
try strpool.intern(state.strings, text), text)
try refresh(state)
return result
}
@hide:file
metadata_package_contains proc(state @State, package ast.NodeId, source diagnostics.SourceId) bool {
for (0..ast.package_decl_count_at(&state.tree, package)) |i| {
if (ast.node_span(&state.tree, ast.package_decl_at(&state.tree, package, i)).source == source) return true
}
return false
}
@hide:file
metadata_tags proc(state @mut State, type ast.NodeId) ast.NodeId ! Error {
if (ast.expr_kind(&state.tree, type) == .enum_type) return type
if (ast.union_type(&state.tree, type).tag) |tag| return try canonical_type(state, tag)
members := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&members)
for (0..ast.record_field_count(&state.tree, type)) |i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
member :: try ast.add_field(state.builder, field.span, field.name, null, null)
try arraylist.append(&members, member)
try refresh(state)
}
result :: try ast.add_enum_type(state.builder, ast.node_span(&state.tree, type), null, members.items)
try refresh(state)
return result
}
@hide:file
metadata_import proc(state @State, at ast.NodeId) ast.NodeId {
source :: ast.node_span(&state.tree, at).source
for (0..state.original_count) |i| {
imported :: ast.NodeId(i)
resolved :: binding(state, imported)
if (ast.node_class(&state.tree, imported) != .import or resolved.kind != .namespace) continue
if (!mem.eql(ast.source_text(&state.tree, ast.import_decl(&state.tree, imported).path), "@std/meta")) continue
other :: ast.node_span(&state.tree, imported).source
if (source == other) return resolved.data.namespace
if (metadata_package_contains(state, resolved.data.namespace, source)) return resolved.data.namespace
for (0..state.original_count) |j| {
package :: ast.NodeId(j)
if (package == state.tree.root or ast.node_class(&state.tree, package) != .package) continue
if (
metadata_package_contains(state, package, source)
and metadata_package_contains(state, package, other)
) return resolved.data.namespace
}
}
return no_node
}
@hide:file
metadata_type proc(state @mut State, package ast.NodeId, name []u8) ast.NodeId ! Error {
for (0..ast.package_decl_count_at(&state.tree, package)) |i| {
declaration :: ast.package_decl_at(&state.tree, package, i)
if (ast.node_class(&state.tree, declaration) != .statement) continue
if (mem.eql(strpool.get_str(state.strings, ast.name_id(&state.tree, declaration))?, name))
return try canonical_type(state, ast.statement_decl(&state.tree, declaration).initializer)
}
return no_node
}
@hide:file
metadata_record proc(state @mut State, at, type ast.NodeId, values []ast.NodeId) ast.NodeId ! Error {
try refresh(state)
fields := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&fields)
span :: ast.node_span(&state.tree, at)
for (values) |value, i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
owned := value
if (!is_type(state, field.type?))
owned = try ast.add_typed_constant(state.builder, span, field.type?, value)
member :: try ast.add_field(state.builder, span, field.name, null, owned)
try arraylist.append(&fields, member)
try refresh(state)
}
literal :: try ast.add_record_literal(state.builder, span, null, fields.items)
result :: try ast.add_typed_constant(state.builder, span, type, literal)
try refresh(state)
return result
}
@hide:file
metadata_schema proc(state @mut State, type ast.NodeId, names [][]u8, types []ast.NodeId) bool ! Error {
if (
type == no_node or ast.expr_kind(&state.tree, type) != .record_type
or ast.record_field_count(&state.tree, type) != names.len
) return false
for (names) |name, i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
if (
field.name == null or field.type == null
or !mem.eql(ast.literal_text(&state.tree, field.name?), name)
or !try same_type(state, field.type?, types[i])
) return false
}
return true
}
@hide:file
metadata_abi proc(state @mut State, at ast.NodeId, types []ast.NodeId) bool ! Error {
info :: types[0]
if (
info == no_node or ast.expr_kind(&state.tree, info) != .union_type
or !ast.union_type(&state.tree, info).auto_tag
) return false
span :: ast.node_span(&state.tree, at)
byte_string :: try ast.add_slice_type(state.builder, span, try builtin(state, at, .u8), false, null)
optional_name :: try ast.add_optional_type(state.builder, span, byte_string)
fields :: try ast.add_slice_type(state.builder, span, types[2], false, null)
usize_type :: try builtin(state, at, .usize)
type_type :: try builtin(state, at, .type)
bool_type :: try builtin(state, at, .bool)
try refresh(state)
array_names [2][]u8 :: ["child", "len"]
array_types [2]ast.NodeId :: [type_type, usize_type]
field_names [3][]u8 :: ["name", "type", "index"]
field_types [3]ast.NodeId :: [byte_string, type_type, usize_type]
record_names [4][]u8 :: ["name", "fields", "is_tuple", "layout"]
record_types [4]ast.NodeId :: [optional_name, fields, bool_type, types[5]]
enum_names [1][]u8 :: ["fields"]
enum_types [1]ast.NodeId :: [fields]
if (
!try metadata_schema(state, types[1], array_names[..], array_types[..])
or !try metadata_schema(state, types[2], field_names[..], field_types[..])
or !try metadata_schema(state, types[3], record_names[..], record_types[..])
or !try metadata_schema(state, types[4], enum_names[..], enum_types[..])
) return false
layout :: types[5]
if (
layout == no_node or ast.expr_kind(&state.tree, layout) != .enum_type
or ast.enum_member_count(&state.tree, layout) != 2
) return false
for (0..usize(2)) |i| {
name :: ast.field(&state.tree, ast.enum_member(&state.tree, layout, i)).name
expected :: if (i == 0) "auto" else "c"
if (name == null or !mem.eql(ast.literal_text(&state.tree, name?), expected)) return false
}
names [18][]u8 :: ["invalid", "void", "noreturn", "anyopaque", "bool", "integer", "float",
"array", "pointer", "slice", "range", "optional", "function", "enum", "record", "union", "fallible", "distinct"]
if (ast.record_field_count(&state.tree, info) != names.len) return false
void_type :: try builtin(state, at, .void)
for (names) |name, i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, info, i))
expected := void_type
if (i == 7) expected = types[1]
if (i == 13) expected = types[4]
if (i == 14) expected = types[3]
if (i == 17) expected = type_type
if (
field.name == null or field.type == null
or !mem.eql(ast.literal_text(&state.tree, field.name?), name)
or !try same_type(state, field.type?, expected)
) return false
}
return true
}
@hide:file
expand_typeinfo proc(state @mut State, id ast.NodeId) void ! Error {
if (ast.call_arg_count(&state.tree, id) != 1) {
try reject(state, id, .argument_count_mismatch)
return
}
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
argument :: ast.call_arg(&state.tree, id, 0)
value :: try evaluate(state, argument)
if (value.kind != .type) {
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) try reject_evaluation(state, id)
else try reject(state, id, .expected_type)
return
}
package :: metadata_import(state, id)
if (package == no_node) {
try reject(state, id, .missing_metadata_import)
return
}
types [6]mut ast.NodeId := undefined
names [6][]u8 :: ["TypeInfo", "ArrayInfo", "FieldInfo", "RecordInfo", "EnumInfo", "Layout"]
for (names) |name, i| {
types[i] = try metadata_type(state, package, name)
if (types[i] == no_node) {
try reject(state, id, .invalid_metadata_abi)
return
}
}
if (!try metadata_abi(state, id, types[..])) {
try reject(state, id, .invalid_metadata_abi)
return
}
type :: value.node
if (type_record_storage(state, type, 0)) {
if (!try type_record_check(state, type)) {
try reject_evaluation(state, id)
return
}
} else if (binding(state, type).kind != .builtin) {
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, argument), argument, true)
try refresh(state)
try arraylist.append(&state.rewrites.constraints, constraint)
}
kind :: ast.expr_kind(&state.tree, type)
tag []u8 := "invalid"
payload := no_node
shape := type
resolved :: binding(state, type)
if (resolved.kind == .builtin) {
builtin_kind :: builtin_identity(resolved.data.builtin)
if (try integer_type(state, type)) tag = "integer"
else if (builtin_kind == .float) tag = "float"
else if (builtin_kind == .bool) tag = "bool"
else if (builtin_kind == .void) tag = "void"
else if (builtin_kind == .anyopaque) tag = "anyopaque"
else if (builtin_kind == .string) tag = "pointer"
} else if (kind == .array_type) {
tag = "array"
array :: ast.array_type(&state.tree, type)
length := Value{}
if (array.count) |count| length = try evaluate(state, count)
if (length.kind != .integer or length.negative) {
try reject(state, id, .invalid_array_length)
return
}
child :: (try materialize(state, try evaluate(state, array.element))).node
count :: try ast.add_integer(state.builder, ast.node_span(&state.tree, id), length.magnitude)
values [2]ast.NodeId :: [child, count]
payload = try metadata_record(state, id, types[1], values[..])
} else if (kind == .distinct_type) {
tag = "distinct"
payload = (try materialize(state, try evaluate(state, ast.type_operand(&state.tree, type)))).node
} else if (kind == .pointer_type) tag = "pointer"
else if (kind == .slice_type) tag = "slice"
else if (kind == .optional_type) tag = "optional"
else if (kind == .function_type) tag = "function"
else if (kind == .fallible_type) tag = "fallible"
else if (kind == .union_type) tag = "union"
else if (kind == .record_type) tag = "record"
else if (kind == .enum_type or kind == .union_tag_type) {
tag = "enum"
if (kind == .union_tag_type) {
variants :: try inline_match_shape(state, try canonical_type(state, ast.type_operand(&state.tree, type)))
shape = try metadata_tags(state, variants)
}
} else if (kind == .sum_type) {
shape = try inline_match_shape(state, type)
if (shape == no_node) {
try reject_evaluation(state, id)
return
}
tag = if (ast.expr_kind(&state.tree, shape) == .enum_type) "enum" else "union"
}
span :: ast.node_span(&state.tree, id)
if (mem.eql(tag, "record") or mem.eql(tag, "enum")) {
enumeration :: mem.eql(tag, "enum")
fields := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&fields)
count :: if (enumeration) ast.enum_member_count(&state.tree, shape) else ast.record_field_count(&state.tree, shape)
tuple := !enumeration
for (0..count) |i| {
if (!try type_step(state, id)) {
try reject_evaluation(state, id)
return
}
field_id :: if (enumeration) ast.enum_member(&state.tree, shape, i)
else ast.record_field(&state.tree, shape, i)
field :: ast.field(&state.tree, field_id)
ordinal :: try ast.add_integer(state.builder, span, u64(i))
try refresh(state)
name := ordinal
if (field.name) |member| {
name = member
tuple = false
}
text :: try ast.add_string(state.builder, span, ast.literal_text(&state.tree, name))
child_type :: if (enumeration) type else field.type?
child :: (try materialize(state, try evaluate(state, child_type))).node
values [3]ast.NodeId :: [text, child, ordinal]
try arraylist.append(&fields, try metadata_record(state, id, types[2], values[..]))
}
collection :: try ast.add_array_literal(state.builder, span, fields.items)
if (enumeration) {
values [1]ast.NodeId :: [collection]
payload = try metadata_record(state, id, types[4], values[..])
} else {
name := try ast.add_literal(state.builder, .null, span)
try refresh(state)
for (state.declarations.items) |declaration| {
if (ast.node_class(&state.tree, declaration) == .statement
and ast.statement_decl(&state.tree, declaration).initializer == type) {
name = try ast.add_string(state.builder, span, strpool.get_str(state.strings, ast.name_id(&state.tree, declaration))?)
break
}
}
tuple_value :: try ast.add_boolean(state.builder, span, tuple)
try refresh(state)
layout_text :: if (ast.record_c_layout(&state.tree, type)) "c" else "auto"
layout_name :: try metadata_member(state, id, layout_text)
layout :: try ast.add_enum_literal(state.builder, span, layout_name)
values [4]ast.NodeId :: [name, collection, tuple_value, layout]
payload = try metadata_record(state, id, types[3], values[..])
}
}
if (payload == no_node) payload = try ast.add_literal(state.builder, .void, span)
member :: try metadata_member(state, id, tag)
field :: try ast.add_field(state.builder, span, member, null, payload)
fields [1]ast.NodeId :: [field]
literal :: try ast.add_record_literal(state.builder, span, null, fields[..])
result :: try ast.add_typed_constant(state.builder, span, types[0], literal)
try replace(state, id, result)
try refresh(state)
}
# erase only the name: ordinary selection checks the base and retains its place, # erase only the name: ordinary selection checks the base and retains its place,
# evaluation order, mutability and active-union checks through both backends. # evaluation order, mutability and active-union checks through both backends.
@hide:file @hide:file
@@ -841,6 +1168,7 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
return return
} }
subject :: ast.match_expr(&state.tree, id).subject subject :: ast.match_expr(&state.tree, id).subject
try expand(state, subject)
annotation := subject_type annotation := subject_type
if (annotation == no_node) annotation = try expression_type(state, subject, 0) if (annotation == no_node) annotation = try expression_type(state, subject, 0)
if (annotation == no_node) { if (annotation == no_node) {
@@ -895,6 +1223,7 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
union_view :: ast.union_type(&state.tree, members_type) union_view :: ast.union_type(&state.tree, members_type)
if (kind != .sum_type and union_view.tag != null) if (kind != .sum_type and union_view.tag != null)
tag_type = try canonical_type(state, union_view.tag?) tag_type = try canonical_type(state, union_view.tag?)
else if (type_record_storage(state, annotation, 0)) tag_type = try metadata_tags(state, annotation)
else { else {
concrete :: try materialize(state, Value{ kind = .type, node = annotation }) concrete :: try materialize(state, Value{ kind = .type, node = annotation })
tag_type = try ast.add_union_tag_type(state.builder, span, concrete.node) tag_type = try ast.add_union_tag_type(state.builder, span, concrete.node)
@@ -943,7 +1272,7 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
} }
payload_capture ?ast.NodeId := null payload_capture ?ast.NodeId := null
if (tagged and arm.capture != null) { if (tagged and arm.capture != null) {
payload_type :: try canonical_type(state, field.type?) payload_type :: if (is_type(state, field.type?)) field.type? else try canonical_type(state, field.type?)
if (payload_type == no_node) { if (payload_type == no_node) {
try reject(state, id, .invalid_record_field_type) try reject(state, id, .invalid_record_field_type)
return return
@@ -1021,9 +1350,10 @@ specialize_match proc(state @mut State, id, statement ast.NodeId) bool ! Error {
} }
} }
} }
if (!explicit and usize(id) < state.original_count) return false
try expand(state, subject) try expand(state, subject)
if (!explicit and !branch_constant(state, subject, 0)) return false metadata :: type_record_storage(state, try expression_type(state, subject, 0), 0)
if (!explicit and usize(id) < state.original_count and !metadata) return false
if (!explicit and !metadata and !branch_constant(state, subject, 0)) return false
state.evaluation_diagnostic = diagnostics.no_diagnostic state.evaluation_diagnostic = diagnostics.no_diagnostic
index :: try type_match_arm(state, id) index :: try type_match_arm(state, id)
if (index == no_index) { if (index == no_index) {
@@ -1160,7 +1490,9 @@ type_storage_annotation proc(tree @ast.Tree, bindings []sema.Binding, input ast.
return type_storage_annotation(tree, bindings, ast.type_operand(tree, input), depth + 1) return type_storage_annotation(tree, bindings, ast.type_operand(tree, input), depth + 1)
if (kind == .array_type) if (kind == .array_type)
return type_storage_annotation(tree, bindings, ast.array_type(tree, input).element, depth + 1) return type_storage_annotation(tree, bindings, ast.array_type(tree, input).element, depth + 1)
if (kind != .record_type) return false if (kind == .slice_type)
return type_storage_annotation(tree, bindings, ast.slice_type(tree, input).element, depth + 1)
if (kind != .record_type and kind != .union_type) return false
for (0..ast.record_field_count(tree, input)) |i| { for (0..ast.record_field_count(tree, input)) |i| {
field :: ast.field(tree, ast.record_field(tree, input, i)) field :: ast.field(tree, ast.record_field(tree, input, i))
if (field.type != null and type_storage_annotation(tree, bindings, field.type?, depth + 1)) return true if (field.type != null and type_storage_annotation(tree, bindings, field.type?, depth + 1)) return true
@@ -2149,6 +2481,20 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
defer arraylist.deinit(&arms) defer arraylist.deinit(&arms)
patterns := arraylist.init(ast.NodeId, state.allocator) patterns := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&patterns) defer arraylist.deinit(&patterns)
subject :: ast.match_expr(&state.tree, id).subject
annotation :: try expression_type(state, subject, 0)
metadata :: type_record_storage(state, annotation, 0)
dispatch := subject
if (metadata) {
value :: try evaluate_expected(state, subject, annotation)
if (value.kind != .aggregate or ast.expr_kind(&state.tree, value.node) != .record_literal) return no_index
active :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
span :: ast.node_span(&state.tree, subject)
tags :: try metadata_tags(state, annotation)
tag :: try ast.add_enum_literal(state.builder, span, active.name?)
dispatch = try ast.add_typed_constant(state.builder, span, tags, tag)
try refresh(state)
}
for (0..ast.match_arm_count(&state.tree, id)) |i| { for (0..ast.match_arm_count(&state.tree, id)) |i| {
arm_id :: ast.match_arm(&state.tree, id, i) arm_id :: ast.match_arm(&state.tree, id, i)
arm :: ast.match_arm_view(&state.tree, arm_id) arm :: ast.match_arm_view(&state.tree, arm_id)
@@ -2160,17 +2506,6 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
return no_index return no_index
} }
} }
if (
i > 0
and arm.expression != ast.match_arm_view(
&state.tree,
ast.match_arm(&state.tree, id, 0),
).expression
) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.type_mismatch, ast.node_span(&state.tree, arm_id))
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| {
pattern :: ast.match_pattern(&state.tree, arm_id, j) pattern :: ast.match_pattern(&state.tree, arm_id, j)
@@ -2178,15 +2513,41 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
try expand(state, pattern) try expand(state, pattern)
try arraylist.append(&patterns, pattern) try arraylist.append(&patterns, pattern)
} }
if (metadata and arm.capture != null) {
expected := no_node
valid := !arm.catch_all and patterns.items.len > 0
for (patterns.items) |pattern| {
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) {
valid = false
continue
}
slot :: type_record_slot(state, annotation, ast.enum_literal(&state.tree, pattern))
if (slot == no_index) {
valid = false
continue
}
payload :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot)).type?
if (binding(state, payload).kind == .builtin and binding(state, payload).data.builtin == .void)
valid = false
if (expected != no_node and !try same_type(state, expected, payload)) valid = false
expected = payload
}
if (!valid) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_match_capture, ast.node_span(&state.tree, arm_id))
return no_index
}
}
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))
capture :: if (metadata) null else arm.capture
selector :: try ast.add_match_arm(state.builder, span, patterns.items, selector :: try ast.add_match_arm(state.builder, span, patterns.items,
arm.capture, index, true, arm.catch_all) capture, index, true, arm.catch_all)
try refresh(state) try refresh(state)
try arraylist.append(&arms, selector) try arraylist.append(&arms, selector)
} }
selector :: try ast.add_match(state.builder, ast.node_span(&state.tree, id), selector :: try ast.add_match(state.builder, ast.node_span(&state.tree, id),
ast.match_expr(&state.tree, id).subject, arms.items) dispatch, arms.items)
try refresh(state) try refresh(state)
# selector arms contain only indices. do not specialize this synthetic match # selector arms contain only indices. do not specialize this synthetic match
# again while its checked evaluation expands the wrapper. # again while its checked evaluation expands the wrapper.
@@ -2202,7 +2563,8 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error { type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
subject :: ast.match_expr(&state.tree, id).subject subject :: ast.match_expr(&state.tree, id).subject
annotation :: try expression_type(state, subject, 0) annotation :: try expression_type(state, subject, 0)
value :: try evaluate_ir(state, subject, annotation) value :: if (type_record_storage(state, annotation, 0))
try evaluate_expected(state, subject, annotation) else try evaluate_ir(state, subject, annotation)
if (value.kind != .aggregate or ast.expr_kind(&state.tree, value.node) != .record_literal) return no_node if (value.kind != .aggregate or ast.expr_kind(&state.tree, value.node) != .record_literal) return no_node
payload :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0)) payload :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
shape :: try inline_match_shape(state, annotation) shape :: try inline_match_shape(state, annotation)
@@ -2213,6 +2575,10 @@ type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
field.name != null and payload.name != null field.name != null and payload.name != null
and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, payload.name?) and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, payload.name?)
) { ) {
if (
ast.expr_kind(&state.tree, payload.value?) == .typed_constant
and try same_type(state, field.type?, ast.typed_constant(&state.tree, payload.value?).type)
) return payload.value?
result :: try ast.add_typed_constant(state.builder, result :: try ast.add_typed_constant(state.builder,
ast.node_span(&state.tree, subject), field.type?, payload.value?) ast.node_span(&state.tree, subject), field.type?, payload.value?)
try refresh(state) try refresh(state)
@@ -2227,6 +2593,7 @@ type_match_body proc(state @mut State, id ast.NodeId, arm ast.MatchArmView) ast.
if (arm.capture == null) return arm.body if (arm.capture == null) return arm.body
value :: try type_match_capture(state, id) value :: try type_match_capture(state, id)
if (value == no_node) return no_node if (value == no_node) return no_node
snapshot :: ast.typed_constant(&state.tree, value)
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)),
@@ -2236,8 +2603,10 @@ type_match_body proc(state @mut State, id ast.NodeId, arm ast.MatchArmView) ast.
defer arraylist.deinit(&execution.locals) defer arraylist.deinit(&execution.locals)
defer mem.free(state.allocator, execution.mapping) defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node) mem.set(execution.mapping, no_node)
captured :: if (is_type(state, snapshot.type)) snapshot.value else value
try arraylist.append(&execution.locals, TypeLocal{ try arraylist.append(&execution.locals, TypeLocal{
declaration = arm.capture?, value = value, annotation = ast.typed_constant(&state.tree, value).type, declaration = arm.capture?, value = captured,
annotation = snapshot.type,
}) })
return try type_snapshot(state, &execution, arm.body) return try type_snapshot(state, &execution, arm.body)
} }
@@ -2291,8 +2660,12 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
array :: ast.typed_constant(&state.tree, base) array :: ast.typed_constant(&state.tree, base)
type :: try canonical_type(state, array.type) type :: try canonical_type(state, array.type)
record :: (type != no_node and type_record_storage(state, type, 0) record :: (type != no_node and type_record_storage(state, type, 0)
and ast.expr_kind(&state.tree, type) == .record_type) and (ast.expr_kind(&state.tree, type) == .record_type
if (!record and !try execution_array_storage(state, array.type)) return true or ast.expr_kind(&state.tree, type) == .union_type))
if (
!record and !try execution_array_storage(state, array.type)
and !try metadata_sequence(state, array.type)
) return true
selected := no_node selected := no_node
if (record) { if (record) {
if (kind != .selection) return true if (kind != .selection) return true
@@ -2303,7 +2676,15 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
.unknown_field, ast.node_span(&state.tree, member)) .unknown_field, ast.node_span(&state.tree, member))
return false return false
} }
selected = ast.field(&state.tree, ast.record_field(&state.tree, array.value, slot)).value? if (ast.expr_kind(&state.tree, type) == .union_type) {
active :: ast.field(&state.tree, ast.record_field(&state.tree, array.value, 0))
if (active.name == null or ast.name_id(&state.tree, active.name?) != ast.name_id(&state.tree, member)) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.inactive_union_field, ast.node_span(&state.tree, id))
return false
}
selected = active.value?
} else selected = ast.field(&state.tree, ast.record_field(&state.tree, array.value, slot)).value?
} else if (kind == .index) { } else if (kind == .index) {
index :: try type_array_index(state, ast.index_expr(&state.tree, id).index, index :: try type_array_index(state, ast.index_expr(&state.tree, id).index,
ast.array_element_count(&state.tree, array.value)) ast.array_element_count(&state.tree, array.value))
@@ -2359,6 +2740,13 @@ execution_array_storage proc(state @mut State, annotation ast.NodeId) bool ! Err
return current != no_node and ast.expr_kind(&state.tree, current) == .array_type return current != no_node and ast.expr_kind(&state.tree, current) == .array_type
} }
@hide:file
metadata_sequence proc(state @mut State, annotation ast.NodeId) bool ! Error {
type :: try canonical_type(state, annotation)
return (type != no_node and ast.expr_kind(&state.tree, type) == .slice_type
and type_record_storage(state, type, 0))
}
@hide:file @hide:file
type_record_storage proc(state @State, annotation ast.NodeId, depth usize) bool { type_record_storage proc(state @State, annotation ast.NodeId, depth usize) bool {
return type_storage_annotation(&state.tree, state.bindings.items, annotation, depth) return type_storage_annotation(&state.tree, state.bindings.items, annotation, depth)
@@ -2386,6 +2774,48 @@ type_record_slot proc(state @State, record, member ast.NodeId) usize {
return ordinal return ordinal
} }
@hide:file
type_union_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error {
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
value :: ast.typed_constant(&state.tree, expression)
if (try same_type(state, type, value.type)) return expression
}
name := no_node
input := no_node
kind :: ast.expr_kind(&state.tree, expression)
if (kind == .enum_literal) name = ast.enum_literal(&state.tree, expression)
else if (kind == .record_literal and ast.record_field_count(&state.tree, expression) == 1) {
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, 0))
if (field.name) |member| name = member
if (field.value) |value| input = value
}
slot :: if (name != no_node) type_record_slot(state, type, name) else no_index
if (slot == no_index) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.union_initializer_count, ast.node_span(&state.tree, expression))
return no_node
}
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, slot))
payload_type :: try canonical_type(state, field.type?)
empty :: (binding(state, payload_type).kind == .builtin
and binding(state, payload_type).data.builtin == .void)
span :: ast.node_span(&state.tree, expression)
if (input == no_node and empty) input = try ast.add_literal(state.builder, .void, span)
if (input == no_node) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .missing_field, span)
return no_node
}
try refresh(state)
value :: try type_execution_value(state, input, field.type?)
if (value == no_node) return no_node
member :: try ast.add_field(state.builder, span, field.name, null, value)
fields [1]ast.NodeId :: [member]
literal :: try ast.add_record_literal(state.builder, span, null, fields[..])
result :: try ast.add_typed_constant(state.builder, span, type, literal)
try refresh(state)
return result
}
# keep ordinary field/default diagnostics even though the enclosing metadata record # keep ordinary field/default diagnostics even though the enclosing metadata record
# has no runtime layout. type leaves are checked here; value leaves use the checker. # has no runtime layout. type leaves are checked here; value leaves use the checker.
@hide:file @hide:file
@@ -2393,6 +2823,8 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
if (is_type(state, annotation)) return true if (is_type(state, annotation)) return true
type :: try canonical_type(state, annotation) type :: try canonical_type(state, annotation)
if (type == no_node) return false if (type == no_node) return false
if (ast.expr_kind(&state.tree, type) == .slice_type)
return try type_record_check(state, ast.slice_type(&state.tree, type).element)
if (ast.expr_kind(&state.tree, type) == .array_type) { if (ast.expr_kind(&state.tree, type) == .array_type) {
if (!try type_step(state, type)) return false if (!try type_step(state, type)) return false
descriptor :: ast.array_type(&state.tree, type) descriptor :: ast.array_type(&state.tree, type)
@@ -2414,6 +2846,13 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
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
} }
if (ast.expr_kind(&state.tree, type) == .union_type) {
payload :: try canonical_type(state, field.type?)
if (
payload != no_node and binding(state, payload).kind == .builtin
and binding(state, payload).data.builtin == .void
) continue
}
if (type_record_storage(state, field.type?, 0)) { if (type_record_storage(state, field.type?, 0)) {
if (!try type_record_check(state, field.type?)) return false if (!try type_record_check(state, field.type?)) return false
if (field.value != null and try type_execution_value(state, field.value?, field.type?) == no_node) return false if (field.value != null and try type_execution_value(state, field.value?, field.type?) == no_node) return false
@@ -2434,6 +2873,8 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
@hide:file @hide:file
type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error { type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
type :: try canonical_type(state, annotation) type :: try canonical_type(state, annotation)
if (ast.expr_kind(&state.tree, type) == .union_type)
return try type_union_value(state, expression, type)
if (ast.expr_kind(&state.tree, expression) == .typed_constant) { if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
actual :: ast.typed_constant(&state.tree, expression) actual :: ast.typed_constant(&state.tree, expression)
if (try same_type(state, type, actual.type)) return expression if (try same_type(state, type, actual.type)) return expression
@@ -2694,6 +3135,14 @@ type_array_update proc(
} }
array :: ast.typed_constant(&state.tree, current) array :: ast.typed_constant(&state.tree, current)
type :: try canonical_type(state, array.type) type :: try canonical_type(state, array.type)
if (
type == no_node
or (ast.expr_kind(&state.tree, type) != .record_type and ast.expr_kind(&state.tree, type) != .array_type)
) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_index_assignment, ast.node_span(&state.tree, target))
return no_node
}
record :: ast.expr_kind(&state.tree, type) == .record_type record :: ast.expr_kind(&state.tree, type) == .record_type
index := no_index index := no_index
element_type := no_node element_type := no_node
@@ -2804,6 +3253,18 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
} }
return value return value
} }
if (
!is_type(state, annotation) and type_record_storage(state, annotation, 0)
and ast.expr_kind(&state.tree, try canonical_type(state, annotation)) == .slice_type
) {
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
actual :: ast.typed_constant(&state.tree, expression)
if (try same_type(state, annotation, actual.type)) return expression
}
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.type_mismatch, ast.node_span(&state.tree, expression))
return no_node
}
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0)) if (!is_type(state, annotation) and type_record_storage(state, annotation, 0))
return try type_record_value(state, expression, annotation) return try type_record_value(state, expression, annotation)
if (is_type(state, annotation)) { if (is_type(state, annotation)) {
@@ -2829,6 +3290,19 @@ 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
} }
kind :: ast.expr_kind(&state.tree, expression)
if (kind == .literal_string or kind == .typed_constant) {
value :: try evaluate(state, expression)
if (
value.kind == .string and try valid_value(state, annotation, value)
and (value.annotation == no_node or try same_type(state, annotation, value.annotation))
) {
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression),
annotation, value.node)
try refresh(state)
return result
}
}
# check the leaf as an assignment, not as the evaluation wrapper's return: # check the leaf as an assignment, not as the evaluation wrapper's return:
# exact value-type failures belong to the source leaf and use type_mismatch. # exact value-type failures belong to the source leaf and use type_mismatch.
contextual :: try ast.add_typed_constant(state.builder, contextual :: try ast.add_typed_constant(state.builder,
@@ -3152,8 +3626,11 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
if (arm.capture) |capture| { if (arm.capture) |capture| {
value :: try type_match_capture(state, snapshot) value :: try type_match_capture(state, snapshot)
if (value == no_node) return .invalid if (value == no_node) return .invalid
constant :: ast.typed_constant(&state.tree, value)
captured :: if (is_type(state, constant.type)) constant.value else value
try arraylist.append(&execution.locals, TypeLocal{ try arraylist.append(&execution.locals, TypeLocal{
declaration = capture, value = value, annotation = ast.typed_constant(&state.tree, value).type, declaration = capture, value = captured,
annotation = constant.type,
}) })
} }
if (!arm.expression) return try execute_type_block(state, execution, arm.body) if (!arm.expression) return try execute_type_block(state, execution, arm.body)
@@ -3260,7 +3737,7 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta
count := Value{} count := Value{}
if tuple { if tuple {
count = Value{ kind = .integer, magnitude = u64(ast.record_field_count(&state.tree, annotation)) } count = Value{ kind = .integer, magnitude = u64(ast.record_field_count(&state.tree, annotation)) }
} else if try execution_array_storage(state, annotation) { } else if (try execution_array_storage(state, annotation) or try metadata_sequence(state, annotation)) {
count = Value{ kind = .integer, count = Value{ kind = .integer,
magnitude = u64(ast.array_element_count(&state.tree, ast.typed_constant(&state.tree, collection).value)) } magnitude = u64(ast.array_element_count(&state.tree, ast.typed_constant(&state.tree, collection).value)) }
} else { } else {
@@ -3271,6 +3748,7 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta
body :: try ast.add_block(state.builder, span, stops[..]) body :: try ast.add_block(state.builder, span, stops[..])
selector :: try ast.add_for_stmt(state.builder, span, collection, loop.capture, loop.index, selector :: try ast.add_for_stmt(state.builder, span, collection, loop.capture, loop.index,
loop.pointer_capture, loop.index_capture, body) loop.pointer_capture, loop.index_capture, body)
try refresh(state)
length := no_node length := no_node
if (ast.expr_kind(&state.tree, annotation) == .array_type) length = ast.array_type(&state.tree, annotation).count? if (ast.expr_kind(&state.tree, annotation) == .array_type) length = ast.array_type(&state.tree, annotation).count?
else { else {
@@ -3417,6 +3895,10 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
match kind { match kind {
.typed_constant: { .typed_constant: {
view :: ast.typed_constant(&state.tree, id) view :: ast.typed_constant(&state.tree, id)
if (!is_type(state, view.type) and type_record_storage(state, view.type, 0)) {
if (!try type_array_initialized(state, id, id)) return Value{}
return Value{ kind = .aggregate, node = view.value, annotation = view.type }
}
value := try evaluate_expected(state, view.value, view.type) value := try evaluate_expected(state, view.value, view.type)
value.annotation = view.type value.annotation = view.type
return value return value
@@ -3825,7 +4307,10 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
.record_type, .array_type, .slice_type, .pointer_type, .function_type, .optional_type, .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: .enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type, .alias_expr:
return try builtin(state, id, .type) return try builtin(state, id, .type)
.typed_constant: return try canonical_type(state, ast.typed_constant(&state.tree, id).type) .typed_constant: {
annotation :: ast.typed_constant(&state.tree, id).type
return if (is_type(state, annotation)) annotation else try canonical_type(state, annotation)
}
.literal_int: return try builtin(state, id, .i64) .literal_int: return try builtin(state, id, .i64)
.literal_float: return try builtin(state, id, .f64) .literal_float: return try builtin(state, id, .f64)
.literal_bool: return try builtin(state, id, .bool) .literal_bool: return try builtin(state, id, .bool)
+502
View File
@@ -7585,6 +7585,507 @@ def check_fallible_packages_and_entry(temporary, executable):
return count return count
def check_typeinfo_packages(temporary, executable):
project = temporary / "typeinfo"
project.mkdir()
(project / "build.hon").write_text("# project root\n")
(project / "std").mkdir()
for package in ("meta", "enummap"):
(project / "std" / package).symlink_to(ROOT / "std" / package, target_is_directory=True)
cases = {
"categories_specialize_generic_calls": (
"""
reflection :: import "@std/meta"
Error :: enum { failed }
Choice :: union(enum) { number i16, empty void }
Raw :: union { number i16, flag bool }
category proc($T type) i16 {
return match typeinfo!(T) {
.integer: 1,
.bool: 2,
.float: 3,
.void: 4,
.anyopaque: 5,
.pointer: 6,
.slice: 7,
.optional: 8,
.function: 9,
.union: 10,
.fallible: 11,
else: -1,
}
}
widen proc($T type, value T) i16 {
return match typeinfo!(T) {
.integer: i16(value),
.bool: match value { true: 23, false: 0 },
else: value.missing,
}
}
main proc() [14]i16 {
return [
category(i8), category(bool), category(f64), category(void),
category(anyopaque), category(@mut i16), category([]u8),
category(?i16), category(proc(value i16) i16),
category(Choice), category(i16 ! Error), category(Raw),
category(string), widen(i8(19)) + widen(true),
]
}
""", b"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 10, 6, 42]\n", None,
),
"arrays_and_immediate_distinct_backing": (
"""
import "@std/meta"
Inner :: distinct u16
Outer :: distinct Inner
Other :: distinct u16
OuterAlias :: alias Outer
ArrayAlias :: alias [3]Inner
array_matches proc($T, $Child type, $length usize) bool {
return match typeinfo!(T) {
.array |info|: info.child == Child and info.len == length,
else: false,
}
}
backing_matches proc($T, $Backing type) bool {
return match typeinfo!(T) {
.distinct |backing|: backing == Backing,
else: false,
}
}
main proc() bool {
return $(
array_matches(ArrayAlias, Inner, 3)
and array_matches([0]bool, bool, 0)
and array_matches([2]mut i16, i16, 2)
and !array_matches(ArrayAlias, Other, 3)
and backing_matches(Inner, u16)
and backing_matches(OuterAlias, Inner)
and !backing_matches(Outer, u16)
and OuterAlias == Outer and Inner != Other
)
}
""", b"true\n", None,
),
"record_tuple_names_order_exact_types_and_layout": (
"""
import "@std/meta"
Child :: struct { value i16 }
Other :: struct { value i16 }
Record :: struct { child Child, maybe ?i8, pointer @u8 }
RecordAlias :: alias Record
Tuple :: alias struct { i8, Child, bool }
Packet :: struct_type!(.c, ["value"], [i16], [null])
same_name proc(left, right []u8) bool {
if (left.len != right.len) return false
for left |byte, index| {
if (byte != right[index]) return false
}
return true
}
valid proc() bool {
match typeinfo!(RecordAlias) {
.record |info|: {
if (info.is_tuple or info.layout != .auto) return false
if (!same_name(info.name?, "Record") or info.fields.len != 3) return false
if (!same_name(info.fields[0].name, "child") or info.fields[0].index != 0) return false
if (!same_name(info.fields[1].name, "maybe") or info.fields[1].index != 1) return false
if (!same_name(info.fields[2].name, "pointer") or info.fields[2].index != 2) return false
if (info.fields[0].type != Child or info.fields[0].type == Other) return false
if (info.fields[1].type != ?i8 or info.fields[2].type != @u8) return false
}
else: return false,
}
match typeinfo!(Tuple) {
.record |info|: {
if (!info.is_tuple or info.name != null or info.layout != .auto) return false
if (info.fields.len != 3) return false
inline for info.fields |field, index| {
if (field.index != index or field.name.len != 1) return false
if (field.name[0] != u8(48 + index)) return false
if (index == 0 and field.type != i8) return false
if (index == 1 and field.type != Child) return false
if (index == 2 and field.type != bool) return false
}
}
else: return false,
}
match typeinfo!(struct { value i16 }) {
.record |info|: if (info.name != null or info.is_tuple) return false,
else: return false,
}
return match typeinfo!(Packet) {
.record |info|: info.layout == .c and !info.is_tuple,
else: false,
}
}
main proc() bool { return $(valid()) }
""", b"true\n", None,
),
"enum_metadata_uses_declaration_order_not_discriminants": (
"""
import "@std/meta"
Sparse :: enum(i16) { last = -7, first = 3, middle = 90 }
SparseAlias :: alias Sparse
Other :: enum(i16) { last = -7, first = 3, middle = 90 }
valid proc() bool {
return match typeinfo!(SparseAlias) {
.enum |info|: (
info.fields.len == 3
and info.fields[0].index == 0
and info.fields[1].index == 1
and info.fields[2].index == 2
and info.fields[0].name.len == 4 and info.fields[0].name[0] == 108
and info.fields[1].name.len == 5 and info.fields[1].name[0] == 102
and info.fields[2].name.len == 6 and info.fields[2].name[0] == 109
and info.fields[0].type == Sparse
and info.fields[1].type == SparseAlias
and info.fields[2].type != Other
),
else: false,
}
}
main proc() [3]mut i16 {
if (!$(valid())) return [0, 0, 0]
values [3]mut i16 := [0, 0, 0]
match typeinfo!(SparseAlias) {
.enum |info|: inline for info.fields |field, index| {
values[index] = i16(field!(SparseAlias, field.name))
}
else: compile_error!("expected enum"),
}
return values
}
""", b"[-7, 3, 90]\n", None,
),
"record_fields_drive_field_access_and_generic_inference": (
"""
import "@std/meta"
Record :: struct { small i8, wide i16, enabled bool }
convert proc($T type, value T) i16 {
return match typeinfo!(T) {
.integer: i16(value),
.bool: match value { true: 2, false: 0 },
else: compile_error!("unsupported field"),
}
}
sum proc($T type, value T) i16 {
total i16 := 0
match typeinfo!(T) {
.record |info|: inline for info.fields |field| {
total = total + convert(field!(value, field.name))
}
else: compile_error!("expected record"),
}
return total
}
main proc() i16 {
value Record :: { small = 19, wide = 21, enabled = true }
return sum(value)
}
""", b"42\n", None,
),
"reflected_fields_generate_owned_schema_and_defaults": (
"""
import "@std/meta"
Source :: struct { left i16, right i16 }
Defaults proc($T type, $default i16) type {
match typeinfo!(T) {
.record |info|: {
names [info.fields.len]mut []u8 := undefined
types [info.fields.len]mut type := undefined
defaults [info.fields.len]mut ?i16 := undefined
inline for info.fields |field, index| {
names[index] = field.name
types[index] = field.type
defaults[index] = default
}
return struct_type!(info.layout, names, types, defaults)
}
else: compile_error!("expected record"),
}
}
main proc() i16 {
first Defaults(Source, 21) := {}
saved :: first
field!(first, "left") = 0
same Defaults(Source, 21) :: saved
return same.left + same.right
}
""", b"42\n", None,
),
"real_meta_enum_field_struct_defaults": (
"""
import "@std/meta"
Key :: enum(i16) { last = -7, first = 3, middle = 90 }
Values :: alias meta.EnumFieldStruct(Key, i16, 21)
main proc() i16 {
values Values :: { last = 19, first = 23 }
empty Values :: {}
if (values.middle != 21 or empty.last != 21 or empty.first != 21) return -1
return values.last + field!(values, "first")
}
""", b"42\n", None,
),
"real_enummap_generates_ordinal_storage": (
"""
import "@std/meta"
import "@std/enummap"
Key :: enum(i16) { last = -7, first = 3, missing = 90 }
KeyAlias :: alias Key
main proc() i16 {
values enummap.EnumMap(KeyAlias, i16) :: {
present = [true, true, false], values = [19, 23, 0],
}
if (values.present[2]) return -1
return values.values[0] + values.values[1]
}
""", b"42\n", None,
),
"metadata_slices_cross_producers_without_losing_type_identity": (
"""
import "@std/meta"
Id :: distinct i16
Record :: struct { value Id }
retain proc(fields []meta.FieldInfo) []meta.FieldInfo { return fields }
describe_fields proc($T type) []meta.FieldInfo {
return match typeinfo!(T) {
.record |info|: retain(info.fields),
.enum |info|: retain(info.fields),
else: compile_error!("expected fields"),
}
}
Storage proc() type {
source :: describe_fields(Record)
copy := source[0]
copy.type = bool
if (copy.type != bool or source[0].type != Id) return bool
if (describe_fields(struct {}).len != 0) return bool
return struct_type!(.auto, [source[0].name], [source[0].type], [Id(42)])
}
main proc() i16 {
value Storage() :: {}
return i16(value.value)
}
""", b"42\n", None,
),
"composed_enum_metadata_preserves_members_and_sum_identity": (
"""
import "@std/meta"
First :: enum { one, two }
Second :: enum { three }
Keys :: alias (First | (Second | First))
valid proc() bool {
match typeinfo!(Keys) {
.enum |info|: {
if (info.fields.len != 3) return false
inline for info.fields |field, index| {
if (field.index != index or field.type != Keys) return false
selected Keys :: field!(Keys, field.name)
if (index == 0 and selected != .one) return false
if (index == 1 and selected != .two) return false
if (index == 2 and selected != .three) return false
}
return true
}
else: return false,
}
}
main proc() bool { return $(valid()) }
""", b"true\n", None,
),
"inline_metadata_match_keeps_static_tags_and_type_payloads": (
"""
import "@std/meta"
Id :: distinct i16
score proc($T type) i16 {
match typeinfo!(T) {
inline |payload, tag|: {
if (tag == .array) {
if (payload.child == i16) return i16(payload.len)
return 0
} else if (tag == .distinct) {
if (payload == i16) return 40
return 0
} else {
return 0
}
},
}
}
main proc() i16 { return score([2]i16) + score(Id) + score(bool) }
""", b"42\n", None,
),
"active_metadata_payloads_support_direct_checked_projection": (
"""
import "@std/meta"
Id :: distinct i16
Storage proc() type {
if (typeinfo!(Id).distinct != i16) return bool
child :: typeinfo!([2]Id).array.child
count :: typeinfo!([2]Id).array.len
return struct { values [count]child }
}
main proc() i16 {
value Storage() :: { values = [Id(19), Id(23)] }
if (typeinfo!(Storage()).record.fields[0].index != 0) return 0
return i16(value.values[0]) + i16(value.values[1])
}
""", b"42\n", None,
),
}
for name, text, error in (
("missing_argument", "main proc() i16 { typeinfo!()\nreturn 42 }", b"argument count"),
("extra_argument", "main proc() i16 { typeinfo!(i16, bool)\nreturn 42 }", b"argument count"),
("non_type_argument", "main proc() i16 { typeinfo!(42)\nreturn 42 }", b"must name a type"),
(
"overlapping_patterns",
"main proc() i16 { return match typeinfo!(i16) { .integer: 42, .integer: 0, else: 0 } }",
b"duplicate or overlapping match pattern",
),
(
"non_exhaustive_match",
"main proc() i16 { return match typeinfo!(i16) { .integer: 42 } }",
b"match is not exhaustive",
),
(
"void_payload_capture",
"main proc() i16 { return match typeinfo!(i16) { .integer |value|: 42, else: 0 } }",
b"payload capture requires compatible nonvoid tagged union variants",
),
(
"incompatible_payload_capture",
"main proc() i16 { return match typeinfo!(i16) { .array, .record |info|: 42, else: 0 } }",
b"payload capture requires compatible nonvoid tagged union variants",
),
(
"field_bounds",
"""
Bad proc() type {
match typeinfo!(struct { value i16 }) {
.record |info|: return info.fields[1].type,
else: return bool,
}
}
main proc() i16 { value Bad() :: 42
return value }
""",
b"index out of bounds",
),
(
"inactive_metadata_payload_projection",
"main proc() usize { return typeinfo!(i16).array.len }",
b"tagged union field is not active",
),
(
"metadata_slice_elements_remain_readonly",
"""
Bad proc() type {
match typeinfo!(struct { value i16 }) {
.record |info|: {
fields := info.fields
fields[0].type = bool
return fields[0].type
}
else: return bool,
}
}
main proc() Bad() { return 42 }
""",
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["missing_meta_import"] = (
"main proc() i16 { typeinfo!(i16)\nreturn 42 }",
b"", b"typeinfo! requires importing @std/meta",
)
cases["relative_meta_import_is_not_reflection_opt_in"] = (
'import "../std/meta"\nmain proc() i16 { typeinfo!(i16)\nreturn 42 }',
b"", b"typeinfo! requires importing @std/meta",
)
for name, (text, stdout, error) in cases.items():
app = project / name
app.mkdir()
(app / "main.hon").write_text(textwrap.dedent(text).strip() + "\n")
check(app, executable, (stdout, error))
library = project / "library"
library.mkdir()
definition = library / "types.hon"
definition.write_text(textwrap.dedent("""
import "@std/meta"
Record :: struct { value i16 }
Alias :: alias Record
Other :: struct { value i16 }
Child proc($T type) type {
return match typeinfo!(T) {
.record |info|: info.fields[0].type,
else: bool,
}
}
""").strip() + "\n")
app = project / "import_identity"
app.mkdir()
source = app / "main.hon"
source.write_text(textwrap.dedent("""
import "@std/meta"
lib :: import "../library"
Record :: struct { value i16 }
same_record proc($T type) bool {
return match typeinfo!(T) {
.array |info|: info.child == lib.Record and info.child != Record and info.child != lib.Other,
else: false,
}
}
main proc() i16 {
if (!$(same_record([1]lib.Alias))) return -1
value lib.Child(lib.Alias) :: 42
return value
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
definition.write_text(textwrap.dedent("""
import "@std/meta"
Bad proc() type {
match typeinfo!(struct { value i16 }) {
.record |info|: return info.fields[1].type,
else: return bool,
}
}
""").strip() + "\n")
source.write_text('lib :: import "../library"\nmain proc() lib.Bad() { return 42 }\n')
check(app, executable, (b"", b"index out of bounds"))
diagnosed = run(str(HONEY), "run", str(app))
assert diagnosed.stderr.startswith(f"{definition.resolve()}:4:".encode()), outcome(diagnosed)
assert len(diagnosed.stderr.splitlines()) == 1, outcome(diagnosed)
sibling = project / "sibling_import"
sibling.mkdir()
(sibling / "metadata.hon").write_text('reflection :: import "@std/meta"\n')
(sibling / "main.hon").write_text(
"main proc() i16 { return match typeinfo!(i16) { .integer: 42, else: 0 } }\n"
)
check(sibling, executable, (b"42\n", None))
malformed = project / "malformed"
(malformed / "std" / "meta").mkdir(parents=True)
(malformed / "build.hon").write_text("# project root\n")
metadata = (ROOT / "std/meta/meta.hon").read_text()
(malformed / "std/meta/meta.hon").write_text(metadata.replace(" type type\n", " type usize\n"))
(malformed / "main.hon").write_text(
'import "@std/meta"\n'
"main proc() i16 { return match typeinfo!(i16) { .integer: 42, else: 0 } }\n"
)
check(malformed, executable, (b"", b"@std/meta declarations do not match the compiler reflection ABI"))
return len(cases) + 4
def main(): def main():
with tempfile.TemporaryDirectory(prefix="honey native ' ; $") as directory: with tempfile.TemporaryDirectory(prefix="honey native ' ; $") as directory:
temporary = Path(directory) temporary = Path(directory)
@@ -7604,6 +8105,7 @@ def main():
count += check_record_packages(temporary, executable) count += check_record_packages(temporary, executable)
count += check_data_model_packages(temporary, executable) count += check_data_model_packages(temporary, executable)
count += check_specialization_packages(temporary, executable) count += check_specialization_packages(temporary, executable)
count += check_typeinfo_packages(temporary, executable)
count += check_fallible_packages_and_entry(temporary, executable) count += check_fallible_packages_and_entry(temporary, executable)
count += check_dependency_provenance(temporary, executable) count += check_dependency_provenance(temporary, executable)
source = ROOT / "programs/factorial.hon" source = ROOT / "programs/factorial.hon"