feat: support callback signatures and many-pointer addition
This commit is contained in:
@@ -79,6 +79,8 @@ the parser supports arithmetic expressions with nested parenthesized groups, suc
|
|||||||
|
|
||||||
function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. argument lists allow newlines and a trailing comma; outside parentheses, newlines remain statement boundaries. type checking validates callable values, argument counts and argument types.
|
function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. argument lists allow newlines and a trailing comma; outside parentheses, newlines remain statement boundaries. type checking validates callable values, argument counts and argument types.
|
||||||
|
|
||||||
|
callback signatures use `@proc(value i16) i16`, including grouped parameter names and trailing commas. compatible named functions can initialize callbacks, which may be passed, returned, replaced and stored in records/arrays; `Callback :: alias @proc(value i16) i16` names a reusable signature. parameter/result types match structurally, retaining nominal record identity and exact pointer mutability. callback fields call directly, such as `allocator.vtable.alloc(context, size, alignment)` once the remaining argument types are supported. `@mut proc` and `*proc` are invalid; `@mut Callback` instead points to writable callback storage.
|
||||||
|
|
||||||
`true` and `false` are boolean literals; `bool` is available for parameters, storage and results. `==`, `!=`, `<`, `<=`, `>` and `>=` compare compatible numeric operands and produce `bool`; `==` and `!=` also accept boolean operands. comparisons preserve contextual numeric inference without making their operands boolean. arithmetic binds tighter than relational comparisons, which bind tighter than equality; comparisons associate left to right. boolean arithmetic/ordering and string/function/void comparisons are invalid; there is no implicit numeric-to-boolean conversion.
|
`true` and `false` are boolean literals; `bool` is available for parameters, storage and results. `==`, `!=`, `<`, `<=`, `>` and `>=` compare compatible numeric operands and produce `bool`; `==` and `!=` also accept boolean operands. comparisons preserve contextual numeric inference without making their operands boolean. arithmetic binds tighter than relational comparisons, which bind tighter than equality; comparisons associate left to right. boolean arithmetic/ordering and string/function/void comparisons are invalid; there is no implicit numeric-to-boolean conversion.
|
||||||
|
|
||||||
`!`, `and` and `or` require boolean operands and produce `bool`. prefix `!` and `-` bind tighter than arithmetic; equality binds tighter than `and`, which binds tighter than `or`. binary operators associate left to right, while prefixes nest right to left. operands evaluate left to right: `false and rhs` and `true or rhs` skip the right operand, including calls, lazy global initialization and runtime traps. both operands are still parsed, resolved and type-checked; errors in a skipped operand remain source diagnostics without forcing a runtime trap. this applies in initializers, assignments, returns, call arguments and conditions. [programs/boolean_conditions.hon](programs/boolean_conditions.hon) demonstrates combined predicates; `./build/honey run programs/boolean_conditions.hon` prints `true`.
|
`!`, `and` and `or` require boolean operands and produce `bool`. prefix `!` and `-` bind tighter than arithmetic; equality binds tighter than `and`, which binds tighter than `or`. binary operators associate left to right, while prefixes nest right to left. operands evaluate left to right: `false and rhs` and `true or rhs` skip the right operand, including calls, lazy global initialization and runtime traps. both operands are still parsed, resolved and type-checked; errors in a skipped operand remain source diagnostics without forcing a runtime trap. this applies in initializers, assignments, returns, call arguments and conditions. [programs/boolean_conditions.hon](programs/boolean_conditions.hon) demonstrates combined predicates; `./build/honey run programs/boolean_conditions.hon` prints `true`.
|
||||||
@@ -95,7 +97,7 @@ indices require `usize`; integer literals and flexible inferred values receive t
|
|||||||
|
|
||||||
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.
|
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.
|
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. many-pointer `pointer + offset` requires a `usize` offset, advances by the pointee's storage size and preserves its type/mutability; single-pointer addition, reversed addition and pointer subtraction are invalid, matching Bro. operands evaluate left to right once; one-past pointers can be formed but not dereferenced. 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. slices, optionals, layout/cast builtins 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.
|
||||||
|
|
||||||
@@ -129,7 +131,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, 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.
|
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 an AST declaration or explicit signature node supplying the callable signature. annotations support builtin types, data pointers, callback signatures, 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.
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ 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.
|
||||||
source data pointers/references now support aliasing, mutation, calls and aggregate
|
source data pointers/references support aliasing, mutation, calls and aggregate
|
||||||
storage through both execution paths. the driver retains frontend storage
|
storage; explicit callback signatures and many-pointer addition work 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.
|
||||||
@@ -42,7 +43,8 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [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.
|
- [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.
|
- [x] function-pointer signatures: explicit `@proc(...) Result`, structural signature checking, callbacks in parameters/results/records/arrays, vtable calls and imported signature aliases; many-pointer `+ usize` arithmetic preserves pointee type, mutability, stride and location identity through interpreter/native execution.
|
||||||
|
- [ ] 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`.
|
||||||
|
|||||||
+31
-6
@@ -1,5 +1,5 @@
|
|||||||
#! grammar:
|
#! grammar:
|
||||||
#! * type ..... -> identifier {'.' member} | ('@' | '*') ['mut'] type | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
|
#! * type ..... -> identifier {'.' member} | ('@' | '*') ['mut'] type | '@proc' '(' parameters ')' 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'
|
||||||
@@ -45,6 +45,7 @@ ExprKind :: enum {
|
|||||||
range
|
range
|
||||||
array_type
|
array_type
|
||||||
pointer_type
|
pointer_type
|
||||||
|
function_type
|
||||||
array_literal
|
array_literal
|
||||||
record_type
|
record_type
|
||||||
record_literal
|
record_literal
|
||||||
@@ -112,6 +113,7 @@ NodeKind :: enum {
|
|||||||
expr_range
|
expr_range
|
||||||
expr_array_type
|
expr_array_type
|
||||||
expr_pointer_type
|
expr_pointer_type
|
||||||
|
expr_function_type
|
||||||
expr_array_literal
|
expr_array_literal
|
||||||
expr_record_type
|
expr_record_type
|
||||||
expr_record_literal
|
expr_record_literal
|
||||||
@@ -511,6 +513,19 @@ add_pointer_type proc(
|
|||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_function_type proc(
|
||||||
|
builder @mut Builder, span Span, result NodeId, params []NodeId,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
|
start :: builder.storage.extra.items.len
|
||||||
|
for (params) |parameter| _ = try add_extra(builder, u32(parameter))
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_function_type,
|
||||||
|
main = NodeData{ node_id = result },
|
||||||
|
data0 = NodeData{ extra = u32(start) },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, 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))
|
||||||
@@ -1032,6 +1047,13 @@ function_decl proc(tree @Tree, id NodeId) FunctionDeclView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callable_result proc(tree @Tree, id NodeId) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
if (node.kind == .func_decl) return function_decl(tree, id).return_type
|
||||||
|
debug.assert(node.kind == .expr_function_type)
|
||||||
|
return node.main.node_id
|
||||||
|
}
|
||||||
|
|
||||||
import_decl proc(tree @Tree, id NodeId) ImportView {
|
import_decl proc(tree @Tree, id NodeId) ImportView {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .import_decl)
|
debug.assert(node.kind == .import_decl)
|
||||||
@@ -1047,15 +1069,17 @@ import_decl proc(tree @Tree, id NodeId) ImportView {
|
|||||||
|
|
||||||
function_param_count proc(tree @Tree, id NodeId) usize {
|
function_param_count proc(tree @Tree, id NodeId) usize {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .func_decl)
|
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type)
|
||||||
return usize(node.data1.extra - node.main.extra - 4)
|
start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra
|
||||||
|
return usize(node.data1.extra - start)
|
||||||
}
|
}
|
||||||
|
|
||||||
function_param proc(tree @Tree, id NodeId, index usize) NodeId {
|
function_param proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .func_decl)
|
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type)
|
||||||
debug.assert(index < usize(node.data1.extra - node.main.extra - 4))
|
start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra
|
||||||
return NodeId(tree.storage.extra.items[usize(node.main.extra) + 4 + index])
|
debug.assert(index < usize(node.data1.extra - start))
|
||||||
|
return NodeId(tree.storage.extra.items[usize(start) + index])
|
||||||
}
|
}
|
||||||
|
|
||||||
param proc(tree @Tree, id NodeId) ParamView {
|
param proc(tree @Tree, id NodeId) ParamView {
|
||||||
@@ -1103,6 +1127,7 @@ expression_kind proc(kind NodeKind) ExprKind {
|
|||||||
.expr_range: .range
|
.expr_range: .range
|
||||||
.expr_array_type: .array_type
|
.expr_array_type: .array_type
|
||||||
.expr_pointer_type: .pointer_type
|
.expr_pointer_type: .pointer_type
|
||||||
|
.expr_function_type: .function_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
|
||||||
|
|||||||
@@ -1065,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, .pointer_type, .record_type, .enum_type, .union_type, .distinct_type: {
|
.array_type, .pointer_type, .function_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)
|
||||||
}
|
}
|
||||||
@@ -1240,6 +1240,14 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
if (state.terminated) return ir.no_value
|
if (state.terminated) return ir.no_value
|
||||||
right :: try lower_expression(state, expression.right)
|
right :: try lower_expression(state, expression.right)
|
||||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
|
if (expression.operator == .add and type == .pointer) {
|
||||||
|
pointer :: typecheck.pointer_type(state.checked, typecheck.type_of(state.checked, id).pointer_type)
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .pointer_offset, type = .pointer, left = left, right = right,
|
||||||
|
pointee = lower_type(state, pointer.element),
|
||||||
|
array_type = try array_shape(state, pointer.element), span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
op ir.Op :: match expression.operator {
|
op ir.Op :: match expression.operator {
|
||||||
.add: .add
|
.add: .add
|
||||||
.subtract: .subtract
|
.subtract: .subtract
|
||||||
|
|||||||
+50
-27
@@ -149,33 +149,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
|
|
||||||
params arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
params arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
defer arraylist.deinit(¶ms)
|
defer arraylist.deinit(¶ms)
|
||||||
names arraylist.ArrayList(lexer.Token) := arraylist.init(state.allocator)
|
try parse_parameters(state, ¶ms)
|
||||||
defer arraylist.deinit(&names)
|
|
||||||
|
|
||||||
while !next_is(state, .close_paren) {
|
|
||||||
arraylist.clear(&names)
|
|
||||||
while true {
|
|
||||||
token :: try consume(state, .ident)
|
|
||||||
try arraylist.append(&names, token)
|
|
||||||
if (starts_type(current(state).kind)) break
|
|
||||||
_ = try consume(state, .comma)
|
|
||||||
}
|
|
||||||
|
|
||||||
type_id :: try parse_type(state)
|
|
||||||
type_span :: ast.builder_span(state.builder, type_id)
|
|
||||||
for (names.items) |parameter_name| {
|
|
||||||
param_id :: try ast.add_param(
|
|
||||||
state.builder,
|
|
||||||
ast.Span{ start = parameter_name.span.start, end = type_span.end, source = parameter_name.span.source },
|
|
||||||
parameter_name.span,
|
|
||||||
parameter_name.data.str_id,
|
|
||||||
type_id,
|
|
||||||
)
|
|
||||||
try arraylist.append(¶ms, param_id)
|
|
||||||
}
|
|
||||||
if next_is(state, .comma) _ = try consume(state, .comma)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = try consume(state, .close_paren)
|
_ = try consume(state, .close_paren)
|
||||||
return_type :: try parse_type(state)
|
return_type :: try parse_type(state)
|
||||||
body :: try parse_block(state)
|
body :: try parse_block(state)
|
||||||
@@ -195,6 +169,36 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_parameters proc(
|
||||||
|
state @mut State, params @mut arraylist.ArrayList(ast.NodeId),
|
||||||
|
) void ! ParseError {
|
||||||
|
names arraylist.ArrayList(lexer.Token) := arraylist.init(state.allocator)
|
||||||
|
defer arraylist.deinit(&names)
|
||||||
|
while !next_is(state, .close_paren) {
|
||||||
|
arraylist.clear(&names)
|
||||||
|
while true {
|
||||||
|
token :: try consume(state, .ident)
|
||||||
|
try arraylist.append(&names, token)
|
||||||
|
if (starts_type(current(state).kind)) break
|
||||||
|
_ = try consume(state, .comma)
|
||||||
|
}
|
||||||
|
type_id :: try parse_type(state)
|
||||||
|
type_span :: ast.builder_span(state.builder, type_id)
|
||||||
|
for (names.items) |parameter_name| {
|
||||||
|
param_id :: try ast.add_param(
|
||||||
|
state.builder,
|
||||||
|
ast.Span{ start = parameter_name.span.start, end = type_span.end, source = parameter_name.span.source },
|
||||||
|
parameter_name.span,
|
||||||
|
parameter_name.data.str_id,
|
||||||
|
type_id,
|
||||||
|
)
|
||||||
|
try arraylist.append(params, param_id)
|
||||||
|
}
|
||||||
|
if next_is(state, .comma) _ = try consume(state, .comma)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
open :: try consume(state, .open_curly)
|
open :: try consume(state, .open_curly)
|
||||||
@@ -610,6 +614,25 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
mutable :: next_is(state, .mut)
|
mutable :: next_is(state, .mut)
|
||||||
if (mutable) state.next_token += 1
|
if (mutable) state.next_token += 1
|
||||||
|
if (next_is(state, .proc)) {
|
||||||
|
state.next_token += 1
|
||||||
|
_ = try consume(state, .open_paren)
|
||||||
|
params := arraylist.init(ast.NodeId, state.allocator)
|
||||||
|
defer arraylist.deinit(¶ms)
|
||||||
|
try parse_parameters(state, ¶ms)
|
||||||
|
_ = try consume(state, .close_paren)
|
||||||
|
result :: try parse_type(state)
|
||||||
|
span :: ast.Span{
|
||||||
|
start = token.span.start,
|
||||||
|
end = ast.builder_span(state.builder, result).end,
|
||||||
|
source = token.span.source,
|
||||||
|
}
|
||||||
|
if (mutable or token.kind == .star) {
|
||||||
|
diagnostic :: try diagnostics.add(state.diagnostics, .invalid_pointer_type, span)
|
||||||
|
return try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||||
|
}
|
||||||
|
return try ast.add_function_type(state.builder, span, result, params.items)
|
||||||
|
}
|
||||||
element :: try parse_type(state)
|
element :: try parse_type(state)
|
||||||
return try ast.add_pointer_type(state.builder, ast.Span{
|
return try ast.add_pointer_type(state.builder, ast.Span{
|
||||||
start = token.span.start,
|
start = token.span.start,
|
||||||
|
|||||||
+71
-10
@@ -351,7 +351,7 @@ check proc(
|
|||||||
callee :: ast.call_expr(tree, id).callee
|
callee :: ast.call_expr(tree, id).callee
|
||||||
signature :: try infer_shape(&state, callee, false)
|
signature :: try infer_shape(&state, callee, false)
|
||||||
if (signature.function != ast.NodeId(maxval!(u32))) {
|
if (signature.function != ast.NodeId(maxval!(u32))) {
|
||||||
result :: ast.function_decl(tree, signature.function).return_type
|
result :: ast.callable_result(tree, signature.function)
|
||||||
seed(&state, id, types[usize(result)])
|
seed(&state, id, types[usize(result)])
|
||||||
} else if (signature.kind == .invalid) {
|
} else if (signature.kind == .invalid) {
|
||||||
seed(&state, id, signature)
|
seed(&state, id, signature)
|
||||||
@@ -391,7 +391,7 @@ check proc(
|
|||||||
}
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
if (logical(expression.operator) or bool_equality(&state, id)) continue
|
if (logical(expression.operator) or bool_equality(&state, id) or pointer_addition(&state, id)) continue
|
||||||
try require_numeric(&state, id, expression.left)
|
try require_numeric(&state, id, expression.left)
|
||||||
try require_numeric(&state, id, expression.right)
|
try require_numeric(&state, id, expression.right)
|
||||||
}
|
}
|
||||||
@@ -411,7 +411,7 @@ check proc(
|
|||||||
}
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
if (logical(expression.operator)) continue
|
if (logical(expression.operator) or pointer_addition(&state, id)) continue
|
||||||
if (comparison(expression.operator)) {
|
if (comparison(expression.operator)) {
|
||||||
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
|
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
|
||||||
} else {
|
} else {
|
||||||
@@ -1135,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, .pointer_type: {
|
.record_type, .union_type, .enum_type, .distinct_type, .pointer_type, .function_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
|
||||||
@@ -1261,6 +1261,17 @@ logical proc(operator ast.BinaryOperator) bool {
|
|||||||
return operator == .logical_and or operator == .logical_or
|
return operator == .logical_and or operator == .logical_or
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
pointer_addition proc(state @mut State, id ast.NodeId) bool {
|
||||||
|
expression :: ast.binary_expr(state.tree, id)
|
||||||
|
left :: group_type(state, expression.left)
|
||||||
|
return (
|
||||||
|
expression.operator == .add and left.kind == .pointer
|
||||||
|
and left.nominal == ast.NodeId(maxval!(u32))
|
||||||
|
and state.pointers.items[usize(left.pointer_type)].many
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
bool_equality proc(state @mut State, id ast.NodeId) bool {
|
bool_equality proc(state @mut State, id ast.NodeId) bool {
|
||||||
expression :: ast.binary_expr(state.tree, id)
|
expression :: ast.binary_expr(state.tree, id)
|
||||||
@@ -1516,14 +1527,15 @@ addressable_target proc(state @mut State, id ast.NodeId) bool {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
same_signature proc(state @State, left, right ast.NodeId) bool {
|
same_signature proc(state @State, left, right ast.NodeId) bool {
|
||||||
|
if (left == right) return true
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
|
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
|
||||||
left_result :: ast.function_decl(tree, left).return_type
|
left_result :: ast.callable_result(tree, left)
|
||||||
right_result :: ast.function_decl(tree, right).return_type
|
right_result :: ast.callable_result(tree, right)
|
||||||
if (!same_type(state, state.types[usize(left_result)], state.types[usize(right_result)])) return false
|
if (!same_type(state, state.types[usize(left_result)], state.types[usize(right_result)])) return false
|
||||||
for (0..ast.function_param_count(tree, left)) |i| {
|
for (0..ast.function_param_count(tree, left)) |i| {
|
||||||
left_param :: ast.function_param(tree, left, i)
|
left_param :: ast.param(tree, ast.function_param(tree, left, i)).type
|
||||||
right_param :: ast.function_param(tree, right, i)
|
right_param :: ast.param(tree, ast.function_param(tree, right, i)).type
|
||||||
if (!same_type(state, state.types[usize(left_param)], state.types[usize(right_param)])) return false
|
if (!same_type(state, state.types[usize(left_param)], state.types[usize(right_param)])) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -1635,6 +1647,12 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
left :: state.types[usize(expression.left)]
|
left :: state.types[usize(expression.left)]
|
||||||
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 (pointer_addition(state, id)) {
|
||||||
|
if (right.kind != .usize or right.nominal != ast.NodeId(maxval!(u32))) {
|
||||||
|
try fail(state, id, .invalid_operand, expression.span)
|
||||||
|
}
|
||||||
|
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 or left.kind == .pointer)
|
and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer)
|
||||||
and (assignable_type(state, left, right) or assignable_type(state, right, left))) return
|
and (assignable_type(state, left, right) or assignable_type(state, right, left))) return
|
||||||
@@ -1901,7 +1919,7 @@ check_pointer_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
|
|||||||
if (element.kind == .invalid) return element
|
if (element.kind == .invalid) return element
|
||||||
binding :: sema.binding(state.resolution, view.element)
|
binding :: sema.binding(state.resolution, view.element)
|
||||||
if (element.kind == .none or element.kind == .void or element.kind == .range
|
if (element.kind == .none or element.kind == .void or element.kind == .range
|
||||||
or element.kind == .type_value or element.kind == .function
|
or element.kind == .type_value
|
||||||
or has_inferred_count(state, element)
|
or has_inferred_count(state, element)
|
||||||
or (binding.kind == .builtin and (binding.data.builtin == .int or binding.data.builtin == .float))) {
|
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 invalid_type(try diagnostics.add(state.diagnostics, .invalid_pointer_type, ast.node_span(state.tree, id)))
|
||||||
@@ -1909,6 +1927,35 @@ check_pointer_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
|
|||||||
return try intern_pointer(state, PointerType{ element = element, mutable = view.mutable, many = view.many })
|
return try intern_pointer(state, PointerType{ element = element, mutable = view.mutable, many = view.many })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
cached := state.cells[usize(id)].type
|
||||||
|
if (cached.kind == .type_value) cached.kind = cached.value_kind
|
||||||
|
if (cached.kind != .none) return cached
|
||||||
|
type := Type{ kind = .function, function = id }
|
||||||
|
state.cells[usize(id)].type = type
|
||||||
|
state.pointer_depth += 1
|
||||||
|
defer state.pointer_depth -= 1
|
||||||
|
result :: ast.callable_result(state.tree, id)
|
||||||
|
result_type :: try check_annotation(state, result, true)
|
||||||
|
if (result_type.kind == .invalid) type = result_type
|
||||||
|
try depend(state, result, id)
|
||||||
|
for 0..ast.function_param_count(state.tree, id) |i| {
|
||||||
|
parameter :: ast.function_param(state.tree, id, i)
|
||||||
|
annotation :: ast.param(state.tree, parameter).type
|
||||||
|
parameter_type :: try check_annotation(state, annotation, false)
|
||||||
|
state.cells[usize(parameter)].role = .annotation
|
||||||
|
state.cells[usize(parameter)].type = parameter_type
|
||||||
|
state.types[usize(parameter)] = parameter_type
|
||||||
|
if (parameter_type.kind == .invalid and type.kind != .invalid) type = parameter_type
|
||||||
|
try depend(state, annotation, parameter)
|
||||||
|
try depend(state, parameter, id)
|
||||||
|
}
|
||||||
|
type.function = id
|
||||||
|
state.cells[usize(id)].type = type
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
@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| {
|
||||||
@@ -2037,6 +2084,19 @@ 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) == .binary) {
|
||||||
|
view :: ast.binary_expr(state.tree, id)
|
||||||
|
if (view.operator == .add) {
|
||||||
|
_ = try infer_shape(state, view.left, defaults)
|
||||||
|
if (pointer_addition(state, id)) {
|
||||||
|
type :: group_type(state, view.left)
|
||||||
|
state.cells[index].numeric = false
|
||||||
|
seed(state, id, type)
|
||||||
|
try demand(state, view.right, Type{ kind = .usize }, .invalid_operand)
|
||||||
|
return group_type(state, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .unary) {
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .unary) {
|
||||||
view :: ast.unary_expr(state.tree, id)
|
view :: ast.unary_expr(state.tree, id)
|
||||||
if (view.operator == .address_of or view.operator == .dereference) {
|
if (view.operator == .address_of or view.operator == .dereference) {
|
||||||
@@ -2688,7 +2748,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
|
or kind == .pointer_type or kind == .function_type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2718,6 +2778,7 @@ check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type !
|
|||||||
kind :: ast.expr_kind(state.tree, id)
|
kind :: ast.expr_kind(state.tree, id)
|
||||||
if (kind == .record_type) return try check_record(state, id, nominal)
|
if (kind == .record_type) return try check_record(state, id, nominal)
|
||||||
if (kind == .pointer_type) return try check_pointer_type(state, id)
|
if (kind == .pointer_type) return try check_pointer_type(state, id)
|
||||||
|
if (kind == .function_type) return try check_function_type(state, id)
|
||||||
if (kind == .union_type) {
|
if (kind == .union_type) {
|
||||||
type :: try check_union(state, id, nominal)
|
type :: try check_union(state, id, nominal)
|
||||||
state.cells[usize(id)].type = type
|
state.cells[usize(id)].type = type
|
||||||
|
|||||||
@@ -572,6 +572,13 @@ resolve_expr proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pointer_type: try resolve_expr(state, scope, ast.pointer_type(tree, id).element)
|
.pointer_type: try resolve_expr(state, scope, ast.pointer_type(tree, id).element)
|
||||||
|
.function_type: {
|
||||||
|
try resolve_expr(state, scope, ast.callable_result(tree, id))
|
||||||
|
for 0..ast.function_param_count(tree, id) |i| {
|
||||||
|
parameter :: ast.function_param(tree, id, i)
|
||||||
|
try resolve_expr(state, scope, ast.param(tree, parameter).type)
|
||||||
|
}
|
||||||
|
}
|
||||||
.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)
|
||||||
|
|||||||
+240
-1
@@ -11,6 +11,143 @@ 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 = {
|
||||||
|
"callback_concrete_vtable": (
|
||||||
|
"""
|
||||||
|
VTable :: struct { run @proc(context @mut State, amount i16) i16 }
|
||||||
|
State :: struct { value i16, vtable @VTable }
|
||||||
|
add proc(context @mut State, amount i16) i16 {
|
||||||
|
context.value = context.value + amount
|
||||||
|
return context.value
|
||||||
|
}
|
||||||
|
table VTable :: { run = add }
|
||||||
|
main proc() i16 {
|
||||||
|
state State := { value = 40, vtable = &table }
|
||||||
|
return state.vtable.run(&state, 2)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"callback_pass_return_store_replace": (
|
||||||
|
"""
|
||||||
|
Callback :: alias @proc(value i16) i16
|
||||||
|
Holder :: struct { run Callback }
|
||||||
|
increment proc(value i16) i16 { return value + 1 }
|
||||||
|
twice proc(value i16) i16 { return value + value }
|
||||||
|
choose proc(first Callback, second Callback, flag bool) Callback {
|
||||||
|
if (flag) return first
|
||||||
|
return second
|
||||||
|
}
|
||||||
|
apply proc(callback Callback, value i16) i16 { return callback(value) }
|
||||||
|
main proc() i16 {
|
||||||
|
callback @proc(n i16) i16 := increment
|
||||||
|
callback = twice
|
||||||
|
holder Holder := { run = increment }
|
||||||
|
holder.run = callback
|
||||||
|
callbacks [2]mut Callback := [increment, twice]
|
||||||
|
callbacks[0] = choose(holder.run, increment, true)
|
||||||
|
return apply(callbacks[0], 21)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"callback_nested_aggregate_arguments_and_result": (
|
||||||
|
"""
|
||||||
|
Payload :: struct { values [2]i16, pointer @mut i16 }
|
||||||
|
Callback :: alias @proc(input Payload, rows [2][2]i16) Payload
|
||||||
|
transform proc(input Payload, rows [2][2]i16) Payload {
|
||||||
|
input.pointer^ = input.pointer^ + rows[1][0]
|
||||||
|
return { values = [input.values[0] + rows[0][1], rows[1][1]], pointer = input.pointer }
|
||||||
|
}
|
||||||
|
select proc(callback Callback) Callback { return callback }
|
||||||
|
main proc() [3]i16 {
|
||||||
|
value i16 := 40
|
||||||
|
callback Callback :: select(transform)
|
||||||
|
result :: callback({ values = [40, 0], pointer = &value }, [[0, 2], [2, 42]])
|
||||||
|
return [result.values[0], result.values[1], result.pointer^]
|
||||||
|
}
|
||||||
|
""", b"[42, 42, 42]\n", None,
|
||||||
|
),
|
||||||
|
"callback_storage_address_is_data_pointer": (
|
||||||
|
"""
|
||||||
|
Callback :: alias @proc(value i16) i16
|
||||||
|
increment proc(value i16) i16 { return value + 1 }
|
||||||
|
twice proc(value i16) i16 { return value + value }
|
||||||
|
replace proc(slot @mut Callback, callback Callback) void { slot^ = callback }
|
||||||
|
main proc() i16 {
|
||||||
|
callback Callback := increment
|
||||||
|
replace(&callback, twice)
|
||||||
|
return (&callback)^(21)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"callback_structural_inline_aggregate_signature": (
|
||||||
|
"""
|
||||||
|
identity proc(value struct { values [2]i16, pointer @i16 }) struct { values [2]i16, pointer @i16 } {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
number i16 :: 2
|
||||||
|
callback @proc(input struct { values [2]i16, pointer @i16 }) struct { values [2]i16, pointer @i16 } :: identity
|
||||||
|
result :: callback({ values = [40, 0], pointer = &number })
|
||||||
|
return result.values[0] + result.pointer^
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"callback_parameter_signature_mismatch": (
|
||||||
|
"wrong proc(value u16) i16 { return 42 }\nmain proc() i16 { callback @proc(value i16) i16 :: wrong\nreturn callback(1) }",
|
||||||
|
b"", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"callback_result_signature_mismatch": (
|
||||||
|
"wrong proc(value i16) u16 { return 42 }\nmain proc() i16 { callback @proc(value i16) i16 :: wrong\nreturn callback(1) }",
|
||||||
|
b"", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"callback_nested_signature_mismatch": (
|
||||||
|
"""
|
||||||
|
wrong proc(value struct { rows [2][2]u16 }) i16 { return 42 }
|
||||||
|
main proc() i16 {
|
||||||
|
callback @proc(value struct { rows [2][2]i16 }) i16 :: wrong
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""", b"", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"callback_pointer_mutability_signature_mismatch": (
|
||||||
|
"wrong proc(value @mut i16) i16 { return value^ }\nmain proc() i16 { callback @proc(value @i16) i16 :: wrong\nreturn 42 }",
|
||||||
|
b"", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"callback_wrong_argument_type": (
|
||||||
|
"identity proc(value i16) i16 { return value }\nmain proc() i16 { callback @proc(value i16) i16 :: identity\nreturn callback(true) }",
|
||||||
|
b"", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"callback_wrong_argument_count": (
|
||||||
|
"identity proc(value i16) i16 { return value }\nmain proc() i16 { callback @proc(value i16) i16 :: identity\nreturn callback() }",
|
||||||
|
b"", b"argument count does not match",
|
||||||
|
),
|
||||||
|
"callback_readonly_vtable_assignment": (
|
||||||
|
"""
|
||||||
|
VTable :: struct { run @proc(value i16) i16 }
|
||||||
|
identity proc(value i16) i16 { return value }
|
||||||
|
replace proc(table @VTable) void { table.run = identity }
|
||||||
|
main proc() i16 {
|
||||||
|
table VTable := { run = identity }
|
||||||
|
replace(&table)
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""", b"", b"pointer assignment requires a mutable pointee",
|
||||||
|
),
|
||||||
|
"callback_readonly_array_assignment": (
|
||||||
|
"identity proc(value i16) i16 { return value }\nmain proc() i16 { callbacks [1]@proc(value i16) i16 := [identity]\ncallbacks[0] = identity\nreturn 42 }",
|
||||||
|
b"", b"indexed assignment requires a writable array element",
|
||||||
|
),
|
||||||
|
"callback_invalid_annotation_recovery": (
|
||||||
|
"bad proc(callback @proc(value void) i16) void {}\nmain proc() i16 { return 42 }",
|
||||||
|
b"42\n", b"void cannot be stored",
|
||||||
|
),
|
||||||
|
"callback_mutable_signature_rejected": (
|
||||||
|
"bad proc(callback @mut proc(value i16) i16) void {}\nmain proc() i16 { return 42 }",
|
||||||
|
b"42\n", b"pointer requires a supported concrete pointee type",
|
||||||
|
),
|
||||||
|
"callback_many_signature_rejected": (
|
||||||
|
"bad proc(callback *proc(value i16) i16) void {}\nmain proc() i16 { return 42 }",
|
||||||
|
b"42\n", b"pointer requires a supported concrete pointee type",
|
||||||
|
),
|
||||||
"pointer_assignment_evaluates_rhs_first_once": (
|
"pointer_assignment_evaluates_rhs_first_once": (
|
||||||
"""
|
"""
|
||||||
Box :: struct { value i16, count i16 }
|
Box :: struct { value i16, count i16 }
|
||||||
@@ -250,6 +387,87 @@ CASES = {
|
|||||||
"unused proc() int { return 42^ }\nmain proc() int { return 42 }",
|
"unused proc() int { return 42^ }\nmain proc() int { return 42 }",
|
||||||
b"42\n", b"dereference requires",
|
b"42\n", b"dereference requires",
|
||||||
),
|
),
|
||||||
|
"pointer_addition_preserves_alias_and_mutability": (
|
||||||
|
"""
|
||||||
|
advance proc(p *mut i16, count usize) *mut i16 { return p + count }
|
||||||
|
main proc() [3]i16 {
|
||||||
|
values [3]mut i16 := [1, 2, 3]
|
||||||
|
pointer :: advance((&values).ptr, 1)
|
||||||
|
pointer^ = 40
|
||||||
|
(pointer + 1)^ = pointer^ + 2
|
||||||
|
return [values[0], values[1], values[2]]
|
||||||
|
}
|
||||||
|
""", b"[1, 40, 42]\n", None,
|
||||||
|
),
|
||||||
|
"pointer_addition_aggregate_stride_and_identity": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x i16, y i64 }
|
||||||
|
main proc() bool {
|
||||||
|
values [3]mut Point := [
|
||||||
|
Point { x = 1, y = 2 }, Point { x = 3, y = 4 }, Point { x = 5, y = 6 },
|
||||||
|
]
|
||||||
|
pointer :: (&values).ptr
|
||||||
|
next :: pointer + (1 + 1)
|
||||||
|
next.y = 42
|
||||||
|
last *mut Point :: &values[2]
|
||||||
|
return (next == last and values[2].y == 42
|
||||||
|
and pointer + 3 == next + 1)
|
||||||
|
}
|
||||||
|
""", b"true\n", None,
|
||||||
|
),
|
||||||
|
"pointer_addition_evaluates_operands_once_in_order": (
|
||||||
|
"""
|
||||||
|
first proc(count @mut usize, values *mut i16) *mut i16 {
|
||||||
|
count^ = count^ + 1
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
second proc(count @mut usize) usize {
|
||||||
|
previous :: count^
|
||||||
|
count^ = count^ + 1
|
||||||
|
return previous
|
||||||
|
}
|
||||||
|
main proc() [2]i16 {
|
||||||
|
count usize := 0
|
||||||
|
values [2]mut i16 := [1, 42]
|
||||||
|
pointer :: first(&count, (&values).ptr) + second(&count)
|
||||||
|
return [pointer^, i16(count)]
|
||||||
|
}
|
||||||
|
""", b"[42, 2]\n", None,
|
||||||
|
),
|
||||||
|
"pointer_addition_zero_sized_elements": (
|
||||||
|
"""
|
||||||
|
Empty :: struct {}
|
||||||
|
main proc() bool {
|
||||||
|
values [2]mut Empty := [Empty {}, Empty {}]
|
||||||
|
pointer :: (&values).ptr
|
||||||
|
return pointer + 2 == pointer
|
||||||
|
}
|
||||||
|
""", b"true\n", None,
|
||||||
|
),
|
||||||
|
"pointer_addition_requires_usize_offset": (
|
||||||
|
"main proc() i16 { values [2]mut i16 := [1, 42]\noffset i16 :: 1\nreturn ((&values).ptr + offset)^ }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"pointer_addition_single_pointer_rejected": (
|
||||||
|
"main proc() i16 { value i16 := 42\nreturn (&value + 1)^ }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"pointer_subtraction_rejected": (
|
||||||
|
"main proc() i16 { values [2]mut i16 := [1, 42]\nreturn ((&values).ptr - 1)^ }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"pointer_reversed_addition_rejected": (
|
||||||
|
"main proc() i16 { values [2]mut i16 := [1, 42]\nreturn (1 + (&values).ptr)^ }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"pointer_addition_preserves_readonly_pointee": (
|
||||||
|
"main proc() i16 { values [2]i16 := [1, 42]\npointer :: (&values).ptr + 1\npointer^ = 7\nreturn values[1] }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"pointer_addition_unused_error_recovers": (
|
||||||
|
"bad proc(p *i16) *i16 { return p + true }\nmain proc() i16 { return 42 }",
|
||||||
|
b"42\n", b"",
|
||||||
|
),
|
||||||
"match_float_ranges": (
|
"match_float_ranges": (
|
||||||
"""
|
"""
|
||||||
score proc(n f64) i16 {
|
score proc(n f64) i16 {
|
||||||
@@ -1257,7 +1475,28 @@ def check_record_packages(temporary, executable):
|
|||||||
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 3
|
types.write_text(
|
||||||
|
"Point :: struct { x i16, y i16 = 2 }\n"
|
||||||
|
"Callback :: alias @proc(p @mut Point, amount i16) i16\n"
|
||||||
|
"VTable :: struct { run Callback }\n"
|
||||||
|
)
|
||||||
|
(library / "functions.hon").write_text(
|
||||||
|
"increment proc(p @mut Point, amount i16) i16 { p.x = p.x + amount\nreturn p.x + p.y }\n"
|
||||||
|
"invoke proc(table VTable, values *mut Point) i16 { return table.run(&(values + 1)^, 40) }\n"
|
||||||
|
)
|
||||||
|
(app / "main.hon").write_text(textwrap.dedent("""
|
||||||
|
lib :: import "../library"
|
||||||
|
main proc() i16 {
|
||||||
|
values [2]mut lib.Point := [lib.Point { x = 7 }, lib.Point { x = 0 }]
|
||||||
|
callback lib.Callback :: lib.increment
|
||||||
|
table :: lib.VTable { run = callback }
|
||||||
|
result :: lib.invoke(table, (&values).ptr)
|
||||||
|
if (values[0].x != 7 or values[1].x != 40) return -1
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
""").strip() + "\n")
|
||||||
|
check(app, executable, (b"42\n", None))
|
||||||
|
return 4
|
||||||
|
|
||||||
|
|
||||||
def check_data_model_packages(temporary, executable):
|
def check_data_model_packages(temporary, executable):
|
||||||
|
|||||||
Reference in New Issue
Block a user