feat: implement compile-time field selection reflection
This commit is contained in:
@@ -126,6 +126,8 @@ 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!`, `struct_type!` and inline expansion remain open.
|
||||
|
||||
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.
|
||||
|
||||
`true` and `false` are boolean literals; `bool` is available for parameters, storage and results. `==`, `!=`, `<`, `<=`, `>` and `>=` compare compatible numeric operands and produce `bool`; `==` and `!=` also accept boolean operands. comparisons preserve contextual numeric inference without making their operands boolean. arithmetic binds tighter than relational comparisons, which bind tighter than equality; comparisons associate left to right. boolean arithmetic/ordering and string/function/void comparisons are invalid; there is no implicit numeric-to-boolean conversion.
|
||||
|
||||
@@ -36,7 +36,9 @@ initialized type-valued fixed arrays support nested copies, indexed/whole update
|
||||
checked projections and by-value collection captures in type-producing bodies.
|
||||
type-array parameters and results cross compile-time procedure boundaries with
|
||||
owned snapshots, cached nominal identity and erased arguments.
|
||||
general type-valued execution, reflection and generic library acceptance remain open.
|
||||
`field!` selects record/tuple/enum and sequence members using compile-time names,
|
||||
including runtime places and type-control updates without runtime reflection.
|
||||
general type-valued execution, metadata reflection and generic 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
|
||||
@@ -89,7 +91,8 @@ test discovery and FFI remain outside these completed slices.
|
||||
- [x] collection-iteration checkpoint: arrays, slices, strings and single pointers-to-arrays support immutable exact-type copy captures, storage-qualified pointer captures and optional `usize` indices through interpreter/native execution. iterables/bounds evaluate once, array lvalues remain borrowed and later element updates are visible; temporary arrays, aggregate copy isolation, zero-cell/empty collections, nested loops and cleanup across transfers retain ordinary semantics. ordinary constant-input procedures reuse the same checked IR; type-producing bodies iterate materializable arrays/strings with by-value captures, fixed entry-time place indices, generated storage, nominal cache identity, dependency diagnostics, shared execution bounds and repeatable compilation. storage-bearing type-control captures retain their existing boundary; type-valued elements are covered below.
|
||||
- [x] type-array storage checkpoint: type-producing bodies own initialized fixed arrays of type values, including explicit/inferred nested arrays, `.len`, checked `usize` indexing, whole/indexed updates and immutable by-value collection captures. persistent snapshots preserve copies and exact shape/mutability while borrowed array places observe later updates. selected types generate nested storage, constructors, signatures and defaults through interpreter/native execution, retaining selected-body and index/type/mutability diagnostics, imported provenance, nominal cache identity, shared bounds and repeatable compilation. uninitialized arrays, type-valued record storage and reflection remain open.
|
||||
- [x] type-array procedure checkpoint: fixed arrays of type values cross compile-time procedure inputs/results, including nested/aliased results, dependent `$N` lengths, empty shapes and erased `$types` parameters in runtime generic procedures. all inputs to type/type-array producers are explicit constants; owned snapshots preserve by-value isolation, exact shape/mutability and canonical/nominal cache identity. projections generate signatures, constructors, storage and defaults through interpreter/native execution. malformed inputs/results, runtime dependencies, imported provenance, shared execution bounds and repeatable compilation retain diagnostics and isolation. runtime type-array storage, uninitialized arrays, type-valued record storage and reflection remain open.
|
||||
- [ ] remaining gate: general type-valued compile-time execution, inline match/loop expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), 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] field-selection reflection checkpoint: `field!(base, name)` evaluates/erases a constant string name and reuses ordinary selection through interpreter/native execution. records/tuples, enum/error-set members, tagged payloads, callbacks, pointer-qualified places and sequence `.len`/`.ptr` retain exact types, single base evaluation, address-taking, mutability and active-field checks. type-producing bodies snapshot changing selector names for nested aggregate updates without losing by-value isolation. runtime/nonstring names, malformed calls, missing members and noncanonical/overflowing tuple ordinals retain diagnostics; full metadata reflection and inline expansion remain open.
|
||||
- [ ] remaining gate: general type-valued compile-time execution, inline match/loop expansion and metadata reflection (`typeinfo!`, `struct_type!`, generated type/default arrays), 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.
|
||||
- [ ] 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.
|
||||
|
||||
+18
-1
@@ -514,6 +514,21 @@ add_identifier proc(
|
||||
}, span)
|
||||
}
|
||||
|
||||
#! copies selector spelling into AST-owned storage, independently of its diagnostic span.
|
||||
add_generated_identifier proc(
|
||||
builder @mut Builder,
|
||||
at Span,
|
||||
interned_name strpool.StringId,
|
||||
text []u8,
|
||||
) NodeId ! mem.AllocError {
|
||||
id :: try add_generated_literal(builder, at, .string, text)
|
||||
node :: &builder.storage.nodes.items[usize(id)]
|
||||
node.kind = .expr_identifier
|
||||
node.data1 = node.main
|
||||
node.main = NodeData{ name_id = interned_name }
|
||||
return id
|
||||
}
|
||||
|
||||
add_unary proc(
|
||||
builder @mut Builder,
|
||||
span Span,
|
||||
@@ -2141,10 +2156,12 @@ add_owned_literal proc(
|
||||
return id
|
||||
}
|
||||
|
||||
#! borrows a literal's spelling, independently of its diagnostic source span.
|
||||
#! borrows literal or selector spelling, independently of its diagnostic source span.
|
||||
#! query float_value first for generated IEEE literals.
|
||||
literal_text proc(tree @Tree, id NodeId) []u8 {
|
||||
node :: get_node(&tree.storage, id)
|
||||
if (node.kind == .expr_identifier and node.data0.extra == 1)
|
||||
return tree.storage.generated.items[usize(node.data1.extra)]
|
||||
if (node.data0.extra == 1) return tree.storage.generated.items[usize(node.main.extra)]
|
||||
return source_text(tree, get_span(&tree.storage, id))
|
||||
}
|
||||
|
||||
@@ -398,7 +398,14 @@ compile proc(
|
||||
}
|
||||
resolution := try sema.resolve_packages(compilation.allocator, &tree, roots.items, imports.items, &compilation.strings, diagnostic_store)
|
||||
defer sema.deinit(&resolution)
|
||||
elaborated := try specialize.elaborate(compilation.allocator, &compilation.builder, compilation.module_root?, &resolution, diagnostic_store)
|
||||
elaborated := try specialize.elaborate(
|
||||
compilation.allocator,
|
||||
&compilation.builder,
|
||||
compilation.module_root?,
|
||||
&resolution,
|
||||
&compilation.strings,
|
||||
diagnostic_store,
|
||||
)
|
||||
defer specialize.deinit(&elaborated)
|
||||
tree = ast.view(&compilation.builder, compilation.module_root?)
|
||||
checked := try typecheck.check_elaborated(compilation.allocator, &tree, &resolution, elaborated.constraints.items, diagnostic_store)
|
||||
|
||||
@@ -138,8 +138,8 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
||||
` config struct { counts [2]mut usize, bias i16 } := { counts = [0, 0], bias = 0 }
|
||||
` original :: config
|
||||
` for (usize(0)..2) |index| {
|
||||
` config.counts[index] = index + 1
|
||||
` config.bias = config.bias + 21
|
||||
` field!(config, "counts")[index] = index + 1
|
||||
` field!(config, "bias") = field!(config, "bias") + 21
|
||||
` }
|
||||
` return struct {
|
||||
` values [config.counts[1]]i16
|
||||
@@ -232,7 +232,7 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
||||
` if (same_type_array.values[0] + same_type_array.values[1] + same_type_array.bias != 42) return 0
|
||||
` boundary BoundaryType(TypeRows()) :: { values = [19, 21] }
|
||||
` same_boundary BoundaryType(TypeRows()) :: boundary
|
||||
` if (same_boundary.values[0] + same_boundary.values[1] + same_boundary.bias != 42) return 0
|
||||
` if (same_boundary.values[0] + same_boundary.values[1] + field!(same_boundary, "bias") != 42) return 0
|
||||
` if (copy_type_rows(TypeRows())[0][0] != u8 or copy_type_rows(TypeRows())[1][0] != i16) return 0
|
||||
` if (TypeRows()[0][0] != i16) return 0
|
||||
` if (read_type_rows(TypeRows(), 40) != 42 or read_type_rows(TypeRows(), 40) != 42) return 0
|
||||
|
||||
@@ -1972,9 +1972,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
and sema.binding(state.resolution, id).kind == .none
|
||||
) {
|
||||
selection :: ast.selection_expr(tree, id)
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
base_type :: typecheck.type_of(state.checked, selection.base)
|
||||
if (base_type.kind == .slice or base_type.kind == .string or base_type.kind == .array) {
|
||||
member :: ast.source_text(tree, ast.node_span(tree, selection.member))
|
||||
member :: ast.literal_text(tree, selection.member)
|
||||
if (mem.eql(member, "len") or mem.eql(member, "ptr")) {
|
||||
parts :: try slice_parts(state, selection.base)
|
||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||
@@ -1986,7 +1987,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
if (!descriptor.many and descriptor.element.kind == .array) {
|
||||
pointer :: try lower_expression(state, selection.base)
|
||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||
member :: ast.source_text(tree, ast.node_span(tree, selection.member))
|
||||
member :: ast.literal_text(tree, selection.member)
|
||||
array :: typecheck.array_type(state.checked, descriptor.element.array_type)
|
||||
if (mem.eql(member, "len")) return try constant(state, ir.Value{
|
||||
type = .usize, data = ir.ValueData{ unsigned = u64(array.len) },
|
||||
|
||||
@@ -2830,7 +2830,7 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
}
|
||||
base := try infer_shape(state, view.base, defaults)
|
||||
if ((base.kind == .array or base.kind == .slice or base.kind == .string) and base.nominal == ast.NodeId(maxval!(u32))) {
|
||||
member :: ast.source_text(state.tree, ast.node_span(state.tree, view.member))
|
||||
member :: ast.literal_text(state.tree, view.member)
|
||||
if (mem.eql(member, "len")) {
|
||||
seed(state, id, Type{ kind = .usize })
|
||||
return group_type(state, id)
|
||||
@@ -2852,7 +2852,7 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
!pointer.many and pointer.element.kind == .array
|
||||
and ast.expr_kind(state.tree, view.member) == .identifier
|
||||
) {
|
||||
member :: ast.source_text(state.tree, ast.node_span(state.tree, view.member))
|
||||
member :: ast.literal_text(state.tree, view.member)
|
||||
if (mem.eql(member, "len")) {
|
||||
type :: Type{ kind = .usize }
|
||||
seed(state, id, type)
|
||||
@@ -3259,7 +3259,7 @@ named_field proc(tree @ast.Tree, fields []RecordField, name ast.NodeId) usize {
|
||||
selected_field proc(state @State, fields []RecordField, member ast.NodeId) usize {
|
||||
if (ast.expr_kind(state.tree, member) == .identifier) return named_field(state.tree, fields, member)
|
||||
value := usize(0)
|
||||
for ast.source_text(state.tree, ast.node_span(state.tree, member)) |byte| {
|
||||
for ast.literal_text(state.tree, member) |byte| {
|
||||
if (byte < '0' or byte > '9') return no_index
|
||||
digit :: usize(byte - '0')
|
||||
if (value > divtrunc!(maxval!(usize) - digit, usize(10))) return no_index
|
||||
|
||||
@@ -15,7 +15,7 @@ typecheck :: import "@source/sema/check"
|
||||
@hide:file no_index :: maxval!(usize)
|
||||
|
||||
@hide:file
|
||||
Error :: alias mem.AllocError | diagnostics.CapacityError
|
||||
Error :: alias mem.AllocError | diagnostics.CapacityError | strpool.InternError
|
||||
|
||||
@hide:file
|
||||
ValueKind :: enum {
|
||||
@@ -82,6 +82,7 @@ State :: struct {
|
||||
allocator mem.Allocator
|
||||
builder @mut ast.Builder
|
||||
tree ast.Tree
|
||||
strings @mut strpool.StringPool
|
||||
bindings arraylist.ArrayList(sema.Binding)
|
||||
declarations arraylist.ArrayList(ast.NodeId)
|
||||
instances arraylist.ArrayList(Instance)
|
||||
@@ -108,6 +109,7 @@ elaborate proc(
|
||||
builder @mut ast.Builder,
|
||||
root ast.NodeId,
|
||||
resolution @mut sema.Resolution,
|
||||
strings @mut strpool.StringPool,
|
||||
diagnostic_store @mut diagnostics.Store,
|
||||
) Result ! Error {
|
||||
result := Result{ builder = builder, changes = arraylist.init(allocator), constraints = arraylist.init(allocator) }
|
||||
@@ -119,6 +121,10 @@ elaborate proc(
|
||||
id :: ast.NodeId(i)
|
||||
class :: ast.node_class(&original, id)
|
||||
if (class == .expression and ast.expr_kind(&original, id) == .comptime_expr) needed = true
|
||||
if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) {
|
||||
name :: ast.builtin_call(&original, id).name
|
||||
if (mem.eql(ast.source_text(&original, ast.node_span(&original, name)), "field")) needed = true
|
||||
}
|
||||
if (class == .function and ast.function_generic(&original, id)) needed = true
|
||||
if (class == .function) {
|
||||
result_type :: sema.binding(resolution, ast.function_decl(&original, id).return_type)
|
||||
@@ -134,7 +140,7 @@ elaborate proc(
|
||||
mapping :: try mem.alloc(ast.NodeId, allocator, ast.node_count(&original))
|
||||
defer mem.free(allocator, mapping)
|
||||
state := State{
|
||||
allocator = allocator, builder = builder, tree = ast.view(builder, root),
|
||||
allocator = allocator, builder = builder, tree = ast.view(builder, root), strings = strings,
|
||||
bindings = arraylist.init(allocator), declarations = arraylist.init(allocator),
|
||||
instances = arraylist.init(allocator), cache = hashmap.init(allocator),
|
||||
expanded = arraylist.init(allocator), diagnostics = diagnostic_store,
|
||||
@@ -269,6 +275,13 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
try refresh(state)
|
||||
return
|
||||
}
|
||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .builtin_call) {
|
||||
name :: ast.builtin_call(&state.tree, id).name
|
||||
if (mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "field")) {
|
||||
try expand_field(state, id)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .call) {
|
||||
callee :: ast.call_expr(&state.tree, id).callee
|
||||
try expand(state, callee)
|
||||
@@ -318,6 +331,66 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
}
|
||||
}
|
||||
|
||||
# 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
|
||||
expand_field proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
if (ast.call_arg_count(&state.tree, id) != 2) {
|
||||
try reject(state, id, .argument_count_mismatch)
|
||||
return
|
||||
}
|
||||
base :: ast.call_arg(&state.tree, id, 0)
|
||||
argument :: ast.call_arg(&state.tree, id, 1)
|
||||
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||
value :: try evaluate(state, argument)
|
||||
if (value.kind != .string) {
|
||||
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.comptime_argument_required, ast.node_span(&state.tree, argument))
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
try arraylist.append(&state.rewrites.constraints, argument)
|
||||
text :: ast.literal_text(&state.tree, value.node)
|
||||
bytes := arraylist.init(u8, state.allocator)
|
||||
defer arraylist.deinit(&bytes)
|
||||
cursor := usize(1)
|
||||
while (cursor < text.len - 1) {
|
||||
byte :: ast.string_next(text, &cursor) catch {
|
||||
try reject(state, id, .invalid_escape)
|
||||
return
|
||||
}
|
||||
try arraylist.append(&bytes, byte)
|
||||
}
|
||||
# only canonical decimal names denote tuple ordinals; "01" is a name.
|
||||
numeric := bytes.items.len > 0 and (bytes.items.len == 1 or bytes.items[0] != '0')
|
||||
ordinal := u64(0)
|
||||
for (bytes.items) |byte| {
|
||||
if (byte < '0' or byte > '9') {
|
||||
numeric = false
|
||||
break
|
||||
}
|
||||
digit :: u64(byte - '0')
|
||||
if (ordinal > divtrunc!(maxval!(u64) - digit, u64(10))) {
|
||||
numeric = false
|
||||
break
|
||||
}
|
||||
ordinal = ordinal * 10 + digit
|
||||
}
|
||||
span :: ast.node_span(&state.tree, argument)
|
||||
member := no_node
|
||||
if (numeric) member = try ast.add_integer(state.builder, span, ordinal)
|
||||
else {
|
||||
interned :: try strpool.intern(state.strings, bytes.items)
|
||||
member = try ast.add_generated_identifier(state.builder, span, interned, bytes.items)
|
||||
}
|
||||
selection :: try ast.add_selection(state.builder, ast.node_span(&state.tree, id), base, member)
|
||||
try replace(state, id, selection)
|
||||
try refresh(state)
|
||||
state.expanded.items[usize(id)] = 0
|
||||
try expand(state, id)
|
||||
}
|
||||
|
||||
# runtime bodies share the type-producer selector, but retain ordinary runtime
|
||||
# parameters and control transfers in the selected arm.
|
||||
@hide:file
|
||||
@@ -1413,7 +1486,7 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
selected = ast.array_element(&state.tree, array.value, index)
|
||||
} else {
|
||||
member :: ast.selection_expr(&state.tree, id).member
|
||||
if (!mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, member)), "len")) {
|
||||
if (!mem.eql(ast.literal_text(&state.tree, member), "len")) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.unknown_field, ast.node_span(&state.tree, member))
|
||||
return false
|
||||
@@ -1717,6 +1790,41 @@ execute_type_block proc(state @mut State, execution @mut TypeExecution, block as
|
||||
return .next
|
||||
}
|
||||
|
||||
# substitute selector names without snapshotting away the assignment's root place.
|
||||
@hide:file
|
||||
type_assignment_target proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
|
||||
kind :: ast.expr_kind(&state.tree, id)
|
||||
if (kind == .builtin_call) {
|
||||
name :: ast.builtin_call(&state.tree, id).name
|
||||
if (!mem.eql(ast.source_text(&state.tree, ast.node_span(&state.tree, name)), "field")) return id
|
||||
if (ast.call_arg_count(&state.tree, id) != 2) {
|
||||
try reject(state, id, .argument_count_mismatch)
|
||||
return no_node
|
||||
}
|
||||
base :: try type_assignment_target(state, execution, ast.call_arg(&state.tree, id, 0))
|
||||
if (base == no_node) return no_node
|
||||
argument :: try type_snapshot(state, execution, ast.call_arg(&state.tree, id, 1))
|
||||
arguments [2]ast.NodeId :: [base, argument]
|
||||
result :: try ast.add_builtin_call(state.builder, ast.node_span(&state.tree, id), name, arguments[..])
|
||||
try refresh(state)
|
||||
try expand(state, result)
|
||||
if (binding(state, result).kind == .invalid) return no_node
|
||||
return result
|
||||
}
|
||||
if (kind != .selection and kind != .index) return id
|
||||
base :: if (kind == .selection) ast.selection_expr(&state.tree, id).base else ast.index_expr(&state.tree, id).base
|
||||
selected :: try type_assignment_target(state, execution, base)
|
||||
if (selected == no_node or selected == base) return if (selected == no_node) no_node else id
|
||||
result := no_node
|
||||
if (kind == .selection)
|
||||
result = try ast.add_selection(state.builder, ast.node_span(&state.tree, id),
|
||||
selected, ast.selection_expr(&state.tree, id).member)
|
||||
else result = try ast.add_index(state.builder, ast.node_span(&state.tree, id),
|
||||
selected, ast.index_expr(&state.tree, id).index)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
execute_type_statement proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
|
||||
match ast.stmt_kind(&state.tree, statement) {
|
||||
@@ -1756,6 +1864,18 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
}
|
||||
.assignment_stmt: {
|
||||
assignment :: ast.assignment_stmt(&state.tree, statement)
|
||||
target :: try type_assignment_target(state, execution, assignment.target)
|
||||
if (target == no_node) return .invalid
|
||||
if (target != assignment.target) {
|
||||
normalized :: try ast.add_assignment_stmt(state.builder,
|
||||
ast.node_span(&state.tree, statement), target, assignment.value)
|
||||
try refresh(state)
|
||||
# synthesized places still need local substitution in snapshots.
|
||||
previous :: execution.mapping.len
|
||||
execution.mapping = try mem.realloc(state.allocator, execution.mapping, ast.node_count(&state.tree))
|
||||
mem.set(execution.mapping[previous..], no_node)
|
||||
return try execute_type_statement(state, execution, normalized)
|
||||
}
|
||||
root := assignment.target
|
||||
while (true) {
|
||||
kind :: ast.expr_kind(&state.tree, root)
|
||||
@@ -2564,7 +2684,7 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
if (base == no_node) return no_node
|
||||
if (ast.expr_kind(&state.tree, base) == .pointer_type) base = try canonical_type(state, ast.pointer_type(&state.tree, base).element)
|
||||
if (base == no_node) return no_node
|
||||
member :: ast.source_text(&state.tree, ast.node_span(&state.tree, view.member))
|
||||
member :: ast.literal_text(&state.tree, view.member)
|
||||
base_kind :: ast.expr_kind(&state.tree, base)
|
||||
if (
|
||||
mem.eql(member, "len") and (
|
||||
|
||||
+136
@@ -11,6 +11,142 @@ HONEY = ROOT / "build/honey"
|
||||
|
||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||
CASES = {
|
||||
"field_reflection_runtime_places_callbacks_and_single_evaluation": (
|
||||
"""
|
||||
Point :: struct { value i16, callback @proc(value i16) i16 }
|
||||
add proc(value i16) i16 { return value + 2 }
|
||||
place proc(point @mut Point, calls @mut i16) @mut Point {
|
||||
calls^ = calls^ + 1
|
||||
return point
|
||||
}
|
||||
name proc() string { return "val\\x75e" }
|
||||
main proc() [3]i16 {
|
||||
point Point := { value = 19, callback = add }
|
||||
calls i16 := 0
|
||||
field!(place(&point, &calls), name()) = 40
|
||||
pointer @mut i16 :: &field!(point, "value")
|
||||
pointer^ = pointer^ + 1
|
||||
return [field!(point, "callback")(field!(point, "value")), calls, point.value]
|
||||
}
|
||||
""", b"[43, 1, 41]\n", None,
|
||||
),
|
||||
"field_reflection_tuple_enum_union_and_sequence_members": (
|
||||
"""
|
||||
A :: enum { first, second }
|
||||
B :: enum { third }
|
||||
E :: alias A | B
|
||||
Choice :: union(enum) { payload i16, empty void }
|
||||
read proc($name string, value struct { count i16 }) i16 {
|
||||
return field!(value, name)
|
||||
}
|
||||
main proc() [4]i16 {
|
||||
tuple :: {i16(19), i16(21)}
|
||||
choice Choice :: { payload = 2 }
|
||||
values [2]i16 :: [19, 21]
|
||||
member E :: field!(E, "third")
|
||||
if (member != E.third) return [0, 0, 0, 0]
|
||||
return [
|
||||
field!(tuple, "0") + field!(tuple, "1") + field!(choice, "payload"),
|
||||
i16(field!(values, "len")),
|
||||
field!(&values, "ptr")[1],
|
||||
read("count", { count = 42 }),
|
||||
]
|
||||
}
|
||||
""", b"[42, 2, 21, 42]\n", None,
|
||||
),
|
||||
"field_reflection_type_control_and_constant_materialization": (
|
||||
"""
|
||||
Config :: struct { count usize, value i16 }
|
||||
config proc() Config { return { count = 2, value = 21 } }
|
||||
Storage proc(name string) type {
|
||||
settings :: config()
|
||||
types :: [i16, bool]
|
||||
return struct {
|
||||
values [field!(settings, name)]types[0]
|
||||
bias i16 = i16(field!(types, "len"))
|
||||
}
|
||||
}
|
||||
main proc() i16 {
|
||||
value Storage("count") :: { values = [19, 21] }
|
||||
constant :: $(field!(config(), "value"))
|
||||
return value.values[0] + constant + value.bias
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"field_reflection_type_control_updates_names_and_nested_places": (
|
||||
"""
|
||||
Selected proc() type {
|
||||
config struct { left [1]mut usize, right [1]mut usize } := { left = [0], right = [0] }
|
||||
original :: config
|
||||
for ["left", "right"] |name| { field!(config, name)[0] = 2 }
|
||||
if (original.left[0] != 0 or original.right[0] != 0) return bool
|
||||
return [config.left[0] + config.right[0]]i16
|
||||
}
|
||||
main proc() i16 {
|
||||
values Selected() :: [10, 10, 10, 12]
|
||||
return values[0] + values[1] + values[2] + values[3]
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"field_reflection_preserves_readonly_pointer_assignment": (
|
||||
"""
|
||||
Point :: struct { value i16 }
|
||||
update proc(point @Point) void { field!(point, "value") = 0 }
|
||||
main proc() i16 {
|
||||
point Point :: { value = 42 }
|
||||
update(&point)
|
||||
return point.value
|
||||
}
|
||||
""", b"", b"mutable pointee",
|
||||
),
|
||||
"field_reflection_rejects_nonstring_names": (
|
||||
"""
|
||||
main proc() i16 { return field!({ value = i16(42) }, 0) }
|
||||
""", b"", b"compile-time value",
|
||||
),
|
||||
"field_reflection_rejects_unknown_enum_members": (
|
||||
"""
|
||||
E :: enum { first }
|
||||
main proc() E { return field!(E, "missing") }
|
||||
""", b"", b"no such field",
|
||||
),
|
||||
"field_reflection_rejects_overflowing_tuple_ordinals": (
|
||||
"""
|
||||
main proc() i16 { return field!({i16(42)}, "18446744073709551616") }
|
||||
""", b"", b"no such field",
|
||||
),
|
||||
"field_reflection_rejects_runtime_names": (
|
||||
"""
|
||||
read proc(name string, value struct { count i16 }) i16 {
|
||||
return field!(value, name)
|
||||
}
|
||||
main proc() i16 { return read("count", { count = 42 }) }
|
||||
""", b"", b"compile-time value",
|
||||
),
|
||||
"field_reflection_rejects_unknown_fields": (
|
||||
"""
|
||||
main proc() i16 { return field!({ count = i16(42) }, "missing") }
|
||||
""", b"", b"no such field",
|
||||
),
|
||||
"field_reflection_rejects_noncanonical_tuple_names": (
|
||||
"""
|
||||
main proc() i16 { return field!({i16(42)}, "00") }
|
||||
""", b"", b"no such field",
|
||||
),
|
||||
"field_reflection_rejects_wrong_arity": (
|
||||
"""
|
||||
main proc() i16 { return field!(42) }
|
||||
""", b"", b"argument count",
|
||||
),
|
||||
"field_reflection_preserves_union_active_field_checks": (
|
||||
"""
|
||||
Choice :: union(enum) { value i16, empty void }
|
||||
main proc() i16 {
|
||||
choice Choice :: .empty
|
||||
return field!(choice, "value")
|
||||
}
|
||||
""", b"", b"not active",
|
||||
),
|
||||
"collection_iteration_borrows_array_and_reads_each_element": (
|
||||
"""
|
||||
main proc() [2]i16 {
|
||||
|
||||
Reference in New Issue
Block a user