feat: support source pointers and references
This commit is contained in:
@@ -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`.
|
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.
|
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}`.
|
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.
|
`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.
|
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.
|
`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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
integers, checked numeric conversions, records/tuples, construction/defaults,
|
||||||
enums, distinct/alias types, overlapping untagged/tagged unions, field reads/writes
|
enums, distinct/alias types, overlapping untagged/tagged unions, field reads/writes
|
||||||
and exhaustive statement/value matching work through both execution paths.
|
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.
|
and source provenance through `@source/compilation`, with compilation-wide AST node identities.
|
||||||
file commands retain single-file behavior; directory commands compile the
|
file commands retain single-file behavior; directory commands compile the
|
||||||
package's production files and load its dependencies in separate scopes.
|
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] 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.
|
- [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`.
|
- [ ] 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`.
|
- [ ] 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.
|
- [ ] 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`.
|
- [ ] 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
@@ -1,9 +1,9 @@
|
|||||||
#! grammar:
|
#! 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 ... -> :: | :=
|
#! * binding ... -> :: | :=
|
||||||
#! * literal .. -> int | float | string | 'true' | 'false'
|
#! * literal .. -> int | float | string | 'true' | 'false'
|
||||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
#! * 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
|
#! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string
|
||||||
#! * block .... -> '{' {stmt} '}'
|
#! * block .... -> '{' {stmt} '}'
|
||||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
#! * 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.
|
#! 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
|
#! * 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.
|
#! 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.
|
#! binary operators associate left; unary operators associate right.
|
||||||
|
|
||||||
import "@std/debug"
|
import "@std/debug"
|
||||||
@@ -44,6 +44,7 @@ ExprKind :: enum {
|
|||||||
call
|
call
|
||||||
range
|
range
|
||||||
array_type
|
array_type
|
||||||
|
pointer_type
|
||||||
array_literal
|
array_literal
|
||||||
record_type
|
record_type
|
||||||
record_literal
|
record_literal
|
||||||
@@ -82,7 +83,12 @@ Mutability :: enum {
|
|||||||
constant, variable
|
constant, variable
|
||||||
}
|
}
|
||||||
|
|
||||||
UnaryOperator :: enum { negate, logical_not }
|
UnaryOperator :: enum {
|
||||||
|
negate
|
||||||
|
logical_not
|
||||||
|
address_of
|
||||||
|
dereference
|
||||||
|
}
|
||||||
|
|
||||||
BinaryOperator :: enum {
|
BinaryOperator :: enum {
|
||||||
add, subtract
|
add, subtract
|
||||||
@@ -105,6 +111,7 @@ NodeKind :: enum {
|
|||||||
expr_call
|
expr_call
|
||||||
expr_range
|
expr_range
|
||||||
expr_array_type
|
expr_array_type
|
||||||
|
expr_pointer_type
|
||||||
expr_array_literal
|
expr_array_literal
|
||||||
expr_record_type
|
expr_record_type
|
||||||
expr_record_literal
|
expr_record_literal
|
||||||
@@ -231,6 +238,12 @@ ArrayTypeView :: struct {
|
|||||||
mutable bool
|
mutable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PointerTypeView :: struct {
|
||||||
|
element NodeId
|
||||||
|
mutable bool
|
||||||
|
many bool
|
||||||
|
}
|
||||||
|
|
||||||
RecordLiteralView :: struct {
|
RecordLiteralView :: struct {
|
||||||
span Span
|
span Span
|
||||||
type ?NodeId
|
type ?NodeId
|
||||||
@@ -482,6 +495,22 @@ add_array_type proc(
|
|||||||
}, span)
|
}, 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 {
|
add_array_literal proc(builder @mut Builder, span Span, elements []NodeId) NodeId ! mem.AllocError {
|
||||||
start :: builder.storage.extra.items.len
|
start :: builder.storage.extra.items.len
|
||||||
for (elements) |element| _ = try add_extra(builder, u32(element))
|
for (elements) |element| _ = try add_extra(builder, u32(element))
|
||||||
@@ -1073,6 +1102,7 @@ expression_kind proc(kind NodeKind) ExprKind {
|
|||||||
.expr_call: .call
|
.expr_call: .call
|
||||||
.expr_range: .range
|
.expr_range: .range
|
||||||
.expr_array_type: .array_type
|
.expr_array_type: .array_type
|
||||||
|
.expr_pointer_type: .pointer_type
|
||||||
.expr_array_literal: .array_literal
|
.expr_array_literal: .array_literal
|
||||||
.expr_record_type: .record_type
|
.expr_record_type: .record_type
|
||||||
.expr_record_literal: .record_literal
|
.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 {
|
array_element_count proc(tree @Tree, id NodeId) usize {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .expr_array_literal)
|
debug.assert(node.kind == .expr_array_literal)
|
||||||
|
|||||||
@@ -89,6 +89,11 @@ Code :: enum {
|
|||||||
unreachable_match_arm
|
unreachable_match_arm
|
||||||
invalid_match_capture
|
invalid_match_capture
|
||||||
inactive_union_field
|
inactive_union_field
|
||||||
|
invalid_pointer_type
|
||||||
|
invalid_address_operand
|
||||||
|
invalid_dereference
|
||||||
|
invalid_pointer_assignment
|
||||||
|
invalid_pointer_access
|
||||||
}
|
}
|
||||||
|
|
||||||
Diagnostic :: struct {
|
Diagnostic :: struct {
|
||||||
@@ -199,5 +204,10 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
unreachable_match_arm = "match arm is unreachable",
|
unreachable_match_arm = "match arm is unreachable",
|
||||||
invalid_match_capture = "payload capture requires compatible nonvoid tagged union variants",
|
invalid_match_capture = "payload capture requires compatible nonvoid tagged union variants",
|
||||||
inactive_union_field = "tagged union field is not active",
|
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",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,19 @@ Address :: struct {
|
|||||||
scalar ?@mut ir.Value = null
|
scalar ?@mut ir.Value = null
|
||||||
initialized ?@mut bool = null
|
initialized ?@mut bool = null
|
||||||
raw bool = false
|
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
|
@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])
|
globals[i].value = aggregate_view(global.type, global.array_type, aggregate_globals[global_offset..global_offset + cells])
|
||||||
global_offset += cells
|
global_offset += cells
|
||||||
}
|
}
|
||||||
|
pointers := arraylist.init(Pointer, allocator)
|
||||||
|
defer arraylist.deinit(&pointers)
|
||||||
frames := arraylist.init(Frame, allocator)
|
frames := arraylist.init(Frame, allocator)
|
||||||
defer {
|
defer {
|
||||||
for (frames.items) |frame| free_frame(allocator, frame)
|
for (frames.items) |frame| free_frame(allocator, frame)
|
||||||
@@ -261,6 +276,9 @@ run proc(
|
|||||||
cells = &[],
|
cells = &[],
|
||||||
initialized = &local.initialized,
|
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
|
if (ir.aggregate(address.type)) address.cells = local.value.data.elements
|
||||||
else address.scalar = &local.value
|
else address.scalar = &local.value
|
||||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
@@ -287,6 +305,9 @@ run proc(
|
|||||||
array_type = instruction.array_type,
|
array_type = instruction.array_type,
|
||||||
cells = &[],
|
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
|
if (ir.aggregate(address.type)) address.cells = global.value.data.elements
|
||||||
else address.scalar = &global.value
|
else address.scalar = &global.value
|
||||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
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)
|
width :: if (base.raw) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
|
||||||
start :: usize(index) * width
|
start :: usize(index) * width
|
||||||
address := Address{
|
address := base
|
||||||
type = instruction.pointee, array_type = instruction.array_type,
|
address.type = instruction.pointee
|
||||||
cells = base.cells[start..start + width], initialized = base.initialized, raw = base.raw,
|
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]
|
if (!address.raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
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
|
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
|
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
|
start :: if (raw) field.byte_offset else field.offset
|
||||||
address := Address{
|
address := base
|
||||||
type = instruction.pointee, array_type = instruction.array_type,
|
address.type = instruction.pointee
|
||||||
cells = base.cells[start..start + width], initialized = base.initialized, raw = raw,
|
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]
|
if (!raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
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: {
|
.load_address, .store_address: {
|
||||||
id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
||||||
address :: frames.items[frame_index].addresses[usize(id)]
|
address :: frames.items[frame_index].addresses[usize(id)]
|
||||||
@@ -447,6 +526,11 @@ run proc(
|
|||||||
.not_equal: left.data.boolean != right.data.boolean
|
.not_equal: left.data.boolean != right.data.boolean
|
||||||
else: unreachable
|
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
|
else: unreachable
|
||||||
}
|
}
|
||||||
value = ir.Value{ type = .bool, data = ir.ValueData{ boolean = result } }
|
value = ir.Value{ type = .bool, data = ir.ValueData{ boolean = result } }
|
||||||
@@ -459,6 +543,77 @@ run proc(
|
|||||||
unreachable
|
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
|
@hide:file
|
||||||
push_frame proc(
|
push_frame proc(
|
||||||
allocator mem.Allocator,
|
allocator mem.Allocator,
|
||||||
|
|||||||
+9
-2
@@ -24,6 +24,7 @@ Type :: enum {
|
|||||||
array
|
array
|
||||||
record
|
record
|
||||||
address
|
address
|
||||||
|
pointer
|
||||||
i8, i16, i32, isize
|
i8, i16, i32, isize
|
||||||
u8, u16, u32, u64, usize
|
u8, u16, u32, u64, usize
|
||||||
}
|
}
|
||||||
@@ -74,6 +75,7 @@ integer_min proc(type Type) i64 {
|
|||||||
|
|
||||||
ValueData :: union {
|
ValueData :: union {
|
||||||
integer i64
|
integer i64
|
||||||
|
# source pointers use run-wide handles; internal addresses use frame-local IDs below.
|
||||||
unsigned u64
|
unsigned u64
|
||||||
real f64
|
real f64
|
||||||
boolean bool
|
boolean bool
|
||||||
@@ -141,6 +143,9 @@ Op :: enum {
|
|||||||
field_address
|
field_address
|
||||||
load_address
|
load_address
|
||||||
store_address
|
store_address
|
||||||
|
pointer_from_address
|
||||||
|
pointer_address
|
||||||
|
pointer_offset
|
||||||
negate
|
negate
|
||||||
convert
|
convert
|
||||||
logical_not
|
logical_not
|
||||||
@@ -160,6 +165,7 @@ Op :: enum {
|
|||||||
Instruction :: struct {
|
Instruction :: struct {
|
||||||
op Op
|
op Op
|
||||||
type Type = .void
|
type Type = .void
|
||||||
|
# pointer operations describe their pointee here, not aggregate result storage.
|
||||||
array_type ArrayId = no_array
|
array_type ArrayId = no_array
|
||||||
pointee Type = .invalid
|
pointee Type = .invalid
|
||||||
span diagnostics.Span = diagnostics.Span{ start = 0, end = 0 }
|
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
|
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
|
||||||
bits :: integer_bits(type)
|
bits :: integer_bits(type)
|
||||||
if (bits != 0) return divtrunc!(bits, 8)
|
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 {
|
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)
|
bits :: integer_bits(type)
|
||||||
if (bits != 0) return divtrunc!(bits, 8)
|
if (bits != 0) return divtrunc!(bits, 8)
|
||||||
return match type {
|
return match type {
|
||||||
.float, .function: 8
|
.float, .function, .pointer: 8
|
||||||
.string: 16
|
.string: 16
|
||||||
.bool: 1
|
.bool: 1
|
||||||
.void: 0
|
.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
|
.bool: bits = if (value.data.boolean) 1 else 0
|
||||||
.string: bits = u64(value.data.string)
|
.string: bits = u64(value.data.string)
|
||||||
.function: bits = u64(value.data.function)
|
.function: bits = u64(value.data.function)
|
||||||
|
.pointer: bits = value.data.unsigned
|
||||||
.void: return
|
.void: return
|
||||||
else: bits = ptrcast!(@u64, &value.data.integer)^
|
else: bits = ptrcast!(@u64, &value.data.integer)^
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,6 +295,8 @@ scan proc(
|
|||||||
',': .comma
|
',': .comma
|
||||||
'|': .pipe
|
'|': .pipe
|
||||||
'@': .at
|
'@': .at
|
||||||
|
'&': .ampersand
|
||||||
|
'^': .caret
|
||||||
'(': .open_paren
|
'(': .open_paren
|
||||||
')': .close_paren
|
')': .close_paren
|
||||||
'[': .open_bracket
|
'[': .open_bracket
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ TokenKind :: enum {
|
|||||||
star, slash
|
star, slash
|
||||||
|
|
||||||
range, range_inclusive
|
range, range_inclusive
|
||||||
pipe, at
|
at, ampersand, caret
|
||||||
|
pipe
|
||||||
|
|
||||||
# logical
|
# logical
|
||||||
bang
|
bang
|
||||||
|
|||||||
@@ -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 poison, ptr %r{}, 0\n", output, {address, pointer})
|
||||||
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
|
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: {
|
.load_address, .store_address: {
|
||||||
address :: try load(state, instruction.left)
|
address :: try load(state, instruction.left)
|
||||||
pointer :: fresh(state)
|
pointer :: fresh(state)
|
||||||
|
|||||||
@@ -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")
|
.float: try io.write_all(output, "double")
|
||||||
.bool: try io.write_all(output, "i1")
|
.bool: try io.write_all(output, "i1")
|
||||||
.string: try io.write_all(output, "%string")
|
.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")
|
.address: try io.write_all(output, "%address")
|
||||||
.array: {
|
.array: {
|
||||||
shape :: module.arrays.items[usize(type.shape)]
|
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})
|
.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})
|
.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})
|
.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: {
|
.array, .record: {
|
||||||
try io.print(" call void @print_array{}(", output, {u32(type.shape)})
|
try io.print(" call void @print_array{}(", output, {u32(type.shape)})
|
||||||
try write_type(output, module, type)
|
try write_type(output, module, type)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ runtime ::
|
|||||||
`@fmt_float = private constant [6 x i8] c"%.17g\00"
|
`@fmt_float = private constant [6 x i8] c"%.17g\00"
|
||||||
`@true = private constant [4 x i8] c"true"
|
`@true = private constant [4 x i8] c"true"
|
||||||
`@false = private constant [5 x i8] c"false"
|
`@false = private constant [5 x i8] c"false"
|
||||||
|
`@pointer_text = private constant [9 x i8] c"<pointer>"
|
||||||
`@colon = private constant [1 x i8] c":"
|
`@colon = private constant [1 x i8] c":"
|
||||||
`@error = private constant [9 x i8] c": error: "
|
`@error = private constant [9 x i8] c": error: "
|
||||||
`@runtime_trap = private constant [16 x i8] c": runtime trap: "
|
`@runtime_trap = private constant [16 x i8] c": runtime trap: "
|
||||||
|
|||||||
+99
-5
@@ -149,6 +149,7 @@ lower_type proc(state @State, type typecheck.Type) ir.Type {
|
|||||||
.u64: .u64
|
.u64: .u64
|
||||||
.array: .array
|
.array: .array
|
||||||
.record: .record
|
.record: .record
|
||||||
|
.pointer: .pointer
|
||||||
.float: .float
|
.float: .float
|
||||||
.bool: .bool
|
.bool: .bool
|
||||||
.string: .string
|
.string: .string
|
||||||
@@ -590,6 +591,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
.assignment_stmt: {
|
.assignment_stmt: {
|
||||||
assignment :: ast.assignment_stmt(tree, id)
|
assignment :: ast.assignment_stmt(tree, id)
|
||||||
if (ast.expr_kind(tree, assignment.target) == .index
|
if (ast.expr_kind(tree, assignment.target) == .index
|
||||||
|
or ast.expr_kind(tree, assignment.target) == .unary
|
||||||
or (ast.expr_kind(tree, assignment.target) == .selection
|
or (ast.expr_kind(tree, assignment.target) == .selection
|
||||||
and sema.binding(state.resolution, assignment.target).kind == .none)) {
|
and sema.binding(state.resolution, assignment.target).kind == .none)) {
|
||||||
value :: try lower_expression(state, assignment.value)
|
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
|
@hide:file
|
||||||
is_place proc(state @State, id ast.NodeId) bool {
|
is_place proc(state @State, id ast.NodeId) bool {
|
||||||
match ast.expr_kind(state.tree, id) {
|
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: {
|
.selection: {
|
||||||
if (sema.binding(state.resolution, id).kind == .none) {
|
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)
|
binding :: sema.binding(state.resolution, id)
|
||||||
if (binding.kind != .declaration) return false
|
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
|
@hide:file
|
||||||
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
span :: ast.node_span(state.tree, id)
|
span :: ast.node_span(state.tree, id)
|
||||||
checked :: typecheck.type_of(state.checked, 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
|
if (ast.expr_kind(state.tree, id) == .selection
|
||||||
and sema.binding(state.resolution, id).kind == .none) {
|
and sema.binding(state.resolution, id).kind == .none) {
|
||||||
expression :: ast.selection_expr(state.tree, id)
|
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
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
return try emit(state, ir.Instruction{
|
return try emit(state, ir.Instruction{
|
||||||
op = .field_address,
|
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) {
|
if (ast.expr_kind(state.tree, id) == .index) {
|
||||||
expression :: ast.index_expr(state.tree, id)
|
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
|
if (state.terminated) return ir.no_value
|
||||||
index :: try lower_expression(state, expression.index)
|
index :: try lower_expression(state, expression.index)
|
||||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
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.
|
# ranges have no physical value; only lower_for decomposes an iterable.
|
||||||
if (!trap_type(state, id)) unreachable
|
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)) {
|
if (!trap_type(state, id)) {
|
||||||
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
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: {
|
.identifier, .selection: {
|
||||||
if (ast.expr_kind(tree, id) == .selection
|
if (ast.expr_kind(tree, id) == .selection
|
||||||
and sema.binding(state.resolution, id).kind == .none) {
|
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)) {
|
if (is_place(state, id)) {
|
||||||
address :: try lower_place(state, id)
|
address :: try lower_place(state, id)
|
||||||
if (state.terminated) return ir.no_value
|
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: {
|
.unary: {
|
||||||
expression :: ast.unary_expr(tree, id)
|
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) {
|
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))
|
text :: ast.source_text(tree, ast.node_span(tree, expression.operand))
|
||||||
magnitude_limit :: ir.integer_max(type) + 1
|
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 {
|
op ir.Op :: match expression.operator {
|
||||||
.negate: .negate
|
.negate: .negate
|
||||||
.logical_not: .logical_not
|
.logical_not: .logical_not
|
||||||
|
.address_of, .dereference: unreachable
|
||||||
}
|
}
|
||||||
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
|
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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})
|
.float: try io.print("{d}", output, {value.data.real})
|
||||||
.bool: try io.print("{}", output, {value.data.boolean})
|
.bool: try io.print("{}", output, {value.data.boolean})
|
||||||
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
|
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
|
||||||
|
.pointer: try io.write_all(output, "<pointer>")
|
||||||
.array, .record: {
|
.array, .record: {
|
||||||
shape :: module.arrays.items[usize(value.array_type)]
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
count :: if (value.type == .array) shape.len else shape.fields.len
|
count :: if (value.type == .array) shape.len else shape.fields.len
|
||||||
|
|||||||
+53
-8
@@ -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, .struct)
|
||||||
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
|
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, .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, .open_bracket) and array_starts_type(state))
|
||||||
or next_n_is(state, 1, .double_colon)
|
or next_n_is(state, 1, .double_colon)
|
||||||
or next_n_is(state, 1, .colon_equal)
|
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)
|
.return: return try parse_return(state, allow_else)
|
||||||
.break, .continue: return try parse_loop_control(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,
|
.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)
|
expression :: try parse_expr(state)
|
||||||
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
||||||
return try ast.add_expression_stmt(state.builder, expression)
|
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
|
@hide:file
|
||||||
parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
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) {
|
if next_is(state, .open_bracket) {
|
||||||
open :: try consume(state, .open_bracket)
|
open :: try consume(state, .open_bracket)
|
||||||
count ?ast.NodeId := null
|
count ?ast.NodeId := null
|
||||||
@@ -653,6 +667,7 @@ starts_type proc(kind lexer.TokenKind) bool {
|
|||||||
return (
|
return (
|
||||||
kind == .ident or kind == .open_bracket or kind == .struct
|
kind == .ident or kind == .open_bracket or kind == .struct
|
||||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
|
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
|
value ?ast.NodeId := null
|
||||||
named :: member_name(current(state).kind) and (
|
named :: member_name(current(state).kind) and (
|
||||||
(!declaration and next_n_is(state, 1, .equal))
|
(!declaration and next_n_is(state, 1, .equal))
|
||||||
or (declaration and (
|
or (declaration and starts_type(state.tokens[state.next_token + 1].kind))
|
||||||
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)
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
if (named) {
|
if (named) {
|
||||||
token :: if (declaration) try consume(state, .ident) else try consume_member(state, false)
|
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
|
state.next_token += 1
|
||||||
operand :: try parse_expr_bp(state, right_bp)
|
operand :: try parse_expr_bp(state, right_bp)
|
||||||
operand_span :: ast.builder_span(state.builder, operand)
|
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(
|
yield try ast.add_unary(
|
||||||
state.builder,
|
state.builder,
|
||||||
ast.Span{
|
ast.Span{
|
||||||
@@ -1086,6 +1101,16 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
|||||||
while true {
|
while true {
|
||||||
skip_continuation_newlines(state)
|
skip_continuation_newlines(state)
|
||||||
token = current(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) {
|
if (token.kind == .open_paren) {
|
||||||
left = try parse_call(state, left, state.tokens[expression_start].span.start)
|
left = try parse_call(state, left, state.tokens[expression_start].span.start)
|
||||||
left_grouped = false
|
left_grouped = false
|
||||||
@@ -1324,6 +1349,23 @@ invalid_array_literal proc(state @mut State, start, end u32) ast.NodeId ! ParseE
|
|||||||
}, diagnostic)
|
}, 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
|
@hide:file
|
||||||
named_binding_starts proc(state @State) bool {
|
named_binding_starts proc(state @State) bool {
|
||||||
return (
|
return (
|
||||||
@@ -1331,6 +1373,8 @@ named_binding_starts proc(state @State) bool {
|
|||||||
and (
|
and (
|
||||||
next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
|
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, .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 {
|
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
token :: current(state)
|
token :: current(state)
|
||||||
match token.kind {
|
match token.kind {
|
||||||
|
.at, .star: return try parse_type(state)
|
||||||
.struct: return try parse_record_type(state)
|
.struct: return try parse_record_type(state)
|
||||||
.enum: return try parse_enum_type(state)
|
.enum: return try parse_enum_type(state)
|
||||||
.union: return try parse_union_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
|
@hide:file
|
||||||
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
||||||
return match kind {
|
return match kind {
|
||||||
.minus, .bang: BindingPowerScalar(50)
|
.minus, .bang, .ampersand: BindingPowerScalar(50)
|
||||||
else: null
|
else: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+302
-45
@@ -18,6 +18,7 @@ TypeKind :: enum {
|
|||||||
function
|
function
|
||||||
range
|
range
|
||||||
array
|
array
|
||||||
|
pointer
|
||||||
record
|
record
|
||||||
enumeration
|
enumeration
|
||||||
type_value
|
type_value
|
||||||
@@ -72,11 +73,21 @@ ArrayType :: struct {
|
|||||||
mutable bool
|
mutable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PointerId :: distinct u32
|
||||||
|
no_pointer :: maxval!(PointerId)
|
||||||
|
|
||||||
|
PointerType :: struct {
|
||||||
|
element Type
|
||||||
|
mutable bool
|
||||||
|
many bool
|
||||||
|
}
|
||||||
|
|
||||||
Type :: struct {
|
Type :: struct {
|
||||||
kind TypeKind = .none
|
kind TypeKind = .none
|
||||||
function ast.NodeId = ast.NodeId(maxval!(u32))
|
function ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
||||||
array_type ArrayId = no_array
|
array_type ArrayId = no_array
|
||||||
|
pointer_type PointerId = no_pointer
|
||||||
range_element TypeKind = .none
|
range_element TypeKind = .none
|
||||||
record_type RecordId = no_record
|
record_type RecordId = no_record
|
||||||
enum_type EnumId = no_enum
|
enum_type EnumId = no_enum
|
||||||
@@ -88,6 +99,7 @@ Type :: struct {
|
|||||||
Storage :: struct {
|
Storage :: struct {
|
||||||
types []mut Type
|
types []mut Type
|
||||||
arrays arraylist.ArrayList(ArrayType)
|
arrays arraylist.ArrayList(ArrayType)
|
||||||
|
pointers arraylist.ArrayList(PointerType)
|
||||||
records arraylist.ArrayList(RecordType)
|
records arraylist.ArrayList(RecordType)
|
||||||
enums arraylist.ArrayList(EnumType)
|
enums arraylist.ArrayList(EnumType)
|
||||||
backings []mut Type
|
backings []mut Type
|
||||||
@@ -151,6 +163,7 @@ Cell :: struct {
|
|||||||
producer ast.NodeId = ast.NodeId(maxval!(u32))
|
producer ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
capture ast.NodeId = ast.NodeId(maxval!(u32))
|
capture ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
record_checking bool = false
|
record_checking bool = false
|
||||||
|
record_pointer_depth usize = 0
|
||||||
nominal bool = false
|
nominal bool = false
|
||||||
match_pattern bool = false
|
match_pattern bool = false
|
||||||
first_edge usize = no_index
|
first_edge usize = no_index
|
||||||
@@ -179,6 +192,7 @@ State :: struct {
|
|||||||
groups []mut GroupLink
|
groups []mut GroupLink
|
||||||
edges arraylist.ArrayList(Edge)
|
edges arraylist.ArrayList(Edge)
|
||||||
arrays arraylist.ArrayList(ArrayType)
|
arrays arraylist.ArrayList(ArrayType)
|
||||||
|
pointers arraylist.ArrayList(PointerType)
|
||||||
records arraylist.ArrayList(RecordType)
|
records arraylist.ArrayList(RecordType)
|
||||||
initializers arraylist.ArrayList(RecordInitializer)
|
initializers arraylist.ArrayList(RecordInitializer)
|
||||||
enums arraylist.ArrayList(EnumType)
|
enums arraylist.ArrayList(EnumType)
|
||||||
@@ -191,6 +205,7 @@ State :: struct {
|
|||||||
slots []mut usize
|
slots []mut usize
|
||||||
collecting_assignment bool = false
|
collecting_assignment bool = false
|
||||||
has_assignments bool = false
|
has_assignments bool = false
|
||||||
|
pointer_depth usize = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#! borrows the AST, resolution, and diagnostic store only during checking.
|
#! borrows the AST, resolution, and diagnostic store only during checking.
|
||||||
@@ -235,6 +250,7 @@ check proc(
|
|||||||
groups = groups,
|
groups = groups,
|
||||||
edges = arraylist.init(allocator),
|
edges = arraylist.init(allocator),
|
||||||
arrays = arraylist.init(allocator),
|
arrays = arraylist.init(allocator),
|
||||||
|
pointers = arraylist.init(allocator),
|
||||||
records = arraylist.init(allocator),
|
records = arraylist.init(allocator),
|
||||||
initializers = arraylist.init(allocator),
|
initializers = arraylist.init(allocator),
|
||||||
slots = slots,
|
slots = slots,
|
||||||
@@ -247,6 +263,7 @@ check proc(
|
|||||||
defer arraylist.deinit(&state.edges)
|
defer arraylist.deinit(&state.edges)
|
||||||
defer arraylist.deinit(&state.variants)
|
defer arraylist.deinit(&state.variants)
|
||||||
errdefer arraylist.deinit(&state.arrays)
|
errdefer arraylist.deinit(&state.arrays)
|
||||||
|
errdefer arraylist.deinit(&state.pointers)
|
||||||
errdefer {
|
errdefer {
|
||||||
for (state.enums.items) |descriptor| mem.free(allocator, descriptor.members)
|
for (state.enums.items) |descriptor| mem.free(allocator, descriptor.members)
|
||||||
arraylist.deinit(&state.enums)
|
arraylist.deinit(&state.enums)
|
||||||
@@ -278,7 +295,7 @@ check proc(
|
|||||||
binding :: sema.binding(resolution, target)
|
binding :: sema.binding(resolution, target)
|
||||||
if (binding.kind == .invalid) {
|
if (binding.kind == .invalid) {
|
||||||
cells[i].type = invalid_type(binding.data.diagnostic)
|
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(
|
cells[i].type = invalid_type(try diagnostics.add(
|
||||||
diagnostic_store, .invalid_assignment_target, ast.node_span(tree, target),
|
diagnostic_store, .invalid_assignment_target, ast.node_span(tree, target),
|
||||||
))
|
))
|
||||||
@@ -511,6 +528,7 @@ check proc(
|
|||||||
return Result{ storage = Storage{
|
return Result{ storage = Storage{
|
||||||
types = types,
|
types = types,
|
||||||
arrays = state.arrays,
|
arrays = state.arrays,
|
||||||
|
pointers = state.pointers,
|
||||||
records = state.records,
|
records = state.records,
|
||||||
initializers = state.initializers,
|
initializers = state.initializers,
|
||||||
enums = state.enums,
|
enums = state.enums,
|
||||||
@@ -544,6 +562,10 @@ array_type proc(result @Result, id ArrayId) ArrayType {
|
|||||||
return result.storage.arrays.items[usize(id)]
|
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 {
|
record_type_count proc(result @Result) usize {
|
||||||
return result.storage.records.items.len
|
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 {
|
deinit proc(result @mut Result) void {
|
||||||
mem.free(result.storage.allocator, result.storage.types)
|
mem.free(result.storage.allocator, result.storage.types)
|
||||||
arraylist.deinit(&result.storage.arrays)
|
arraylist.deinit(&result.storage.arrays)
|
||||||
|
arraylist.deinit(&result.storage.pointers)
|
||||||
for (result.storage.enums.items) |descriptor| mem.free(result.storage.allocator, descriptor.members)
|
for (result.storage.enums.items) |descriptor| mem.free(result.storage.allocator, descriptor.members)
|
||||||
arraylist.deinit(&result.storage.enums)
|
arraylist.deinit(&result.storage.enums)
|
||||||
mem.free(result.storage.allocator, result.storage.backings)
|
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) {
|
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)]
|
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
|
||||||
state.cells[usize(id)].role = .annotation
|
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))) {
|
if (type_syntax(ast.expr_kind(state.tree, id))) {
|
||||||
type :: try check_type_expression(state, id, false)
|
type :: try check_type_expression(state, id, false)
|
||||||
state.types[usize(id)] = type
|
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 == .negate) state.cells[usize(id)].producer = id
|
||||||
if (expression.operator == .logical_not) {
|
if (expression.operator == .logical_not) {
|
||||||
state.cells[usize(id)].type = Type{ kind = .bool }
|
state.cells[usize(id)].type = Type{ kind = .bool }
|
||||||
} else {
|
} else if (expression.operator == .negate) {
|
||||||
state.cells[usize(id)].numeric = true
|
state.cells[usize(id)].numeric = true
|
||||||
}
|
}
|
||||||
operand :: expression.operand
|
operand :: expression.operand
|
||||||
@@ -1106,7 +1135,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
.array_type: {
|
.array_type: {
|
||||||
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
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)
|
type := try check_type_expression(state, id, state.cells[usize(id)].nominal)
|
||||||
if (type.kind != .invalid) {
|
if (type.kind != .invalid) {
|
||||||
type.value_kind = type.kind
|
type.value_kind = type.kind
|
||||||
@@ -1240,7 +1269,8 @@ bool_equality proc(state @mut State, id ast.NodeId) bool {
|
|||||||
return (
|
return (
|
||||||
(expression.operator == .equal or expression.operator == .not_equal)
|
(expression.operator == .equal or expression.operator == .not_equal)
|
||||||
and ((left.kind == .bool and right.kind == .bool)
|
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) {
|
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
|
if (state.types[usize(id)].kind == .invalid) return
|
||||||
index :: root(state, usize(id))
|
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) {
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
|
||||||
state.cells[index].expected = expected
|
state.cells[index].expected = expected
|
||||||
for 0..ast.match_arm_count(state.tree, id) |i| {
|
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
|
@hide:file
|
||||||
mutable_target proc(state @State, id ast.NodeId) bool {
|
mutable_target proc(state @mut State, id ast.NodeId, allow_global bool) bool {
|
||||||
if (ast.expr_kind(state.tree, id) == .index) {
|
kind :: ast.expr_kind(state.tree, id)
|
||||||
view :: ast.index_expr(state.tree, id)
|
if (kind == .unary) {
|
||||||
base :: state.types[usize(view.base)]
|
view :: ast.unary_expr(state.tree, id)
|
||||||
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base)
|
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
|
if (kind == .index) {
|
||||||
and sema.binding(state.resolution, id).kind == .none) {
|
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)
|
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)
|
binding :: sema.binding(state.resolution, id)
|
||||||
if (binding.kind != .declaration) return false
|
if (binding.kind != .declaration) return false
|
||||||
target :: binding.data.declaration
|
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
|
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
|
@hide:file
|
||||||
same_signature proc(state @State, left, right ast.NodeId) bool {
|
same_signature proc(state @State, left, right ast.NodeId) bool {
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
@@ -1437,11 +1529,22 @@ same_signature proc(state @State, left, right ast.NodeId) bool {
|
|||||||
return true
|
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
|
@hide:file
|
||||||
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
|
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
actual :: state.types[usize(value)]
|
actual :: state.types[usize(value)]
|
||||||
if (actual.kind == .invalid or expected.kind == .invalid) return
|
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))
|
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)
|
assignment :: ast.assignment_stmt(tree, id)
|
||||||
target_type :: state.types[usize(assignment.target)]
|
target_type :: state.types[usize(assignment.target)]
|
||||||
if (target_type.kind == .invalid) return
|
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)
|
try compatible(state, assignment.value, target_type)
|
||||||
} else {
|
} else {
|
||||||
code :: if (ast.expr_kind(tree, assignment.target) == .index)
|
kind :: ast.expr_kind(tree, assignment.target)
|
||||||
diagnostics.Code.invalid_index_assignment else diagnostics.Code.invalid_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))
|
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)]
|
actual :: state.types[usize(value)]
|
||||||
if (actual.kind == .invalid) {
|
if (actual.kind == .invalid) {
|
||||||
state.types[usize(id)] = actual
|
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))
|
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
|
||||||
state.types[usize(id)] = state.types[usize(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)
|
try require_bool(state, expression.operand)
|
||||||
return
|
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)]
|
operand :: state.types[usize(expression.operand)]
|
||||||
if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) {
|
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))
|
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)]
|
right :: state.types[usize(expression.right)]
|
||||||
if (left.kind == .invalid or right.kind == .invalid) return
|
if (left.kind == .invalid or right.kind == .invalid) return
|
||||||
if ((expression.operator == .equal or expression.operator == .not_equal)
|
if ((expression.operator == .equal or expression.operator == .not_equal)
|
||||||
and (left.kind == .bool or left.kind == .enumeration)
|
and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer)
|
||||||
and same_type(state, left, right)) return
|
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)) {
|
if (!numeric_kind(left.kind) or !numeric_kind(right.kind) or !same_type(state, left, right)) {
|
||||||
try fail(state, id, .invalid_operand, expression.span)
|
try fail(state, id, .invalid_operand, expression.span)
|
||||||
} else if (expression.operator == .divide and left.kind != .float) {
|
} 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)))) {
|
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))
|
try fail(state, view.index, .invalid_index_type, ast.node_span(tree, view.index))
|
||||||
}
|
}
|
||||||
if (base.kind != .invalid
|
if (base.kind != .invalid and !indexable_type(state, base)) {
|
||||||
and (base.kind != .array or base.nominal != ast.NodeId(maxval!(u32)))) {
|
|
||||||
try fail(state, id, .not_indexable, ast.node_span(tree, view.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
|
@hide:file
|
||||||
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
|
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
|
||||||
for state.arrays.items |existing, i| {
|
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.kind != right.kind) return false
|
||||||
if (left.nominal != right.nominal) return false
|
if (left.nominal != right.nominal) return false
|
||||||
if (left.kind == .enumeration) return left.enum_type == right.enum_type
|
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) {
|
if (left.kind == .type_value) {
|
||||||
a := left
|
a := left
|
||||||
b := right
|
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
|
if (state.cells[index].type.kind == .invalid) return state.cells[index].type
|
||||||
state.cells[index].visiting = true
|
state.cells[index].visiting = true
|
||||||
defer state.cells[index].visiting = false
|
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) {
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
|
||||||
return try infer_match(state, id, defaults)
|
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)
|
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)
|
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))) {
|
if (base.kind == .record and base.nominal == ast.NodeId(maxval!(u32))) {
|
||||||
fields :: state.records.items[usize(base.record_type)].fields
|
fields :: state.records.items[usize(base.record_type)].fields
|
||||||
slot :: selected_field(state, fields, view.member)
|
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) {
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
|
||||||
view :: ast.index_expr(state.tree, id)
|
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))) {
|
if (base.kind == .array and base.nominal == ast.NodeId(maxval!(u32))) {
|
||||||
element :: state.arrays.items[usize(base.array_type)].element
|
element :: state.arrays.items[usize(base.array_type)].element
|
||||||
seed(state, id, 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) {
|
if (assignment_values and state.cells[i].role == .assignment_stmt) {
|
||||||
view :: ast.assignment_stmt(tree, id)
|
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.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) {
|
if (state.backings[i].kind != .none) {
|
||||||
@@ -2093,7 +2305,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
|
|||||||
@hide:file
|
@hide:file
|
||||||
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
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
|
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)
|
try demand(state, id, expected, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2281,18 +2493,35 @@ intern_record proc(state @mut State, declaration ast.NodeId, fields []mut Record
|
|||||||
@hide:file
|
@hide:file
|
||||||
check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
cached := state.cells[usize(id)].type
|
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
|
cached.kind = .record
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
if (cached.kind == .invalid) return cached
|
|
||||||
if (state.cells[usize(id)].record_checking) {
|
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)))
|
type :: invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
|
||||||
state.cells[usize(id)].type = type
|
state.cells[usize(id)].type = type
|
||||||
return type
|
return type
|
||||||
}
|
}
|
||||||
state.cells[usize(id)].record_checking = true
|
state.cells[usize(id)].record_checking = true
|
||||||
defer state.cells[usize(id)].record_checking = false
|
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)
|
count :: ast.record_field_count(state.tree, id)
|
||||||
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
||||||
owned := true
|
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
|
state.cells[usize(id)].type = failure
|
||||||
return failure
|
return failure
|
||||||
}
|
}
|
||||||
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
|
if (nominal) {
|
||||||
type :: try intern_record(state, declaration, fields)
|
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
|
owned = false
|
||||||
state.cells[usize(id)].type = type
|
state.cells[usize(id)].type = result_type
|
||||||
state.cells[usize(id)].record_checking = false
|
state.cells[usize(id)].record_checking = false
|
||||||
saved_assignment :: state.collecting_assignment
|
saved_assignment :: state.collecting_assignment
|
||||||
state.collecting_assignment = false
|
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)
|
try collect_expression(state, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return type
|
return result_type
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -2457,6 +2688,7 @@ type_syntax proc(kind ast.ExprKind) bool {
|
|||||||
return (
|
return (
|
||||||
kind == .record_type or kind == .union_type or kind == .enum_type
|
kind == .record_type or kind == .union_type or kind == .enum_type
|
||||||
or kind == .distinct_type or kind == .alias_expr
|
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
|
@hide:file
|
||||||
check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
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
|
cached := state.cells[usize(id)].type
|
||||||
if (cached.kind == .type_value) {
|
if (cached.kind == .type_value) {
|
||||||
cached.kind = cached.value_kind
|
cached.kind = cached.value_kind
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
if (cached.kind != .none) return cached
|
if (cached.kind != .none) return cached
|
||||||
kind :: ast.expr_kind(state.tree, id)
|
if (kind == .enum_type) {
|
||||||
if (kind == .record_type) return try check_record(state, id, nominal)
|
type :: try check_enum(state, id)
|
||||||
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)
|
|
||||||
state.cells[usize(id)].type = type
|
state.cells[usize(id)].type = type
|
||||||
return type
|
return type
|
||||||
}
|
}
|
||||||
@@ -2630,11 +2868,33 @@ check_enum proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagno
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
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.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)))
|
return invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
|
||||||
}
|
}
|
||||||
state.cells[usize(id)].record_checking = true
|
state.cells[usize(id)].record_checking = true
|
||||||
defer state.cells[usize(id)].record_checking = false
|
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)
|
count :: ast.record_field_count(state.tree, id)
|
||||||
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
||||||
owned := true
|
owned := true
|
||||||
@@ -2688,16 +2948,13 @@ check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.Allo
|
|||||||
members_owned = false
|
members_owned = false
|
||||||
tag = Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
|
tag = Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
|
||||||
}
|
}
|
||||||
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
|
mem.free(state.records.allocator, state.records.items[usize(record_id)].fields)
|
||||||
try arraylist.append(&state.records, RecordType{
|
state.records.items[usize(record_id)].fields = fields
|
||||||
declaration = declaration,
|
state.records.items[usize(record_id)].tag_type = tag
|
||||||
fields = fields, is_union = true, tag_type = tag,
|
state.records.items[usize(record_id)].declared_tag_type = declared_tag
|
||||||
declared_tag_type = declared_tag,
|
|
||||||
})
|
|
||||||
owned = false
|
owned = false
|
||||||
type :: Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
|
state.cells[usize(id)].type = result_type
|
||||||
state.cells[usize(id)].type = type
|
return result_type
|
||||||
return type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
|
|||||||
@@ -571,6 +571,7 @@ resolve_expr proc(
|
|||||||
try resolve_expr(state, scope, ast.call_arg(tree, id, i))
|
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: {
|
.array_type: {
|
||||||
type :: ast.array_type(tree, id)
|
type :: ast.array_type(tree, id)
|
||||||
if (type.count) |count| try resolve_expr(state, scope, count)
|
if (type.count) |count| try resolve_expr(state, scope, count)
|
||||||
|
|||||||
+261
-1
@@ -11,6 +11,245 @@ HONEY = ROOT / "build/honey"
|
|||||||
|
|
||||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||||
CASES = {
|
CASES = {
|
||||||
|
"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": (
|
"match_float_ranges": (
|
||||||
"""
|
"""
|
||||||
score proc(n f64) i16 {
|
score proc(n f64) i16 {
|
||||||
@@ -1000,11 +1239,25 @@ def check_record_packages(temporary, executable):
|
|||||||
}
|
}
|
||||||
""").strip() + "\n")
|
""").strip() + "\n")
|
||||||
check(app, executable, (b"42\n", None))
|
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")
|
types.write_text("Point :: struct { x i16, y i16 = 32767 + 1 }\n")
|
||||||
check(app, executable, (b"", b"integer arithmetic overflow"))
|
check(app, executable, (b"", b"integer arithmetic overflow"))
|
||||||
trapped = run(str(HONEY), "run", str(app))
|
trapped = run(str(HONEY), "run", str(app))
|
||||||
assert trapped.stderr.startswith(f"{types.resolve()}:1:".encode()), outcome(trapped)
|
assert trapped.stderr.startswith(f"{types.resolve()}:1:".encode()), outcome(trapped)
|
||||||
return 2
|
return 3
|
||||||
|
|
||||||
|
|
||||||
def check_data_model_packages(temporary, executable):
|
def check_data_model_packages(temporary, executable):
|
||||||
@@ -1105,6 +1358,13 @@ def check_dependency_provenance(temporary, executable):
|
|||||||
return a[1]
|
return a[1]
|
||||||
}
|
}
|
||||||
""", 5, 12, False),
|
""", 5, 12, False),
|
||||||
|
("""
|
||||||
|
fail proc() int {
|
||||||
|
a :: [42]
|
||||||
|
pointer :: &a
|
||||||
|
return pointer[1]
|
||||||
|
}
|
||||||
|
""", 6, 12, False),
|
||||||
("""
|
("""
|
||||||
fail proc() int {
|
fail proc() int {
|
||||||
before :: after
|
before :: after
|
||||||
|
|||||||
Reference in New Issue
Block a user