feat: support callback signatures and many-pointer addition

This commit is contained in:
2026-10-26 11:32:29 +01:00
parent 676f3666b4
commit 7856f4f963
8 changed files with 417 additions and 50 deletions
+31 -6
View File
@@ -1,5 +1,5 @@
#! 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 ... -> :: | :=
#! * literal .. -> int | float | string | 'true' | 'false'
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
@@ -45,6 +45,7 @@ ExprKind :: enum {
range
array_type
pointer_type
function_type
array_literal
record_type
record_literal
@@ -112,6 +113,7 @@ NodeKind :: enum {
expr_range
expr_array_type
expr_pointer_type
expr_function_type
expr_array_literal
expr_record_type
expr_record_literal
@@ -511,6 +513,19 @@ add_pointer_type proc(
}, 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 {
start :: builder.storage.extra.items.len
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 {
node :: get_node(&tree.storage, id)
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 {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl)
return usize(node.data1.extra - node.main.extra - 4)
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type)
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 {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl)
debug.assert(index < usize(node.data1.extra - node.main.extra - 4))
return NodeId(tree.storage.extra.items[usize(node.main.extra) + 4 + index])
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type)
start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra
debug.assert(index < usize(node.data1.extra - start))
return NodeId(tree.storage.extra.items[usize(start) + index])
}
param proc(tree @Tree, id NodeId) ParamView {
@@ -1103,6 +1127,7 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_range: .range
.expr_array_type: .array_type
.expr_pointer_type: .pointer_type
.expr_function_type: .function_type
.expr_array_literal: .array_literal
.expr_record_type: .record_type
.expr_record_literal: .record_literal
+9 -1
View File
@@ -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.
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)) {
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
right :: try lower_expression(state, expression.right)
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 {
.add: .add
.subtract: .subtract
+50 -27
View File
@@ -149,33 +149,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
params arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
defer arraylist.deinit(&params)
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)
}
try parse_parameters(state, &params)
_ = try consume(state, .close_paren)
return_type :: try parse_type(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
parse_block proc(state @mut State) ast.NodeId ! ParseError {
open :: try consume(state, .open_curly)
@@ -610,6 +614,25 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
state.next_token += 1
mutable :: next_is(state, .mut)
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(&params)
try parse_parameters(state, &params)
_ = 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)
return try ast.add_pointer_type(state.builder, ast.Span{
start = token.span.start,
+71 -10
View File
@@ -351,7 +351,7 @@ check proc(
callee :: ast.call_expr(tree, id).callee
signature :: try infer_shape(&state, callee, false)
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)])
} else if (signature.kind == .invalid) {
seed(&state, id, signature)
@@ -391,7 +391,7 @@ check proc(
}
.binary: {
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.right)
}
@@ -411,7 +411,7 @@ check proc(
}
.binary: {
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 (!bool_equality(&state, id)) try comparison_operands(&state, id)
} else {
@@ -1135,7 +1135,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
.array_type: {
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
}
.record_type, .union_type, .enum_type, .distinct_type, .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)
if (type.kind != .invalid) {
type.value_kind = type.kind
@@ -1261,6 +1261,17 @@ logical proc(operator ast.BinaryOperator) bool {
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
bool_equality proc(state @mut State, id ast.NodeId) bool {
expression :: ast.binary_expr(state.tree, id)
@@ -1516,14 +1527,15 @@ addressable_target proc(state @mut State, id ast.NodeId) bool {
@hide:file
same_signature proc(state @State, left, right ast.NodeId) bool {
if (left == right) return true
tree :: state.tree
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
left_result :: ast.function_decl(tree, left).return_type
right_result :: ast.function_decl(tree, right).return_type
left_result :: ast.callable_result(tree, left)
right_result :: ast.callable_result(tree, right)
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| {
left_param :: ast.function_param(tree, left, i)
right_param :: ast.function_param(tree, right, i)
left_param :: ast.param(tree, ast.function_param(tree, left, i)).type
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
}
return true
@@ -1635,6 +1647,12 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
left :: state.types[usize(expression.left)]
right :: state.types[usize(expression.right)]
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)
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
@@ -1901,7 +1919,7 @@ check_pointer_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
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 element.kind == .type_value
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)))
@@ -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 })
}
@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
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
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
state.cells[index].visiting = true
defer state.cells[index].visiting = false
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .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) {
view :: ast.unary_expr(state.tree, id)
if (view.operator == .address_of or view.operator == .dereference) {
@@ -2688,7 +2748,7 @@ type_syntax proc(kind ast.ExprKind) bool {
return (
kind == .record_type or kind == .union_type or kind == .enum_type
or kind == .distinct_type or kind == .alias_expr
or kind == .pointer_type
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)
if (kind == .record_type) return try check_record(state, id, nominal)
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) {
type :: try check_union(state, id, nominal)
state.cells[usize(id)].type = type
+7
View File
@@ -572,6 +572,13 @@ resolve_expr proc(
}
}
.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: {
type :: ast.array_type(tree, id)
if (type.count) |count| try resolve_expr(state, scope, count)