feat: add compile-time type metadata reflection
This commit is contained in:
@@ -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`.
|
||||
|
||||
`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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
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.
|
||||
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
|
||||
`@source/compilation`, with compilation-wide AST node identities.
|
||||
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] 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.
|
||||
- [ ] 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`.
|
||||
- [ ] 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.
|
||||
|
||||
@@ -557,6 +557,118 @@ recompiles_generated_records_without_borrowing_previous_ir test {
|
||||
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 {
|
||||
mains [2][]u8 :: [
|
||||
`dep :: import "../dependency"
|
||||
|
||||
@@ -119,6 +119,8 @@ Code :: enum {
|
||||
invalid_generated_field_name
|
||||
invalid_struct_layout
|
||||
invalid_c_record_field_type
|
||||
missing_metadata_import
|
||||
invalid_metadata_abi
|
||||
}
|
||||
|
||||
Diagnostic :: struct {
|
||||
@@ -259,5 +261,7 @@ messages :: $enummap.init(Code, []u8, {
|
||||
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_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",
|
||||
})
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ elaborate proc(
|
||||
if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) {
|
||||
name :: ast.builtin_call(&original, id).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) {
|
||||
@@ -383,6 +383,10 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
try expand_struct_type(state, id)
|
||||
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) {
|
||||
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,
|
||||
# evaluation order, mutability and active-union checks through both backends.
|
||||
@hide:file
|
||||
@@ -841,6 +1168,7 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
|
||||
return
|
||||
}
|
||||
subject :: ast.match_expr(&state.tree, id).subject
|
||||
try expand(state, subject)
|
||||
annotation := subject_type
|
||||
if (annotation == no_node) annotation = try expression_type(state, subject, 0)
|
||||
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)
|
||||
if (kind != .sum_type and union_view.tag != null)
|
||||
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 {
|
||||
concrete :: try materialize(state, Value{ kind = .type, node = annotation })
|
||||
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
|
||||
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) {
|
||||
try reject(state, id, .invalid_record_field_type)
|
||||
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)
|
||||
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
|
||||
index :: try type_match_arm(state, id)
|
||||
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)
|
||||
if (kind == .array_type)
|
||||
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| {
|
||||
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
|
||||
@@ -2149,6 +2481,20 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
defer arraylist.deinit(&arms)
|
||||
patterns := arraylist.init(ast.NodeId, state.allocator)
|
||||
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| {
|
||||
arm_id :: ast.match_arm(&state.tree, id, i)
|
||||
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
|
||||
}
|
||||
}
|
||||
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]
|
||||
for (0..ast.match_pattern_count(&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 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)
|
||||
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,
|
||||
arm.capture, index, true, arm.catch_all)
|
||||
capture, index, true, arm.catch_all)
|
||||
try refresh(state)
|
||||
try arraylist.append(&arms, selector)
|
||||
}
|
||||
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)
|
||||
# selector arms contain only indices. do not specialize this synthetic match
|
||||
# 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 {
|
||||
subject :: ast.match_expr(&state.tree, id).subject
|
||||
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
|
||||
payload :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
|
||||
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
|
||||
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,
|
||||
ast.node_span(&state.tree, subject), field.type?, payload.value?)
|
||||
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
|
||||
value :: try type_match_capture(state, id)
|
||||
if (value == no_node) return no_node
|
||||
snapshot :: ast.typed_constant(&state.tree, value)
|
||||
execution := TypeExecution{
|
||||
locals = arraylist.init(state.allocator),
|
||||
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 mem.free(state.allocator, execution.mapping)
|
||||
mem.set(execution.mapping, no_node)
|
||||
captured :: if (is_type(state, snapshot.type)) snapshot.value else value
|
||||
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)
|
||||
}
|
||||
@@ -2291,8 +2660,12 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
array :: ast.typed_constant(&state.tree, base)
|
||||
type :: try canonical_type(state, array.type)
|
||||
record :: (type != no_node and type_record_storage(state, type, 0)
|
||||
and ast.expr_kind(&state.tree, type) == .record_type)
|
||||
if (!record and !try execution_array_storage(state, array.type)) return true
|
||||
and (ast.expr_kind(&state.tree, type) == .record_type
|
||||
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
|
||||
if (record) {
|
||||
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))
|
||||
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) {
|
||||
index :: try type_array_index(state, ast.index_expr(&state.tree, id).index,
|
||||
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
|
||||
}
|
||||
|
||||
@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
|
||||
type_record_storage proc(state @State, annotation ast.NodeId, depth usize) bool {
|
||||
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
|
||||
}
|
||||
|
||||
@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
|
||||
# has no runtime layout. type leaves are checked here; value leaves use the checker.
|
||||
@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
|
||||
type :: try canonical_type(state, annotation)
|
||||
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 (!try type_step(state, type)) return false
|
||||
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)
|
||||
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 (!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
|
||||
@@ -2434,6 +2873,8 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
||||
@hide:file
|
||||
type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
||||
type :: try canonical_type(state, annotation)
|
||||
if (ast.expr_kind(&state.tree, type) == .union_type)
|
||||
return try type_union_value(state, expression, type)
|
||||
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
|
||||
actual :: ast.typed_constant(&state.tree, 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)
|
||||
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
|
||||
index := no_index
|
||||
element_type := no_node
|
||||
@@ -2804,6 +3253,18 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
|
||||
}
|
||||
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))
|
||||
return try type_record_value(state, expression, 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)
|
||||
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:
|
||||
# exact value-type failures belong to the source leaf and use type_mismatch.
|
||||
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| {
|
||||
value :: try type_match_capture(state, snapshot)
|
||||
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{
|
||||
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)
|
||||
@@ -3260,7 +3737,7 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta
|
||||
count := Value{}
|
||||
if tuple {
|
||||
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,
|
||||
magnitude = u64(ast.array_element_count(&state.tree, ast.typed_constant(&state.tree, collection).value)) }
|
||||
} 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[..])
|
||||
selector :: try ast.add_for_stmt(state.builder, span, collection, loop.capture, loop.index,
|
||||
loop.pointer_capture, loop.index_capture, body)
|
||||
try refresh(state)
|
||||
length := no_node
|
||||
if (ast.expr_kind(&state.tree, annotation) == .array_type) length = ast.array_type(&state.tree, annotation).count?
|
||||
else {
|
||||
@@ -3417,6 +3895,10 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
match kind {
|
||||
.typed_constant: {
|
||||
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.annotation = view.type
|
||||
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,
|
||||
.enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type, .alias_expr:
|
||||
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_float: return try builtin(state, id, .f64)
|
||||
.literal_bool: return try builtin(state, id, .bool)
|
||||
|
||||
+502
@@ -7585,6 +7585,507 @@ def check_fallible_packages_and_entry(temporary, executable):
|
||||
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():
|
||||
with tempfile.TemporaryDirectory(prefix="honey native ' ; $") as directory:
|
||||
temporary = Path(directory)
|
||||
@@ -7604,6 +8105,7 @@ def main():
|
||||
count += check_record_packages(temporary, executable)
|
||||
count += check_data_model_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_dependency_provenance(temporary, executable)
|
||||
source = ROOT / "programs/factorial.hon"
|
||||
|
||||
Reference in New Issue
Block a user