feat: support source pointers and references

This commit is contained in:
2026-10-26 10:37:08 +01:00
parent 9966a1f523
commit 676f3666b4
17 changed files with 981 additions and 78 deletions
+6 -2
View File
@@ -93,6 +93,10 @@ array lengths currently support nonnegative integer literals, unary signs, `+`,
indices require `usize`; integer literals and flexible inferred values receive this context, while fixed signed integers do not convert implicitly. out-of-bounds access traps before loading or storing, including for empty arrays. reads evaluate the base before the index; indexed assignment evaluates its right-hand side before the destination, each once. leading nested assignment targets follow Bro's declaration ambiguity: write `(matrix[1])[0] = 9`, not `matrix[1][0] = 9`. [programs/array_sum.hon](programs/array_sum.hon) uses a `usize` range bound to index, mutate and sum an array; `./build/honey run programs/array_sum.hon` prints `17`.
data pointers use `@T` for one value and `*T` for many values, with `mut` after the pointer marker for writable pointees. `&value` borrows addressable initialized storage; postfix `^` reads or writes the pointee. pointer mutability is independent of binding mutability: an immutable pointer binding or parameter can mutate its `mut` pointee. mutable pointers weaken to readonly pointers with the same child type. address-taking also accepts an expected many-pointer type, such as `pointer *mut i16 :: &values[0]`; arbitrary temporaries are not addressable.
pointer-to-record field selection dereferences implicitly. pointers to arrays support bounded indexing, `.len`, and `.ptr`; `.ptr` returns a many-pointer and retains both pointer and array-element mutability restrictions. many-pointer indices require `usize` and caller-provided valid storage. pointers preserve alias identity through calls, returns, globals and aggregate copies; dereferenced aggregates still copy by value. `==` and `!=` compare locations. recursive records may contain pointers to themselves without recursive by-value layout. CLI results render pointer leaves as `<pointer>`. pointee lifetimes remain caller-managed; there is no borrow checker. function-pointer signatures, general pointer arithmetic, slices, optionals, pointer casts and allocator libc calls remain unimplemented.
records use `Point :: struct { x i16, y i16 = 2 }`, with comma- or newline-separated fields. named record declarations have nominal identity; two separately declared records are not interchangeable. construct them with `Point { x = 40 }` or a contextual keyed literal, such as `point Point := { x = 40 }`. omitted fields use their declared defaults; missing required fields, duplicate/unknown fields, incompatible values and recursive by-value layouts are diagnosed. explicit initializers run in source order, followed by omitted defaults in declaration order. an explicit value overrides its field's default without evaluating that default.
tuple types use `struct { i64, bool }`; tuple values use `{42, true}`, `{42,}` and `{}`. anonymous keyed records, such as `{ x = 40, ready = true }`, infer structural types. fields use `.x` or canonical tuple ordinals such as `.0`, including chained selections. mutable local roots permit field updates; constants, parameters and globals remain readonly, and traversed arrays still require mutable elements. construction, binding, assignment, arguments and returns copy nested records/tuples and arrays by value. fields can be read from returned temporaries; records containing no scalar leaves retain their logical shapes. CLI results print declaration-order values as `{value, value}`.
@@ -125,7 +129,7 @@ resolver scopes use `@std/hashmap.AutoHashMap(K, V)`, which shares `init`, `put`
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration.
type checking (`@source/sema/check`) runs after name resolution. `check.check` borrows the AST and binding table and returns owned per-node types, array/record/enum descriptors and construction mappings; query with `check.type_of`, `check.array_type`, `check.record_type`, `check.enum_type`, `check.field_index` and `check.initializer_field`, and release with `check.deinit`. declarations infer their initializer types, including forward references and function aliases; function types identify the declaration supplying the callable signature. annotations support builtin types, fixed arrays and local/imported records, enums, unions and distinct/alias types; type names are not ordinary runtime values. `void` is valid as a function result or tagged-union payload, not as a parameter or standalone stored value.
type checking (`@source/sema/check`) runs after name resolution. `check.check` borrows the AST and binding table and returns owned per-node types, pointer/array/record/enum descriptors and construction mappings; query with `check.type_of`, `check.pointer_type`, `check.array_type`, `check.record_type`, `check.enum_type`, `check.field_index` and `check.initializer_field`, and release with `check.deinit`. declarations infer their initializer types, including forward references and function aliases; function types identify the declaration supplying the callable signature. annotations support builtin types, data pointers, fixed arrays and local/imported records, enums, unions and distinct/alias types; type names are not ordinary runtime values. `void` is valid as a function result or tagged-union payload, not as a parameter or standalone stored value.
function fallthrough obligations are separate from body types. `check.fallthrough_diagnostic(&result, function)` accepts a function declaration's `NodeId` and returns its original missing-return diagnostic ID, or `null` when no obligation exists. nested body errors retain their original identities. lowering consumes this fact without interpreting diagnostic codes and places its trap only on a surviving fallthrough path; `check.deinit` releases the obligation storage.
@@ -143,7 +147,7 @@ type errors carry source spans and diagnostic identities; invalid operands propa
`lower.lower` turns the checked AST and binding table into an owned `@source/ir` module. functions, globals, local slots and instruction-result temporaries have explicit IDs; typed instructions execute in source evaluation order and basic blocks end in jump, conditional-branch, return or diagnostic-trap terminators. conditional arms join only when execution can continue; loops use condition/body/exit blocks and backedges. short-circuit expressions use branches and synthetic boolean local slots to merge results; call arguments are packed in the block reached after evaluating them. local slots and temporaries are function-wide, without SSA or phi nodes. literals are parsed and strings decoded during lowering; the module retains no AST, binding-table or source-buffer references. release it with `ir.deinit`; keep and reuse the diagnostic store used during lowering so diagnostic IDs remain valid. the IR is target-independent and is intended to feed later native backends as well as the current interpreter.
aggregate IR retains recursive array element types/counts/mutability and declaration-order record fields with flattened offsets. union descriptors additionally retain overlapping byte layouts and per-variant discriminants. typed construction, field/element addresses and load/store instructions use storage reserved per invocation; nested copies and accesses reuse that storage without per-iteration allocation. addresses are internal IR values, not source-language pointers. match lowering uses ordinary branches and local result slots; enum and distinct identities are checked before erasure to their runtime backing.
aggregate IR retains recursive array element types/counts/mutability and declaration-order record fields with flattened offsets. union descriptors additionally retain overlapping byte layouts and per-variant discriminants. typed construction, field/element addresses and load/store instructions use storage reserved per invocation; nested copies and accesses reuse that storage without per-iteration allocation. internal `address` values remain distinct from source `pointer` values: explicit bridge instructions preserve initialization checks, while pointer offsets retain typed element strides. native pointers occupy eight bytes; interpreter handles intern root storage identity and byte offset across frames, including union serialization. match lowering uses ordinary branches and local result slots; enum and distinct identities are checked before erasure to their runtime backing.
IR construction owns derived layout metadata. `ir.add_array`, `ir.add_record` and `ir.add_union` check shape capacity and compute their interpreter storage and native layouts; record/union shapes own copied field metadata. all aggregate kinds use `ArrayId` shapes and the existing backing segments. `ir.add_parameter` and `ir.add_local` create slots with disjoint aggregate backing. parameters precede other locals. `ir.emit` assigns fresh result IDs by default and maintains temporary counts, aggregate-result segments and address presence. hand-authored non-SSA IR may supply explicit result IDs; repeated aggregate results retain their shape and reuse the same segment. producers do not register backing separately or write derived counts, offsets or flags. construction failures leave logical contents unchanged, and no finalization pass or extra runtime allocation is required.
+4 -1
View File
@@ -9,7 +9,8 @@ selection through the interpreter and native backend. concrete 8/16/32/64-bit
integers, checked numeric conversions, records/tuples, construction/defaults,
enums, distinct/alias types, overlapping untagged/tagged unions, field reads/writes
and exhaustive statement/value matching work through both execution paths.
the driver retains frontend storage
source data pointers/references now support aliasing, mutation, calls and aggregate
storage through both execution paths. 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
package's production files and load its dependencies in separate scopes.
@@ -40,6 +41,8 @@ test discovery and FFI remain outside these completed slices.
- [x] records/tuples, typed/contextual/anonymous construction, field defaults and reads/writes, nested aggregate value copies, imported record types and constructors through interpreter/native execution.
- [x] nominal enums and distinct types, transparent declaration/type aliases, overlapping untagged/tagged unions, contextual payload construction, active-field checks and exhaustive scalar/enum/tagged-union matching through interpreter/native execution. pointer captures, inline specialization, sum composition and yielding value blocks remain with their respective later gates.
- [ ] memory representation: source pointers/references, mutable and sentinel slices, optionals, layout/cast builtins and allocator-required libc calls. internal IR array addresses are not source pointers. examples: `std/mem/mem.hon`, `std/arraylist/arraylist.hon`.
- [x] source data pointers/references: `@T`/`*T`, `mut`, address-taking and dereference, pointer-aware fields/indexing, array `.len`/`.ptr`, readonly weakening, location equality, recursive pointer-linked records and shared pointees across interpreter/native calls and aggregate copies.
- [ ] function-pointer signatures and general pointer arithmetic; mutable/sentinel slices, optionals, layout/cast builtins and allocator-required libc calls.
- [ ] fallible results and cleanup: error-set composition, `try`/`catch`, `defer`/`errdefer`; infrastructure errors remain distinct from diagnostic traps. examples: `source/main.hon:41`, `source/sema/check/check.hon:117`.
- [ ] generic specialization and compile-time execution/reflection sufficient for existing `ArrayList`, hash maps, `enummap`, `meta` and `io.print`; include type/value parameters and compile-time generated storage.
- [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
+44 -4
View File
@@ -1,9 +1,9 @@
#! grammar:
#! * type ..... -> identifier {'.' member} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
#! * type ..... -> identifier {'.' member} | ('@' | '*') ['mut'] type | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
#! * binding ... -> :: | :=
#! * literal .. -> int | float | string | 'true' | 'false'
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&') expr | expr '^' | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
#! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string
#! * block .... -> '{' {stmt} '}'
#! * arm ...... -> block | stmt (single statements become canonical blocks)
@@ -11,7 +11,7 @@
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
#! * stmt ..... -> decl | expr | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr arm ['else' arm] | 'while' expr arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
#! braceless guards/iterables require parentheses unless they are calls; else binds to the nearest if.
#! precedence (tightest first): calls/indices/selections, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
#! precedence (tightest first): calls/indices/selections/dereference, unary -/!/& higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
#! binary operators associate left; unary operators associate right.
import "@std/debug"
@@ -44,6 +44,7 @@ ExprKind :: enum {
call
range
array_type
pointer_type
array_literal
record_type
record_literal
@@ -82,7 +83,12 @@ Mutability :: enum {
constant, variable
}
UnaryOperator :: enum { negate, logical_not }
UnaryOperator :: enum {
negate
logical_not
address_of
dereference
}
BinaryOperator :: enum {
add, subtract
@@ -105,6 +111,7 @@ NodeKind :: enum {
expr_call
expr_range
expr_array_type
expr_pointer_type
expr_array_literal
expr_record_type
expr_record_literal
@@ -231,6 +238,12 @@ ArrayTypeView :: struct {
mutable bool
}
PointerTypeView :: struct {
element NodeId
mutable bool
many bool
}
RecordLiteralView :: struct {
span Span
type ?NodeId
@@ -482,6 +495,22 @@ add_array_type proc(
}, span)
}
add_pointer_type proc(
builder @mut Builder,
span Span,
element NodeId,
mutable, many bool,
) NodeId ! mem.AllocError {
mutable_flag :: if (mutable) u32(1) else u32(0)
many_flag :: if (many) u32(1) else u32(0)
return try add_node(builder, Node{
kind = .expr_pointer_type,
main = NodeData{ extra = mutable_flag },
data0 = NodeData{ extra = many_flag },
data1 = NodeData{ node_id = element },
}, span)
}
add_array_literal proc(builder @mut Builder, span Span, elements []NodeId) NodeId ! mem.AllocError {
start :: builder.storage.extra.items.len
for (elements) |element| _ = try add_extra(builder, u32(element))
@@ -1073,6 +1102,7 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_call: .call
.expr_range: .range
.expr_array_type: .array_type
.expr_pointer_type: .pointer_type
.expr_array_literal: .array_literal
.expr_record_type: .record_type
.expr_record_literal: .record_literal
@@ -1154,6 +1184,16 @@ array_type proc(tree @Tree, id NodeId) ArrayTypeView {
}
}
pointer_type proc(tree @Tree, id NodeId) PointerTypeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_pointer_type)
return PointerTypeView{
element = node.data1.node_id,
mutable = node.main.extra != 0,
many = node.data0.extra != 0,
}
}
array_element_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_array_literal)
+10
View File
@@ -89,6 +89,11 @@ Code :: enum {
unreachable_match_arm
invalid_match_capture
inactive_union_field
invalid_pointer_type
invalid_address_operand
invalid_dereference
invalid_pointer_assignment
invalid_pointer_access
}
Diagnostic :: struct {
@@ -199,5 +204,10 @@ messages :: $enummap.init(Code, []u8, {
unreachable_match_arm = "match arm is unreachable",
invalid_match_capture = "payload capture requires compatible nonvoid tagged union variants",
inactive_union_field = "tagged union field is not active",
invalid_pointer_type = "pointer requires a supported concrete pointee type",
invalid_address_operand = "address-of requires addressable storage",
invalid_dereference = "dereference requires a pointer",
invalid_pointer_assignment = "pointer assignment requires a mutable pointee",
invalid_pointer_access = "pointer does not refer to live accessible storage",
})
+163 -8
View File
@@ -42,6 +42,19 @@ Address :: struct {
scalar ?@mut ir.Value = null
initialized ?@mut bool = null
raw bool = false
# source handles retain root identity and native offsets, never frame-local address IDs.
root ?@mut ir.Value = null
root_type ir.Type = .invalid
root_array ir.ArrayId = ir.no_array
byte_offset usize = 0
}
@hide:file
Pointer :: struct {
root @mut ir.Value
root_type ir.Type
root_array ir.ArrayId
byte_offset usize
}
@hide:file
@@ -90,6 +103,8 @@ run proc(
globals[i].value = aggregate_view(global.type, global.array_type, aggregate_globals[global_offset..global_offset + cells])
global_offset += cells
}
pointers := arraylist.init(Pointer, allocator)
defer arraylist.deinit(&pointers)
frames := arraylist.init(Frame, allocator)
defer {
for (frames.items) |frame| free_frame(allocator, frame)
@@ -261,6 +276,9 @@ run proc(
cells = &[],
initialized = &local.initialized,
}
address.root = &local.value
address.root_type = address.type
address.root_array = address.array_type
if (ir.aggregate(address.type)) address.cells = local.value.data.elements
else address.scalar = &local.value
frames.items[frame_index].addresses[usize(instruction.destination)] = address
@@ -287,6 +305,9 @@ run proc(
array_type = instruction.array_type,
cells = &[],
}
address.root = &global.value
address.root_type = address.type
address.root_array = address.array_type
if (ir.aggregate(address.type)) address.cells = global.value.data.elements
else address.scalar = &global.value
frames.items[frame_index].addresses[usize(instruction.destination)] = address
@@ -305,10 +326,12 @@ run proc(
}
width :: if (base.raw) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
start :: usize(index) * width
address := Address{
type = instruction.pointee, array_type = instruction.array_type,
cells = base.cells[start..start + width], initialized = base.initialized, raw = base.raw,
}
address := base
address.type = instruction.pointee
address.array_type = instruction.array_type
address.cells = base.cells[start..start + width]
address.scalar = null
address.byte_offset += usize(index) * ir.storage_size(module, shape.element, shape.element_array)
if (!address.raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
@@ -329,14 +352,70 @@ run proc(
raw :: base.raw or shape.is_union
width :: if (raw) ir.storage_size(module, field.type, field.array_type) else if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
start :: if (raw) field.byte_offset else field.offset
address := Address{
type = instruction.pointee, array_type = instruction.array_type,
cells = base.cells[start..start + width], initialized = base.initialized, raw = raw,
}
address := base
address.type = instruction.pointee
address.array_type = instruction.array_type
address.cells = base.cells[start..start + width]
address.scalar = null
address.raw = raw
address.byte_offset += field.byte_offset
if (!raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
}
.pointer_from_address: {
id :: frames.items[frame_index].values[usize(instruction.left)].data.address
address :: frames.items[frame_index].addresses[usize(id)]
if address.initialized |initialized| {
if (!initialized^) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span)
}
value = try pointer_value(&pointers, Pointer{
root = address.root?,
root_type = address.root_type,
root_array = address.root_array,
byte_offset = address.byte_offset,
})
}
.pointer_address: {
handle :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
if (handle >= u64(pointers.items.len)) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
location :: pointers.items[usize(handle)]
root := Address{
type = location.root_type,
array_type = location.root_array,
cells = &[],
root = location.root,
root_type = location.root_type,
root_array = location.root_array,
}
if (ir.aggregate(root.type)) root.cells = root.root?^.data.elements
else root.scalar = root.root
if (project_pointer(module, root, location.byte_offset, instruction.pointee, instruction.array_type)) |address| {
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
} else {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
}
.pointer_offset: {
handle :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
if (handle >= u64(pointers.items.len)) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
address := pointers.items[usize(handle)]
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
stride :: ir.storage_size(module, instruction.pointee, instruction.array_type)
if (stride != 0 and index > u64(divtrunc!(maxval!(usize) - address.byte_offset, stride))) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
address.byte_offset += usize(index) * stride
if (address.byte_offset > ir.storage_size(module, address.root_type, address.root_array)) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
value = try pointer_value(&pointers, address)
}
.load_address, .store_address: {
id :: frames.items[frame_index].values[usize(instruction.left)].data.address
address :: frames.items[frame_index].addresses[usize(id)]
@@ -447,6 +526,11 @@ run proc(
.not_equal: left.data.boolean != right.data.boolean
else: unreachable
}
.pointer: result = match instruction.op {
.equal: left.data.unsigned == right.data.unsigned
.not_equal: left.data.unsigned != right.data.unsigned
else: unreachable
}
else: unreachable
}
value = ir.Value{ type = .bool, data = ir.ValueData{ boolean = result } }
@@ -459,6 +543,77 @@ run proc(
unreachable
}
@hide:file
pointer_value proc(pointers @mut arraylist.ArrayList(Pointer), address Pointer) ir.Value ! mem.AllocError {
# ponytail: linear location interning; use a location-keyed map if distinct-address workloads grow.
root_bits :: ptrcast!(@usize, &address.root)^
for (pointers.items) |location, handle| {
if (ptrcast!(@usize, &location.root)^ == root_bits and location.byte_offset == address.byte_offset) {
# refresh metadata if a later invocation reused a released frame's allocation.
pointers.items[handle] = address
return ir.Value{ type = .pointer, data = ir.ValueData{ unsigned = u64(handle) } }
}
}
handle :: pointers.items.len
try arraylist.append(pointers, address)
return ir.Value{ type = .pointer, data = ir.ValueData{ unsigned = u64(handle) } }
}
@hide:file
project_pointer proc(
module @ir.Module,
base Address,
offset usize,
type ir.Type,
array_type ir.ArrayId,
) ?Address {
if (offset == 0 and base.type == type and base.array_type == array_type) return base
if (!ir.aggregate(base.type)) return null
shape :: module.arrays.items[usize(base.array_type)]
if (base.raw or shape.is_union) {
width :: ir.storage_size(module, type, array_type)
if (offset > base.cells.len or width > base.cells.len - offset) return null
address := base
address.type = type
address.array_type = array_type
address.cells = base.cells[offset..offset + width]
address.scalar = null
address.raw = true
address.byte_offset += offset
return address
}
if (shape.kind == .array) {
stride :: ir.storage_size(module, shape.element, shape.element_array)
index :: if (stride == 0) usize(0) else divtrunc!(offset, stride)
if (index >= shape.len) return null
width :: element_cells(module, shape)
start :: index * width
child := base
child.type = shape.element
child.array_type = shape.element_array
child.cells = base.cells[start..start + width]
child.scalar = null
child.byte_offset += index * stride
if (!ir.aggregate(child.type)) child.scalar = &child.cells[0]
return project_pointer(module, child, offset - index * stride, type, array_type)
}
for (shape.fields) |field| {
if (offset < field.byte_offset) continue
relative :: offset - field.byte_offset
if (relative > ir.storage_size(module, field.type, field.array_type)) continue
width :: if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
child := base
child.type = field.type
child.array_type = field.array_type
child.cells = base.cells[field.offset..field.offset + width]
child.scalar = null
child.byte_offset += field.byte_offset
if (!ir.aggregate(child.type)) child.scalar = &child.cells[0]
if (project_pointer(module, child, relative, type, array_type)) |address| return address
}
return null
}
@hide:file
push_frame proc(
allocator mem.Allocator,
+9 -2
View File
@@ -24,6 +24,7 @@ Type :: enum {
array
record
address
pointer
i8, i16, i32, isize
u8, u16, u32, u64, usize
}
@@ -74,6 +75,7 @@ integer_min proc(type Type) i64 {
ValueData :: union {
integer i64
# source pointers use run-wide handles; internal addresses use frame-local IDs below.
unsigned u64
real f64
boolean bool
@@ -141,6 +143,9 @@ Op :: enum {
field_address
load_address
store_address
pointer_from_address
pointer_address
pointer_offset
negate
convert
logical_not
@@ -160,6 +165,7 @@ Op :: enum {
Instruction :: struct {
op Op
type Type = .void
# pointer operations describe their pointee here, not aggregate result storage.
array_type ArrayId = no_array
pointee Type = .invalid
span diagnostics.Span = diagnostics.Span{ start = 0, end = 0 }
@@ -395,7 +401,7 @@ storage_alignment proc(module @Module, type Type, shape ArrayId) usize {
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
bits :: integer_bits(type)
if (bits != 0) return divtrunc!(bits, 8)
return if (type == .float or type == .string or type == .function) 8 else 1
return if (type == .float or type == .string or type == .function or type == .pointer) 8 else 1
}
storage_size proc(module @Module, type Type, shape ArrayId) usize {
@@ -403,7 +409,7 @@ storage_size proc(module @Module, type Type, shape ArrayId) usize {
bits :: integer_bits(type)
if (bits != 0) return divtrunc!(bits, 8)
return match type {
.float, .function: 8
.float, .function, .pointer: 8
.string: 16
.bool: 1
.void: 0
@@ -457,6 +463,7 @@ write_storage proc(module @Module, cells []mut Value, value Value) void {
.bool: bits = if (value.data.boolean) 1 else 0
.string: bits = u64(value.data.string)
.function: bits = u64(value.data.function)
.pointer: bits = value.data.unsigned
.void: return
else: bits = ptrcast!(@u64, &value.data.integer)^
}
+2
View File
@@ -295,6 +295,8 @@ scan proc(
',': .comma
'|': .pipe
'@': .at
'&': .ampersand
'^': .caret
'(': .open_paren
')': .close_paren
'[': .open_bracket
+2 -1
View File
@@ -48,7 +48,8 @@ TokenKind :: enum {
star, slash
range, range_inclusive
pipe, at
at, ampersand, caret
pipe
# logical
bang
+21
View File
@@ -168,6 +168,27 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
}
.pointer_from_address: {
address :: try load(state, instruction.left)
flag :: fresh(state)
position :: location(state.line_starts, instruction.span)
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, address})
try io.print(" call void @check_init(ptr %r{}, i1 true, i64 {}, i64 {}, i64 {})\n", output, {flag, usize(instruction.span.source), position.line, position.column})
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {result, address})
}
.pointer_address: {
pointer :: try load(state, instruction.left)
address :: fresh(state)
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
}
.pointer_offset: {
pointer :: try load(state, instruction.left)
index :: try load(state, instruction.right)
try io.print(" %r{} = getelementptr ", output, {result})
try write_type(output, state.module, Type{ kind = instruction.pointee, shape = instruction.array_type })
try io.print(", ptr %r{}, i64 %r{}\n", output, {pointer, index})
}
.load_address, .store_address: {
address :: try load(state, instruction.left)
pointer :: fresh(state)
+2 -1
View File
@@ -133,7 +133,7 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
.float: try io.write_all(output, "double")
.bool: try io.write_all(output, "i1")
.string: try io.write_all(output, "%string")
.function: try io.write_all(output, "ptr")
.function, .pointer: try io.write_all(output, "ptr")
.address: try io.write_all(output, "%address")
.array: {
shape :: module.arrays.items[usize(type.shape)]
@@ -275,6 +275,7 @@ print_call proc(output io.Writer, module @ir.Module, type Type, operand usize) v
.float: try io.print(" call void @print_float(double %r{})\n", output, {operand})
.bool: try io.print(" call void @print_bool(i1 %r{})\n", output, {operand})
.string: try io.print(" call void @print_string(%string %r{})\n", output, {operand})
.pointer: try io.write_all(output, " call void @out(i32 1, ptr @pointer_text, i64 9)\n")
.array, .record: {
try io.print(" call void @print_array{}(", output, {u32(type.shape)})
try write_type(output, module, type)
+1
View File
@@ -37,6 +37,7 @@ runtime ::
`@fmt_float = private constant [6 x i8] c"%.17g\00"
`@true = private constant [4 x i8] c"true"
`@false = private constant [5 x i8] c"false"
`@pointer_text = private constant [9 x i8] c"<pointer>"
`@colon = private constant [1 x i8] c":"
`@error = private constant [9 x i8] c": error: "
`@runtime_trap = private constant [16 x i8] c": runtime trap: "
+99 -5
View File
@@ -149,6 +149,7 @@ lower_type proc(state @State, type typecheck.Type) ir.Type {
.u64: .u64
.array: .array
.record: .record
.pointer: .pointer
.float: .float
.bool: .bool
.string: .string
@@ -590,6 +591,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
.assignment_stmt: {
assignment :: ast.assignment_stmt(tree, id)
if (ast.expr_kind(tree, assignment.target) == .index
or ast.expr_kind(tree, assignment.target) == .unary
or (ast.expr_kind(tree, assignment.target) == .selection
and sema.binding(state.resolution, assignment.target).kind == .none)) {
value :: try lower_expression(state, assignment.value)
@@ -924,10 +926,20 @@ lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.Va
@hide:file
is_place proc(state @State, id ast.NodeId) bool {
match ast.expr_kind(state.tree, id) {
.index: return is_place(state, ast.index_expr(state.tree, id).base)
.unary: return ast.unary_expr(state.tree, id).operator == .dereference
.index: {
base :: ast.index_expr(state.tree, id).base
return typecheck.type_of(state.checked, base).kind == .pointer or is_place(state, base)
}
.selection: {
if (sema.binding(state.resolution, id).kind == .none) {
return is_place(state, ast.selection_expr(state.tree, id).base)
base :: ast.selection_expr(state.tree, id).base
checked :: typecheck.type_of(state.checked, base)
if (checked.kind == .pointer) {
pointer :: typecheck.pointer_type(state.checked, checked.pointer_type)
return pointer.element.kind == .record
}
return is_place(state, base)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return false
@@ -945,14 +957,37 @@ is_place proc(state @State, id ast.NodeId) bool {
}
}
@hide:file
pointer_address proc(
state @mut State, pointer ir.ValueId, element typecheck.Type, span diagnostics.Span,
) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
return try emit(state, ir.Instruction{
op = .pointer_address, type = .address, left = pointer,
pointee = lower_type(state, element), array_type = try array_shape(state, element), span = span,
})
}
@hide:file
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id)
checked :: typecheck.type_of(state.checked, id)
if (ast.expr_kind(state.tree, id) == .unary) {
expression :: ast.unary_expr(state.tree, id)
pointer :: try lower_expression(state, expression.operand)
if (state.terminated or trap_type(state, id)) return ir.no_value
return try pointer_address(state, pointer, checked, span)
}
if (ast.expr_kind(state.tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none) {
expression :: ast.selection_expr(state.tree, id)
base :: try lower_place(state, expression.base)
base_type :: typecheck.type_of(state.checked, expression.base)
base := ir.no_value
if (base_type.kind == .pointer) {
pointer :: try lower_expression(state, expression.base)
if (state.terminated or trap_type(state, id)) return ir.no_value
element :: typecheck.pointer_type(state.checked, base_type.pointer_type).element
base = try pointer_address(state, pointer, element, span)
} else base = try lower_place(state, expression.base)
if (state.terminated or trap_type(state, id)) return ir.no_value
return try emit(state, ir.Instruction{
op = .field_address,
@@ -966,7 +1001,23 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
}
if (ast.expr_kind(state.tree, id) == .index) {
expression :: ast.index_expr(state.tree, id)
base :: try lower_place(state, expression.base)
base_type :: typecheck.type_of(state.checked, expression.base)
base := ir.no_value
if (base_type.kind == .pointer) {
pointer :: try lower_expression(state, expression.base)
if (state.terminated or trap_type(state, id)) return ir.no_value
descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type)
if (descriptor.many) {
index :: try lower_expression(state, expression.index)
if (state.terminated) return ir.no_value
offset :: try emit(state, ir.Instruction{
op = .pointer_offset, type = .pointer, left = pointer, right = index,
pointee = lower_type(state, checked), array_type = try array_shape(state, checked), span = span,
})
return try pointer_address(state, offset, checked, span)
}
base = try pointer_address(state, pointer, descriptor.element, span)
} else base = try lower_place(state, expression.base)
if (state.terminated) return ir.no_value
index :: try lower_expression(state, expression.index)
if (state.terminated or trap_type(state, id)) return ir.no_value
@@ -1014,7 +1065,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
# ranges have no physical value; only lower_for decomposes an iterable.
if (!trap_type(state, id)) unreachable
}
.array_type, .record_type, .enum_type, .union_type, .distinct_type: {
.array_type, .pointer_type, .record_type, .enum_type, .union_type, .distinct_type: {
if (!trap_type(state, id)) {
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
}
@@ -1091,6 +1142,27 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
.identifier, .selection: {
if (ast.expr_kind(tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none) {
selection :: ast.selection_expr(tree, id)
base_type :: typecheck.type_of(state.checked, selection.base)
if (base_type.kind == .pointer) {
descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type)
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))
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) },
}, span)
if (mem.eql(member, "ptr")) {
zero :: try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = 0 } }, span)
return try emit(state, ir.Instruction{
op = .pointer_offset, type = .pointer, left = pointer, right = zero,
pointee = lower_type(state, array.element), array_type = try array_shape(state, array.element), span = span,
})
}
}
}
if (is_place(state, id)) {
address :: try lower_place(state, id)
if (state.terminated) return ir.no_value
@@ -1120,6 +1192,27 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
}
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .address_of) {
if (!is_place(state, expression.operand)) {
_ = try lower_expression(state, expression.operand)
if (!state.terminated) _ = trap_type(state, id)
return ir.no_value
}
address :: try lower_place(state, expression.operand)
if (state.terminated or trap_type(state, id)) return ir.no_value
element :: typecheck.type_of(state.checked, expression.operand)
return try emit(state, ir.Instruction{
op = .pointer_from_address, type = .pointer, left = address,
pointee = lower_type(state, element), array_type = try array_shape(state, element), span = span,
})
}
if (expression.operator == .dereference) {
address :: try lower_place(state, id)
if (state.terminated) return ir.no_value
return try emit(state, ir.Instruction{
op = .load_address, type = type, array_type = shape, left = address, span = span,
})
}
if (expression.operator == .negate and ir.integer_bits(type) != 0 and !ir.unsigned_integer(type) and ast.expr_kind(tree, expression.operand) == .literal_int) {
text :: ast.source_text(tree, ast.node_span(tree, expression.operand))
magnitude_limit :: ir.integer_max(type) + 1
@@ -1134,6 +1227,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
op ir.Op :: match expression.operator {
.negate: .negate
.logical_not: .logical_not
.address_of, .dereference: unreachable
}
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
}
+1
View File
@@ -208,6 +208,7 @@ print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.
.float: try io.print("{d}", output, {value.data.real})
.bool: try io.print("{}", output, {value.data.boolean})
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
.pointer: try io.write_all(output, "<pointer>")
.array, .record: {
shape :: module.arrays.items[usize(value.array_type)]
count :: if (value.type == .array) shape.len else shape.fields.len
+53 -8
View File
@@ -240,6 +240,8 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
or next_n_is(state, 1, .struct)
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
or next_n_is(state, 1, .distinct)
or next_n_is(state, 1, .at)
or (next_n_is(state, 1, .star) and pointer_binding_starts(state))
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
or next_n_is(state, 1, .double_colon)
or next_n_is(state, 1, .colon_equal)
@@ -254,7 +256,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
.return: return try parse_return(state, allow_else)
.break, .continue: return try parse_loop_control(state, allow_else)
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct,
.enum, .union, .distinct, .alias, .dot, .match: {
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star: {
expression :: try parse_expr(state)
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
return try ast.add_expression_stmt(state.builder, expression)
@@ -603,6 +605,18 @@ parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
@hide:file
parse_type proc(state @mut State) ast.NodeId ! ParseError {
if (next_is(state, .at) or next_is(state, .star)) {
token :: current(state)
state.next_token += 1
mutable :: next_is(state, .mut)
if (mutable) state.next_token += 1
element :: try parse_type(state)
return try ast.add_pointer_type(state.builder, ast.Span{
start = token.span.start,
end = ast.builder_span(state.builder, element).end,
source = token.span.source,
}, element, mutable, token.kind == .star)
}
if next_is(state, .open_bracket) {
open :: try consume(state, .open_bracket)
count ?ast.NodeId := null
@@ -653,6 +667,7 @@ starts_type proc(kind lexer.TokenKind) bool {
return (
kind == .ident or kind == .open_bracket or kind == .struct
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
or kind == .at or kind == .star
)
}
@@ -979,11 +994,7 @@ parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseEr
value ?ast.NodeId := null
named :: member_name(current(state).kind) and (
(!declaration and next_n_is(state, 1, .equal))
or (declaration and (
next_n_is(state, 1, .ident) or next_n_is(state, 1, .open_bracket)
or next_n_is(state, 1, .struct) or next_n_is(state, 1, .enum)
or next_n_is(state, 1, .union) or next_n_is(state, 1, .distinct)
))
or (declaration and starts_type(state.tokens[state.next_token + 1].kind))
)
if (named) {
token :: if (declaration) try consume(state, .ident) else try consume_member(state, false)
@@ -1067,7 +1078,11 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
state.next_token += 1
operand :: try parse_expr_bp(state, right_bp)
operand_span :: ast.builder_span(state.builder, operand)
operator ast.UnaryOperator :: if (token.kind == .bang) .logical_not else .negate
operator ast.UnaryOperator :: match token.kind {
.bang: .logical_not
.ampersand: .address_of
else: .negate
}
yield try ast.add_unary(
state.builder,
ast.Span{
@@ -1086,6 +1101,16 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
while true {
skip_continuation_newlines(state)
token = current(state)
if (token.kind == .caret) {
state.next_token += 1
left = try ast.add_unary(state.builder, ast.Span{
start = state.tokens[expression_start].span.start,
end = token.span.end,
source = token.span.source,
}, .dereference, left)
left_grouped = false
continue
}
if (token.kind == .open_paren) {
left = try parse_call(state, left, state.tokens[expression_start].span.start)
left_grouped = false
@@ -1324,6 +1349,23 @@ invalid_array_literal proc(state @mut State, start, end u32) ast.NodeId ! ParseE
}, diagnostic)
}
@hide:file
pointer_binding_starts proc(state @State) bool {
depth := usize(0)
for (state.next_token + 2)..(state.tokens.len) |i| {
kind :: state.tokens[i].kind
if (kind == .open_curly or kind == .open_bracket or kind == .open_paren) depth += 1
if (kind == .close_curly or kind == .close_bracket or kind == .close_paren) {
if (depth == 0) return false
depth -= 1
}
if (depth != 0) continue
if (kind == .double_colon or kind == .colon_equal) return true
if (kind == .newline or kind == .eof or kind == .equal) return false
}
return false
}
@hide:file
named_binding_starts proc(state @State) bool {
return (
@@ -1331,6 +1373,8 @@ named_binding_starts proc(state @State) bool {
and (
next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc)
or next_n_is(state, 1, .at)
or (next_n_is(state, 1, .star) and pointer_binding_starts(state))
)
)
}
@@ -1339,6 +1383,7 @@ named_binding_starts proc(state @State) bool {
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state)
match token.kind {
.at, .star: return try parse_type(state)
.struct: return try parse_record_type(state)
.enum: return try parse_enum_type(state)
.union: return try parse_union_type(state)
@@ -1620,7 +1665,7 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
@hide:file
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
return match kind {
.minus, .bang: BindingPowerScalar(50)
.minus, .bang, .ampersand: BindingPowerScalar(50)
else: null
}
}
+302 -45
View File
@@ -18,6 +18,7 @@ TypeKind :: enum {
function
range
array
pointer
record
enumeration
type_value
@@ -72,11 +73,21 @@ ArrayType :: struct {
mutable bool
}
PointerId :: distinct u32
no_pointer :: maxval!(PointerId)
PointerType :: struct {
element Type
mutable bool
many bool
}
Type :: struct {
kind TypeKind = .none
function ast.NodeId = ast.NodeId(maxval!(u32))
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
array_type ArrayId = no_array
pointer_type PointerId = no_pointer
range_element TypeKind = .none
record_type RecordId = no_record
enum_type EnumId = no_enum
@@ -88,6 +99,7 @@ Type :: struct {
Storage :: struct {
types []mut Type
arrays arraylist.ArrayList(ArrayType)
pointers arraylist.ArrayList(PointerType)
records arraylist.ArrayList(RecordType)
enums arraylist.ArrayList(EnumType)
backings []mut Type
@@ -151,6 +163,7 @@ Cell :: struct {
producer ast.NodeId = ast.NodeId(maxval!(u32))
capture ast.NodeId = ast.NodeId(maxval!(u32))
record_checking bool = false
record_pointer_depth usize = 0
nominal bool = false
match_pattern bool = false
first_edge usize = no_index
@@ -179,6 +192,7 @@ State :: struct {
groups []mut GroupLink
edges arraylist.ArrayList(Edge)
arrays arraylist.ArrayList(ArrayType)
pointers arraylist.ArrayList(PointerType)
records arraylist.ArrayList(RecordType)
initializers arraylist.ArrayList(RecordInitializer)
enums arraylist.ArrayList(EnumType)
@@ -191,6 +205,7 @@ State :: struct {
slots []mut usize
collecting_assignment bool = false
has_assignments bool = false
pointer_depth usize = 0
}
#! borrows the AST, resolution, and diagnostic store only during checking.
@@ -235,6 +250,7 @@ check proc(
groups = groups,
edges = arraylist.init(allocator),
arrays = arraylist.init(allocator),
pointers = arraylist.init(allocator),
records = arraylist.init(allocator),
initializers = arraylist.init(allocator),
slots = slots,
@@ -247,6 +263,7 @@ check proc(
defer arraylist.deinit(&state.edges)
defer arraylist.deinit(&state.variants)
errdefer arraylist.deinit(&state.arrays)
errdefer arraylist.deinit(&state.pointers)
errdefer {
for (state.enums.items) |descriptor| mem.free(allocator, descriptor.members)
arraylist.deinit(&state.enums)
@@ -278,7 +295,7 @@ check proc(
binding :: sema.binding(resolution, target)
if (binding.kind == .invalid) {
cells[i].type = invalid_type(binding.data.diagnostic)
} else if (binding.kind == .declaration and !mutable_target(&state, target)) {
} else if (binding.kind == .declaration and !mutable_target(&state, target, false)) {
cells[i].type = invalid_type(try diagnostics.add(
diagnostic_store, .invalid_assignment_target, ast.node_span(tree, target),
))
@@ -511,6 +528,7 @@ check proc(
return Result{ storage = Storage{
types = types,
arrays = state.arrays,
pointers = state.pointers,
records = state.records,
initializers = state.initializers,
enums = state.enums,
@@ -544,6 +562,10 @@ array_type proc(result @Result, id ArrayId) ArrayType {
return result.storage.arrays.items[usize(id)]
}
pointer_type proc(result @Result, id PointerId) PointerType {
return result.storage.pointers.items[usize(id)]
}
record_type_count proc(result @Result) usize {
return result.storage.records.items.len
}
@@ -621,6 +643,7 @@ initializer_field proc(result @Result, literal ast.NodeId, index usize) ast.Node
deinit proc(result @mut Result) void {
mem.free(result.storage.allocator, result.storage.types)
arraylist.deinit(&result.storage.arrays)
arraylist.deinit(&result.storage.pointers)
for (result.storage.enums.items) |descriptor| mem.free(result.storage.allocator, descriptor.members)
arraylist.deinit(&result.storage.enums)
mem.free(result.storage.allocator, result.storage.backings)
@@ -741,6 +764,12 @@ collect_invalid proc(state @mut State, id ast.NodeId) void {
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
state.cells[usize(id)].role = .annotation
if (ast.expr_kind(state.tree, id) == .pointer_type) {
type :: try check_pointer_type(state, id)
state.types[usize(id)] = type
state.cells[usize(id)].type = type
return type
}
if (type_syntax(ast.expr_kind(state.tree, id))) {
type :: try check_type_expression(state, id, false)
state.types[usize(id)] = type
@@ -1047,7 +1076,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
if (expression.operator == .negate) state.cells[usize(id)].producer = id
if (expression.operator == .logical_not) {
state.cells[usize(id)].type = Type{ kind = .bool }
} else {
} else if (expression.operator == .negate) {
state.cells[usize(id)].numeric = true
}
operand :: expression.operand
@@ -1106,7 +1135,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
.array_type: {
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
}
.record_type, .union_type, .enum_type, .distinct_type: {
.record_type, .union_type, .enum_type, .distinct_type, .pointer_type: {
type := try check_type_expression(state, id, state.cells[usize(id)].nominal)
if (type.kind != .invalid) {
type.value_kind = type.kind
@@ -1240,7 +1269,8 @@ bool_equality proc(state @mut State, id ast.NodeId) bool {
return (
(expression.operator == .equal or expression.operator == .not_equal)
and ((left.kind == .bool and right.kind == .bool)
or left.kind == .enumeration or right.kind == .enumeration)
or left.kind == .enumeration or right.kind == .enumeration
or left.kind == .pointer or right.kind == .pointer)
)
}
@@ -1308,6 +1338,14 @@ numeric_constraint proc(state @mut State, expression, operand, operation ast.Nod
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(id)].kind == .invalid) return
index :: root(state, usize(id))
if (expected.kind == .pointer and state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .unary
and ast.unary_expr(state.tree, id).operator == .address_of) {
state.cells[index].expected = expected
try demand(state, ast.unary_expr(state.tree, id).operand,
state.pointers.items[usize(expected.pointer_type)].element, code)
return
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
state.cells[index].expected = expected
for 0..ast.match_arm_count(state.tree, id) |i| {
@@ -1404,24 +1442,78 @@ require_bool proc(state @mut State, operand ast.NodeId) void ! (mem.AllocError |
}
@hide:file
mutable_target proc(state @State, id ast.NodeId) bool {
if (ast.expr_kind(state.tree, id) == .index) {
view :: ast.index_expr(state.tree, id)
base :: state.types[usize(view.base)]
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base)
mutable_target proc(state @mut State, id ast.NodeId, allow_global bool) bool {
kind :: ast.expr_kind(state.tree, id)
if (kind == .unary) {
view :: ast.unary_expr(state.tree, id)
base :: group_type(state, view.operand)
return (
view.operator == .dereference and base.kind == .pointer
and state.pointers.items[usize(base.pointer_type)].mutable
)
}
if (ast.expr_kind(state.tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none) {
if (kind == .index) {
view :: ast.index_expr(state.tree, id)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.mutable and (pointer.many
or (pointer.element.kind == .array and state.arrays.items[usize(pointer.element.array_type)].mutable))
}
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base, allow_global)
}
if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
view :: ast.selection_expr(state.tree, id)
return state.types[usize(view.base)].kind == .record and mutable_target(state, view.base)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.element.kind == .record and pointer.mutable
}
return base.kind == .record and mutable_target(state, view.base, allow_global)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return false
target :: binding.data.declaration
if (state.cells[usize(target)].role != .declaration or !state.cells[usize(target)].local) return false
if (state.cells[usize(target)].role != .declaration or (!allow_global and !state.cells[usize(target)].local)) return false
return ast.statement_decl(state.tree, target).mutability == .variable
}
@hide:file
indexable_type proc(state @State, type Type) bool {
if (type.nominal != ast.NodeId(maxval!(u32))) return false
if (type.kind == .array) return true
if (type.kind != .pointer) return false
pointer :: state.pointers.items[usize(type.pointer_type)]
return pointer.many or pointer.element.kind == .array
}
@hide:file
addressable_target proc(state @mut State, id ast.NodeId) bool {
kind :: ast.expr_kind(state.tree, id)
if (kind == .unary) {
view :: ast.unary_expr(state.tree, id)
return view.operator == .dereference and group_type(state, view.operand).kind == .pointer
}
if (kind == .index) {
view :: ast.index_expr(state.tree, id)
base :: group_type(state, view.base)
return indexable_type(state, base) and (base.kind == .pointer or addressable_target(state, view.base))
}
if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
view :: ast.selection_expr(state.tree, id)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.element.kind == .record
}
return base.kind == .record and addressable_target(state, view.base)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return false
role :: state.cells[usize(binding.data.declaration)].role
return role == .declaration or role == .parameter or role == .loop_capture
}
@hide:file
same_signature proc(state @State, left, right ast.NodeId) bool {
tree :: state.tree
@@ -1437,11 +1529,22 @@ same_signature proc(state @State, left, right ast.NodeId) bool {
return true
}
@hide:file
assignable_type proc(state @State, actual, expected Type) bool {
if (same_type(state, actual, expected)) return true
if (actual.kind != .pointer or expected.kind != .pointer or actual.nominal != expected.nominal) return false
a :: state.pointers.items[usize(actual.pointer_type)]
b :: state.pointers.items[usize(expected.pointer_type)]
if (a.many != b.many or (!a.mutable and b.mutable)) return false
if (b.mutable) return same_type(state, a.element, b.element)
return assignable_type(state, a.element, b.element)
}
@hide:file
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
actual :: state.types[usize(value)]
if (actual.kind == .invalid or expected.kind == .invalid) return
if (!same_type(state, actual, expected)) {
if (!assignable_type(state, actual, expected)) {
try fail(state, value, .type_mismatch, ast.node_span(state.tree, value))
}
}
@@ -1465,11 +1568,18 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
assignment :: ast.assignment_stmt(tree, id)
target_type :: state.types[usize(assignment.target)]
if (target_type.kind == .invalid) return
if (mutable_target(state, assignment.target)) {
if (mutable_target(state, assignment.target, false)) {
try compatible(state, assignment.value, target_type)
} else {
code :: if (ast.expr_kind(tree, assignment.target) == .index)
diagnostics.Code.invalid_index_assignment else diagnostics.Code.invalid_assignment_target
kind :: ast.expr_kind(tree, assignment.target)
code := if (kind == .index) diagnostics.Code.invalid_index_assignment else diagnostics.Code.invalid_assignment_target
if (kind == .unary and ast.unary_expr(tree, assignment.target).operator == .dereference) {
code = .invalid_pointer_assignment
} else if (kind == .index and state.types[usize(ast.index_expr(tree, assignment.target).base)].kind == .pointer) {
code = .invalid_pointer_assignment
} else if (kind == .selection and state.types[usize(ast.selection_expr(tree, assignment.target).base)].kind == .pointer) {
code = .invalid_pointer_assignment
}
try fail(state, id, code, ast.node_span(tree, assignment.target))
}
}
@@ -1481,7 +1591,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
actual :: state.types[usize(value)]
if (actual.kind == .invalid) {
state.types[usize(id)] = actual
} else if (!same_type(state, actual, expected)) {
} else if (!assignable_type(state, actual, expected)) {
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
state.types[usize(id)] = state.types[usize(value)]
}
@@ -1497,6 +1607,19 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
try require_bool(state, expression.operand)
return
}
if (expression.operator == .address_of) {
if (!addressable_target(state, expression.operand)) {
try fail(state, id, .invalid_address_operand, expression.span)
}
return
}
if (expression.operator == .dereference) {
if (state.types[usize(expression.operand)].kind != .invalid
and state.types[usize(expression.operand)].kind != .pointer) {
try fail(state, id, .invalid_dereference, expression.span)
}
return
}
operand :: state.types[usize(expression.operand)]
if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) {
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
@@ -1513,8 +1636,8 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
right :: state.types[usize(expression.right)]
if (left.kind == .invalid or right.kind == .invalid) return
if ((expression.operator == .equal or expression.operator == .not_equal)
and (left.kind == .bool or left.kind == .enumeration)
and same_type(state, left, right)) return
and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer)
and (assignable_type(state, left, right) or assignable_type(state, right, left))) return
if (!numeric_kind(left.kind) or !numeric_kind(right.kind) or !same_type(state, left, right)) {
try fail(state, id, .invalid_operand, expression.span)
} else if (expression.operator == .divide and left.kind != .float) {
@@ -1572,8 +1695,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
if (index.kind != .invalid and (index.kind != .usize or index.nominal != ast.NodeId(maxval!(u32)))) {
try fail(state, view.index, .invalid_index_type, ast.node_span(tree, view.index))
}
if (base.kind != .invalid
and (base.kind != .array or base.nominal != ast.NodeId(maxval!(u32)))) {
if (base.kind != .invalid and !indexable_type(state, base)) {
try fail(state, id, .not_indexable, ast.node_span(tree, view.base))
}
}
@@ -1758,6 +1880,35 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
}
}
@hide:file
intern_pointer proc(state @mut State, descriptor PointerType) Type ! mem.AllocError {
for state.pointers.items |existing, i| {
if (existing.mutable == descriptor.mutable and existing.many == descriptor.many
and same_type(state, existing.element, descriptor.element)) {
return Type{ kind = .pointer, pointer_type = PointerId(i) }
}
}
try arraylist.append(&state.pointers, descriptor)
return Type{ kind = .pointer, pointer_type = PointerId(state.pointers.items.len - 1) }
}
@hide:file
check_pointer_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
view :: ast.pointer_type(state.tree, id)
state.pointer_depth += 1
defer state.pointer_depth -= 1
element :: try check_annotation(state, view.element, true)
if (element.kind == .invalid) return element
binding :: sema.binding(state.resolution, view.element)
if (element.kind == .none or element.kind == .void or element.kind == .range
or element.kind == .type_value or element.kind == .function
or has_inferred_count(state, element)
or (binding.kind == .builtin and (binding.data.builtin == .int or binding.data.builtin == .float))) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_pointer_type, ast.node_span(state.tree, id)))
}
return try intern_pointer(state, PointerType{ element = element, mutable = view.mutable, many = view.many })
}
@hide:file
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
for state.arrays.items |existing, i| {
@@ -1775,6 +1926,11 @@ same_type proc(state @State, left, right Type) bool {
if (left.kind != right.kind) return false
if (left.nominal != right.nominal) return false
if (left.kind == .enumeration) return left.enum_type == right.enum_type
if (left.kind == .pointer) {
a :: state.pointers.items[usize(left.pointer_type)]
b :: state.pointers.items[usize(right.pointer_type)]
return a.mutable == b.mutable and a.many == b.many and same_type(state, a.element, b.element)
}
if (left.kind == .type_value) {
a := left
b := right
@@ -1881,6 +2037,33 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
if (state.cells[index].type.kind == .invalid) return state.cells[index].type
state.cells[index].visiting = true
defer state.cells[index].visiting = false
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .unary) {
view :: ast.unary_expr(state.tree, id)
if (view.operator == .address_of or view.operator == .dereference) {
operand :: try infer_shape(state, view.operand, defaults)
if (operand.kind == .none) return Type{}
if (operand.kind == .invalid) {
seed(state, id, operand)
return operand
}
if (view.operator == .dereference) {
if (operand.kind != .pointer) {
try fail(state, id, .invalid_dereference, view.span)
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
element :: state.pointers.items[usize(operand.pointer_type)].element
seed(state, id, element)
return element
}
mutable :: mutable_target(state, view.operand, true)
expected :: state.cells[index].expected
many :: expected.kind == .pointer and state.pointers.items[usize(expected.pointer_type)].many
type :: try intern_pointer(state, PointerType{ element = operand, mutable = mutable, many = many })
seed(state, id, type)
return type
}
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
return try infer_match(state, id, defaults)
}
@@ -1899,7 +2082,28 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
enum_shape :: try check_annotation(state, view.base, false)
if (enum_shape.kind == .enumeration) return try select_enum_member(state, id, view.member, enum_shape)
}
base :: try infer_shape(state, view.base, defaults)
base := try infer_shape(state, view.base, defaults)
if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)]
if (!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))
if (mem.eql(member, "len")) {
type :: Type{ kind = .usize }
seed(state, id, type)
return type
}
if (mem.eql(member, "ptr")) {
array :: state.arrays.items[usize(pointer.element.array_type)]
type :: try intern_pointer(state, PointerType{
element = array.element, mutable = pointer.mutable and array.mutable, many = true,
})
seed(state, id, type)
return type
}
}
if (!pointer.many or pointer.element.kind == .record) base = pointer.element
}
if (base.kind == .record and base.nominal == ast.NodeId(maxval!(u32))) {
fields :: state.records.items[usize(base.record_type)].fields
slot :: selected_field(state, fields, view.member)
@@ -1987,7 +2191,15 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
view :: ast.index_expr(state.tree, id)
base :: try infer_shape(state, view.base, defaults)
base := try infer_shape(state, view.base, defaults)
if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)]
if (pointer.many) {
seed(state, id, pointer.element)
return pointer.element
}
base = pointer.element
}
if (base.kind == .array and base.nominal == ast.NodeId(maxval!(u32))) {
element :: state.arrays.items[usize(base.array_type)].element
seed(state, id, element)
@@ -2057,7 +2269,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
}
if (assignment_values and state.cells[i].role == .assignment_stmt) {
view :: ast.assignment_stmt(tree, id)
if (mutable_target(state, view.target)) try context_demand(state, view.value, state.types[usize(view.target)], .type_mismatch, early)
if (mutable_target(state, view.target, false)) try context_demand(state, view.value, state.types[usize(view.target)], .type_mismatch, early)
}
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
if (state.backings[i].kind != .none) {
@@ -2093,7 +2305,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
@hide:file
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
if (early and expected.kind != .array and expected.kind != .record
and expected.kind != .enumeration and !integer_kind(expected.kind)) return
and expected.kind != .pointer and expected.kind != .enumeration and !integer_kind(expected.kind)) return
try demand(state, id, expected, code)
}
@@ -2281,18 +2493,35 @@ intern_record proc(state @mut State, declaration ast.NodeId, fields []mut Record
@hide:file
check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .record or cached.kind == .type_value) {
if (cached.kind == .invalid) return cached
if (!state.cells[usize(id)].record_checking and (cached.kind == .record or cached.kind == .type_value)) {
cached.kind = .record
return cached
}
if (cached.kind == .invalid) return cached
if (state.cells[usize(id)].record_checking) {
if (cached.kind == .record and state.pointer_depth > state.cells[usize(id)].record_pointer_depth) {
cached.kind = .record
return cached
}
type :: invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
state.cells[usize(id)].type = type
return type
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
state.cells[usize(id)].record_pointer_depth = state.pointer_depth
record_id := no_record
result_type := Type{}
if (nominal) {
record_id = RecordId(state.records.items.len)
empty :: try mem.alloc(RecordField, state.records.allocator, 0)
arraylist.append(&state.records, RecordType{ declaration = id, fields = empty }) catch |err| {
mem.free(state.records.allocator, empty)
return err
}
result_type = Type{ kind = .record, record_type = record_id }
state.cells[usize(id)].type = result_type
}
count :: ast.record_field_count(state.tree, id)
fields :: try mem.alloc(RecordField, state.records.allocator, count)
owned := true
@@ -2319,10 +2548,12 @@ check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.All
state.cells[usize(id)].type = failure
return failure
}
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
type :: try intern_record(state, declaration, fields)
if (nominal) {
mem.free(state.records.allocator, state.records.items[usize(record_id)].fields)
state.records.items[usize(record_id)].fields = fields
} else result_type = try intern_record(state, ast.NodeId(maxval!(u32)), fields)
owned = false
state.cells[usize(id)].type = type
state.cells[usize(id)].type = result_type
state.cells[usize(id)].record_checking = false
saved_assignment :: state.collecting_assignment
state.collecting_assignment = false
@@ -2332,7 +2563,7 @@ check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.All
try collect_expression(state, value)
}
}
return type
return result_type
}
@hide:file
@@ -2457,6 +2688,7 @@ type_syntax proc(kind ast.ExprKind) bool {
return (
kind == .record_type or kind == .union_type or kind == .enum_type
or kind == .distinct_type or kind == .alias_expr
or kind == .pointer_type
)
}
@@ -2483,16 +2715,22 @@ immediate_backing proc(state @State, type Type) Type {
@hide:file
check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
kind :: ast.expr_kind(state.tree, id)
if (kind == .record_type) return try check_record(state, id, nominal)
if (kind == .pointer_type) return try check_pointer_type(state, id)
if (kind == .union_type) {
type :: try check_union(state, id, nominal)
state.cells[usize(id)].type = type
return type
}
cached := state.cells[usize(id)].type
if (cached.kind == .type_value) {
cached.kind = cached.value_kind
return cached
}
if (cached.kind != .none) return cached
kind :: ast.expr_kind(state.tree, id)
if (kind == .record_type) return try check_record(state, id, nominal)
if (kind == .union_type or kind == .enum_type) {
type :: if (kind == .union_type) try check_union(state, id, nominal) else try check_enum(state, id)
if (kind == .enum_type) {
type :: try check_enum(state, id)
state.cells[usize(id)].type = type
return type
}
@@ -2630,11 +2868,33 @@ check_enum proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagno
@hide:file
check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .invalid) return cached
if (!state.cells[usize(id)].record_checking and (cached.kind == .record or cached.kind == .type_value)) {
cached.kind = .record
return cached
}
if (state.cells[usize(id)].record_checking) {
if (state.pointer_depth > state.cells[usize(id)].record_pointer_depth) {
cached.kind = .record
return cached
}
return invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
state.cells[usize(id)].record_pointer_depth = state.pointer_depth
record_id :: RecordId(state.records.items.len)
empty :: try mem.alloc(RecordField, state.records.allocator, 0)
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
arraylist.append(&state.records, RecordType{
declaration = declaration, fields = empty, is_union = true,
}) catch |err| {
mem.free(state.records.allocator, empty)
return err
}
result_type :: Type{ kind = .record, record_type = record_id }
state.cells[usize(id)].type = result_type
count :: ast.record_field_count(state.tree, id)
fields :: try mem.alloc(RecordField, state.records.allocator, count)
owned := true
@@ -2688,16 +2948,13 @@ check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.Allo
members_owned = false
tag = Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
}
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
try arraylist.append(&state.records, RecordType{
declaration = declaration,
fields = fields, is_union = true, tag_type = tag,
declared_tag_type = declared_tag,
})
mem.free(state.records.allocator, state.records.items[usize(record_id)].fields)
state.records.items[usize(record_id)].fields = fields
state.records.items[usize(record_id)].tag_type = tag
state.records.items[usize(record_id)].declared_tag_type = declared_tag
owned = false
type :: Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
state.cells[usize(id)].type = type
return type
state.cells[usize(id)].type = result_type
return result_type
}
@hide:file
+1
View File
@@ -571,6 +571,7 @@ resolve_expr proc(
try resolve_expr(state, scope, ast.call_arg(tree, id, i))
}
}
.pointer_type: try resolve_expr(state, scope, ast.pointer_type(tree, id).element)
.array_type: {
type :: ast.array_type(tree, id)
if (type.count) |count| try resolve_expr(state, scope, count)
+261 -1
View File
@@ -11,6 +11,245 @@ HONEY = ROOT / "build/honey"
# Explicit expectations guard against frontend bugs shared by both execution paths.
CASES = {
"pointer_assignment_evaluates_rhs_first_once": (
"""
Box :: struct { value i16, count i16 }
target proc(p @mut Box) @mut Box {
p.count = p.count + 1
return p
}
input proc(p @mut Box) i16 {
p.count = p.count + 10
return p.count
}
main proc() [2]i16 {
box Box := { value = 0, count = 0 }
target(&box).value = input(&box)
return [box.value, box.count]
}
""", b"[10, 11]\n", None,
),
"pointer_recursive_record_type": (
"""
Node :: struct { value i16, next @Node }
read_next proc(p @Node) i16 { return p.next.value }
main proc() i16 { return 42 }
""", b"42\n", None,
),
"pointer_many_dereference": (
"""
read proc(p *i16) i16 { return p^ }
main proc() i16 {
values [1]mut i16 := [42]
return read((&values).ptr)
}
""", b"42\n", None,
),
"pointer_many_from_array_element": (
"""
main proc() i16 {
values [3]mut i16 := [1, 2, 3]
pointer *mut i16 :: &values[0]
pointer[2] = 42
return values[2]
}
""", b"42\n", None,
),
"pointer_readonly_child_type_rejected": (
"read proc(p @u16) u16 { return p^ }\nmain proc() u16 { value i16 := 42\nreturn read(&value) }",
b"", b"incompatible value type",
),
"pointer_scalar_alias_across_calls": (
"""
identity proc(p @mut i16) @mut i16 { return p }
update proc(p @mut i16) void { p^ = p^ + 2 }
main proc() i16 {
value i16 := 40
pointer :: identity(&value)
update(pointer)
return value
}
""", b"42\n", None,
),
"pointer_readonly_parameter": (
"""
read proc(p @i16) i16 { return p^ }
main proc() i16 {
value i16 := 42
return read(&value)
}
""", b"42\n", None,
),
"pointer_record_alias_and_value_copy": (
"""
Point :: struct { x i16, y i16 }
set proc(p @mut Point) void { p.x = 40 }
main proc() i16 {
point Point := { x = 1, y = 2 }
pointer :: &point
copy :: pointer^
set(pointer)
if (copy.x != 1) return -1
return point.x + pointer.y
}
""", b"42\n", None,
),
"pointer_array_and_many_index": (
"""
update proc(p @mut [3]mut i16) void {
p[1] = p[0] + 2
values :: p.ptr
values[2] = values[1]
}
main proc() i16 {
values [3]mut i16 := [40, 0, 0]
update(&values)
if ((&values).len != 3) return -1
return values[2]
}
""", b"42\n", None,
),
"pointer_aggregate_copy_keeps_alias": (
"""
Holder :: struct { pointer @mut i16 }
identity proc(h Holder) Holder { return h }
main proc() i16 {
value i16 := 40
first :: Holder { pointer = &value }
second :: identity(first)
pointers [2]@mut i16 :: [first.pointer, second.pointer]
pointers[1]^ = pointers[0]^ + 2
return value
}
""", b"42\n", None,
),
"pointer_global_returned_from_callee": (
"""
value i16 :: 42
address proc() @i16 { return &value }
recurse proc(p @i16, n i16) i16 {
if (n == 0) return p^
return recurse(p, n - 1)
}
main proc() i16 { return recurse(address(), 20) }
""", b"42\n", None,
),
"pointer_location_identity": (
"""
Point :: struct { x i16, y i16 }
identity proc(p @mut i16) @mut i16 { return p }
main proc() bool {
point Point := { x = 40, y = 2 }
values [2]mut i16 := [40, 2]
first :: &point
second :: &point
return (
first == second
and identity(&point.x) == &point.x
and &point.x != &point.y
and (&values).ptr == (&values).ptr
)
}
""", b"true\n", None,
),
"pointer_union_roundtrip": (
"""
Storage :: union { pointer @mut i16, same @mut i16 }
main proc() i16 {
value i16 := 40
storage :: Storage { pointer = &value }
pointer :: storage.same
pointer^ = pointer^ + 2
return value
}
""", b"42\n", None,
),
"pointer_nested_array_aggregate": (
"""
Point :: struct { x i16, y i16 }
main proc() i16 {
values [2]mut Point := [Point { x = 40, y = 2 }, Point { x = 0, y = 0 }]
pointer :: (&values).ptr
pointer[1] = pointer[0]
pointer[0].x = 9
return values[1].x + values[1].y
}
""", b"42\n", None,
),
"pointer_to_pointer": (
"""
set proc(p @mut @mut i16) void { p^^ = 42 }
main proc() i16 {
value i16 := 0
pointer := &value
set(&pointer)
return value
}
""", b"42\n", None,
),
"pointer_repeated_address_in_loop": (
"""
main proc() i16 {
value i16 := 0
for 0..42 |i| {
pointer :: &value
pointer^ = i16(i) + 1
}
return value
}
""", b"42\n", None,
),
"pointer_zero_sized_array_elements": (
"""
Empty :: struct {}
main proc() bool {
values [2]mut Empty := [Empty {}, Empty {}]
pointer :: (&values).ptr
pointer[1] = pointer[0]
return (&values).len == 2
}
""", b"true\n", None,
),
"pointer_array_bounds_trap": (
"main proc() i16 { values [1]mut i16 := [42]\nreturn (&values)[1] }",
b"", b"index out of bounds",
),
"pointer_address_before_initialization": (
"main proc() i16 { pointer :: &value\nvalue i16 := 42\nreturn pointer^ }",
b"", b"read before initialization",
),
"pointer_constant_is_readonly": (
"main proc() i16 { value i16 :: 40\npointer :: &value\npointer^ = 42\nreturn value }",
b"", b"",
),
"pointer_readonly_parameter_write_rejected": (
"set proc(p @i16) void { p^ = 42 }\nmain proc() i16 { value i16 := 0\nset(&value)\nreturn value }",
b"", b"",
),
"pointer_array_readonly_elements": (
"main proc() i16 { values [1]i16 := [40]\npointer :: &values\npointer[0] = 42\nreturn values[0] }",
b"", b"",
),
"pointer_readonly_not_mutable_argument": (
"set proc(p @mut i16) void { p^ = 42 }\nmain proc() i16 { value i16 :: 0\nset(&value)\nreturn value }",
b"", b"incompatible value type",
),
"pointer_nonpointer_dereference_rejected": (
"main proc() i16 { value i16 :: 42\nreturn value^ }",
b"", b"dereference requires",
),
"pointer_void_pointee_rejected": (
"unused proc(p @void) void {}\nmain proc() int { return 42 }",
b"42\n", b"pointer requires",
),
"pointer_temporary_address_rejected": (
"main proc() i16 { pointer :: &(40 + 2)\nreturn pointer^ }",
b"", b"address-of requires",
),
"pointer_unused_invalid_path": (
"unused proc() int { return 42^ }\nmain proc() int { return 42 }",
b"42\n", b"dereference requires",
),
"match_float_ranges": (
"""
score proc(n f64) i16 {
@@ -1000,11 +1239,25 @@ def check_record_packages(temporary, executable):
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
(library / "functions.hon").write_text(
"set proc(p @mut Point) void { p.x = 40 }\n"
"identity proc(p @mut Point) @mut Point { return p }\n"
)
(app / "main.hon").write_text(textwrap.dedent("""
lib :: import "../library"
main proc() i16 {
p lib.Point := { x = 1 }
pointer :: lib.identity(&p)
lib.set(pointer)
return pointer.x + p.y
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
types.write_text("Point :: struct { x i16, y i16 = 32767 + 1 }\n")
check(app, executable, (b"", b"integer arithmetic overflow"))
trapped = run(str(HONEY), "run", str(app))
assert trapped.stderr.startswith(f"{types.resolve()}:1:".encode()), outcome(trapped)
return 2
return 3
def check_data_model_packages(temporary, executable):
@@ -1105,6 +1358,13 @@ def check_dependency_provenance(temporary, executable):
return a[1]
}
""", 5, 12, False),
("""
fail proc() int {
a :: [42]
pointer :: &a
return pointer[1]
}
""", 6, 12, False),
("""
fail proc() int {
before :: after