feat: support source pointers and references

This commit is contained in:
2026-10-26 10:37:08 +01:00
parent 9966a1f523
commit 676f3666b4
17 changed files with 981 additions and 78 deletions
+44 -4
View File
@@ -1,9 +1,9 @@
#! grammar:
#! * type ..... -> identifier {'.' member} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
#! * type ..... -> identifier {'.' member} | ('@' | '*') ['mut'] type | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
#! * binding ... -> :: | :=
#! * literal .. -> int | float | string | 'true' | 'false'
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&') expr | expr '^' | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
#! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string
#! * block .... -> '{' {stmt} '}'
#! * arm ...... -> block | stmt (single statements become canonical blocks)
@@ -11,7 +11,7 @@
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
#! * stmt ..... -> decl | expr | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr arm ['else' arm] | 'while' expr arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
#! braceless guards/iterables require parentheses unless they are calls; else binds to the nearest if.
#! precedence (tightest first): calls/indices/selections, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
#! precedence (tightest first): calls/indices/selections/dereference, unary -/!/& higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
#! binary operators associate left; unary operators associate right.
import "@std/debug"
@@ -44,6 +44,7 @@ ExprKind :: enum {
call
range
array_type
pointer_type
array_literal
record_type
record_literal
@@ -82,7 +83,12 @@ Mutability :: enum {
constant, variable
}
UnaryOperator :: enum { negate, logical_not }
UnaryOperator :: enum {
negate
logical_not
address_of
dereference
}
BinaryOperator :: enum {
add, subtract
@@ -105,6 +111,7 @@ NodeKind :: enum {
expr_call
expr_range
expr_array_type
expr_pointer_type
expr_array_literal
expr_record_type
expr_record_literal
@@ -231,6 +238,12 @@ ArrayTypeView :: struct {
mutable bool
}
PointerTypeView :: struct {
element NodeId
mutable bool
many bool
}
RecordLiteralView :: struct {
span Span
type ?NodeId
@@ -482,6 +495,22 @@ add_array_type proc(
}, span)
}
add_pointer_type proc(
builder @mut Builder,
span Span,
element NodeId,
mutable, many bool,
) NodeId ! mem.AllocError {
mutable_flag :: if (mutable) u32(1) else u32(0)
many_flag :: if (many) u32(1) else u32(0)
return try add_node(builder, Node{
kind = .expr_pointer_type,
main = NodeData{ extra = mutable_flag },
data0 = NodeData{ extra = many_flag },
data1 = NodeData{ node_id = element },
}, span)
}
add_array_literal proc(builder @mut Builder, span Span, elements []NodeId) NodeId ! mem.AllocError {
start :: builder.storage.extra.items.len
for (elements) |element| _ = try add_extra(builder, u32(element))
@@ -1073,6 +1102,7 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_call: .call
.expr_range: .range
.expr_array_type: .array_type
.expr_pointer_type: .pointer_type
.expr_array_literal: .array_literal
.expr_record_type: .record_type
.expr_record_literal: .record_literal
@@ -1154,6 +1184,16 @@ array_type proc(tree @Tree, id NodeId) ArrayTypeView {
}
}
pointer_type proc(tree @Tree, id NodeId) PointerTypeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_pointer_type)
return PointerTypeView{
element = node.data1.node_id,
mutable = node.main.extra != 0,
many = node.data0.extra != 0,
}
}
array_element_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_array_literal)
+10
View File
@@ -89,6 +89,11 @@ Code :: enum {
unreachable_match_arm
invalid_match_capture
inactive_union_field
invalid_pointer_type
invalid_address_operand
invalid_dereference
invalid_pointer_assignment
invalid_pointer_access
}
Diagnostic :: struct {
@@ -199,5 +204,10 @@ messages :: $enummap.init(Code, []u8, {
unreachable_match_arm = "match arm is unreachable",
invalid_match_capture = "payload capture requires compatible nonvoid tagged union variants",
inactive_union_field = "tagged union field is not active",
invalid_pointer_type = "pointer requires a supported concrete pointee type",
invalid_address_operand = "address-of requires addressable storage",
invalid_dereference = "dereference requires a pointer",
invalid_pointer_assignment = "pointer assignment requires a mutable pointee",
invalid_pointer_access = "pointer does not refer to live accessible storage",
})
+163 -8
View File
@@ -42,6 +42,19 @@ Address :: struct {
scalar ?@mut ir.Value = null
initialized ?@mut bool = null
raw bool = false
# source handles retain root identity and native offsets, never frame-local address IDs.
root ?@mut ir.Value = null
root_type ir.Type = .invalid
root_array ir.ArrayId = ir.no_array
byte_offset usize = 0
}
@hide:file
Pointer :: struct {
root @mut ir.Value
root_type ir.Type
root_array ir.ArrayId
byte_offset usize
}
@hide:file
@@ -90,6 +103,8 @@ run proc(
globals[i].value = aggregate_view(global.type, global.array_type, aggregate_globals[global_offset..global_offset + cells])
global_offset += cells
}
pointers := arraylist.init(Pointer, allocator)
defer arraylist.deinit(&pointers)
frames := arraylist.init(Frame, allocator)
defer {
for (frames.items) |frame| free_frame(allocator, frame)
@@ -261,6 +276,9 @@ run proc(
cells = &[],
initialized = &local.initialized,
}
address.root = &local.value
address.root_type = address.type
address.root_array = address.array_type
if (ir.aggregate(address.type)) address.cells = local.value.data.elements
else address.scalar = &local.value
frames.items[frame_index].addresses[usize(instruction.destination)] = address
@@ -287,6 +305,9 @@ run proc(
array_type = instruction.array_type,
cells = &[],
}
address.root = &global.value
address.root_type = address.type
address.root_array = address.array_type
if (ir.aggregate(address.type)) address.cells = global.value.data.elements
else address.scalar = &global.value
frames.items[frame_index].addresses[usize(instruction.destination)] = address
@@ -305,10 +326,12 @@ run proc(
}
width :: if (base.raw) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
start :: usize(index) * width
address := Address{
type = instruction.pointee, array_type = instruction.array_type,
cells = base.cells[start..start + width], initialized = base.initialized, raw = base.raw,
}
address := base
address.type = instruction.pointee
address.array_type = instruction.array_type
address.cells = base.cells[start..start + width]
address.scalar = null
address.byte_offset += usize(index) * ir.storage_size(module, shape.element, shape.element_array)
if (!address.raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
@@ -329,14 +352,70 @@ run proc(
raw :: base.raw or shape.is_union
width :: if (raw) ir.storage_size(module, field.type, field.array_type) else if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
start :: if (raw) field.byte_offset else field.offset
address := Address{
type = instruction.pointee, array_type = instruction.array_type,
cells = base.cells[start..start + width], initialized = base.initialized, raw = raw,
}
address := base
address.type = instruction.pointee
address.array_type = instruction.array_type
address.cells = base.cells[start..start + width]
address.scalar = null
address.raw = raw
address.byte_offset += field.byte_offset
if (!raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
}
.pointer_from_address: {
id :: frames.items[frame_index].values[usize(instruction.left)].data.address
address :: frames.items[frame_index].addresses[usize(id)]
if address.initialized |initialized| {
if (!initialized^) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span)
}
value = try pointer_value(&pointers, Pointer{
root = address.root?,
root_type = address.root_type,
root_array = address.root_array,
byte_offset = address.byte_offset,
})
}
.pointer_address: {
handle :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
if (handle >= u64(pointers.items.len)) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
location :: pointers.items[usize(handle)]
root := Address{
type = location.root_type,
array_type = location.root_array,
cells = &[],
root = location.root,
root_type = location.root_type,
root_array = location.root_array,
}
if (ir.aggregate(root.type)) root.cells = root.root?^.data.elements
else root.scalar = root.root
if (project_pointer(module, root, location.byte_offset, instruction.pointee, instruction.array_type)) |address| {
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
} else {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
}
.pointer_offset: {
handle :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
if (handle >= u64(pointers.items.len)) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
address := pointers.items[usize(handle)]
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
stride :: ir.storage_size(module, instruction.pointee, instruction.array_type)
if (stride != 0 and index > u64(divtrunc!(maxval!(usize) - address.byte_offset, stride))) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
address.byte_offset += usize(index) * stride
if (address.byte_offset > ir.storage_size(module, address.root_type, address.root_array)) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
value = try pointer_value(&pointers, address)
}
.load_address, .store_address: {
id :: frames.items[frame_index].values[usize(instruction.left)].data.address
address :: frames.items[frame_index].addresses[usize(id)]
@@ -447,6 +526,11 @@ run proc(
.not_equal: left.data.boolean != right.data.boolean
else: unreachable
}
.pointer: result = match instruction.op {
.equal: left.data.unsigned == right.data.unsigned
.not_equal: left.data.unsigned != right.data.unsigned
else: unreachable
}
else: unreachable
}
value = ir.Value{ type = .bool, data = ir.ValueData{ boolean = result } }
@@ -459,6 +543,77 @@ run proc(
unreachable
}
@hide:file
pointer_value proc(pointers @mut arraylist.ArrayList(Pointer), address Pointer) ir.Value ! mem.AllocError {
# ponytail: linear location interning; use a location-keyed map if distinct-address workloads grow.
root_bits :: ptrcast!(@usize, &address.root)^
for (pointers.items) |location, handle| {
if (ptrcast!(@usize, &location.root)^ == root_bits and location.byte_offset == address.byte_offset) {
# refresh metadata if a later invocation reused a released frame's allocation.
pointers.items[handle] = address
return ir.Value{ type = .pointer, data = ir.ValueData{ unsigned = u64(handle) } }
}
}
handle :: pointers.items.len
try arraylist.append(pointers, address)
return ir.Value{ type = .pointer, data = ir.ValueData{ unsigned = u64(handle) } }
}
@hide:file
project_pointer proc(
module @ir.Module,
base Address,
offset usize,
type ir.Type,
array_type ir.ArrayId,
) ?Address {
if (offset == 0 and base.type == type and base.array_type == array_type) return base
if (!ir.aggregate(base.type)) return null
shape :: module.arrays.items[usize(base.array_type)]
if (base.raw or shape.is_union) {
width :: ir.storage_size(module, type, array_type)
if (offset > base.cells.len or width > base.cells.len - offset) return null
address := base
address.type = type
address.array_type = array_type
address.cells = base.cells[offset..offset + width]
address.scalar = null
address.raw = true
address.byte_offset += offset
return address
}
if (shape.kind == .array) {
stride :: ir.storage_size(module, shape.element, shape.element_array)
index :: if (stride == 0) usize(0) else divtrunc!(offset, stride)
if (index >= shape.len) return null
width :: element_cells(module, shape)
start :: index * width
child := base
child.type = shape.element
child.array_type = shape.element_array
child.cells = base.cells[start..start + width]
child.scalar = null
child.byte_offset += index * stride
if (!ir.aggregate(child.type)) child.scalar = &child.cells[0]
return project_pointer(module, child, offset - index * stride, type, array_type)
}
for (shape.fields) |field| {
if (offset < field.byte_offset) continue
relative :: offset - field.byte_offset
if (relative > ir.storage_size(module, field.type, field.array_type)) continue
width :: if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
child := base
child.type = field.type
child.array_type = field.array_type
child.cells = base.cells[field.offset..field.offset + width]
child.scalar = null
child.byte_offset += field.byte_offset
if (!ir.aggregate(child.type)) child.scalar = &child.cells[0]
if (project_pointer(module, child, relative, type, array_type)) |address| return address
}
return null
}
@hide:file
push_frame proc(
allocator mem.Allocator,
+9 -2
View File
@@ -24,6 +24,7 @@ Type :: enum {
array
record
address
pointer
i8, i16, i32, isize
u8, u16, u32, u64, usize
}
@@ -74,6 +75,7 @@ integer_min proc(type Type) i64 {
ValueData :: union {
integer i64
# source pointers use run-wide handles; internal addresses use frame-local IDs below.
unsigned u64
real f64
boolean bool
@@ -141,6 +143,9 @@ Op :: enum {
field_address
load_address
store_address
pointer_from_address
pointer_address
pointer_offset
negate
convert
logical_not
@@ -160,6 +165,7 @@ Op :: enum {
Instruction :: struct {
op Op
type Type = .void
# pointer operations describe their pointee here, not aggregate result storage.
array_type ArrayId = no_array
pointee Type = .invalid
span diagnostics.Span = diagnostics.Span{ start = 0, end = 0 }
@@ -395,7 +401,7 @@ storage_alignment proc(module @Module, type Type, shape ArrayId) usize {
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
bits :: integer_bits(type)
if (bits != 0) return divtrunc!(bits, 8)
return if (type == .float or type == .string or type == .function) 8 else 1
return if (type == .float or type == .string or type == .function or type == .pointer) 8 else 1
}
storage_size proc(module @Module, type Type, shape ArrayId) usize {
@@ -403,7 +409,7 @@ storage_size proc(module @Module, type Type, shape ArrayId) usize {
bits :: integer_bits(type)
if (bits != 0) return divtrunc!(bits, 8)
return match type {
.float, .function: 8
.float, .function, .pointer: 8
.string: 16
.bool: 1
.void: 0
@@ -457,6 +463,7 @@ write_storage proc(module @Module, cells []mut Value, value Value) void {
.bool: bits = if (value.data.boolean) 1 else 0
.string: bits = u64(value.data.string)
.function: bits = u64(value.data.function)
.pointer: bits = value.data.unsigned
.void: return
else: bits = ptrcast!(@u64, &value.data.integer)^
}
+2
View File
@@ -295,6 +295,8 @@ scan proc(
',': .comma
'|': .pipe
'@': .at
'&': .ampersand
'^': .caret
'(': .open_paren
')': .close_paren
'[': .open_bracket
+2 -1
View File
@@ -48,7 +48,8 @@ TokenKind :: enum {
star, slash
range, range_inclusive
pipe, at
at, ampersand, caret
pipe
# logical
bang
+21
View File
@@ -168,6 +168,27 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
}
.pointer_from_address: {
address :: try load(state, instruction.left)
flag :: fresh(state)
position :: location(state.line_starts, instruction.span)
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, address})
try io.print(" call void @check_init(ptr %r{}, i1 true, i64 {}, i64 {}, i64 {})\n", output, {flag, usize(instruction.span.source), position.line, position.column})
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {result, address})
}
.pointer_address: {
pointer :: try load(state, instruction.left)
address :: fresh(state)
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
}
.pointer_offset: {
pointer :: try load(state, instruction.left)
index :: try load(state, instruction.right)
try io.print(" %r{} = getelementptr ", output, {result})
try write_type(output, state.module, Type{ kind = instruction.pointee, shape = instruction.array_type })
try io.print(", ptr %r{}, i64 %r{}\n", output, {pointer, index})
}
.load_address, .store_address: {
address :: try load(state, instruction.left)
pointer :: fresh(state)
+2 -1
View File
@@ -133,7 +133,7 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
.float: try io.write_all(output, "double")
.bool: try io.write_all(output, "i1")
.string: try io.write_all(output, "%string")
.function: try io.write_all(output, "ptr")
.function, .pointer: try io.write_all(output, "ptr")
.address: try io.write_all(output, "%address")
.array: {
shape :: module.arrays.items[usize(type.shape)]
@@ -275,6 +275,7 @@ print_call proc(output io.Writer, module @ir.Module, type Type, operand usize) v
.float: try io.print(" call void @print_float(double %r{})\n", output, {operand})
.bool: try io.print(" call void @print_bool(i1 %r{})\n", output, {operand})
.string: try io.print(" call void @print_string(%string %r{})\n", output, {operand})
.pointer: try io.write_all(output, " call void @out(i32 1, ptr @pointer_text, i64 9)\n")
.array, .record: {
try io.print(" call void @print_array{}(", output, {u32(type.shape)})
try write_type(output, module, type)
+1
View File
@@ -37,6 +37,7 @@ runtime ::
`@fmt_float = private constant [6 x i8] c"%.17g\00"
`@true = private constant [4 x i8] c"true"
`@false = private constant [5 x i8] c"false"
`@pointer_text = private constant [9 x i8] c"<pointer>"
`@colon = private constant [1 x i8] c":"
`@error = private constant [9 x i8] c": error: "
`@runtime_trap = private constant [16 x i8] c": runtime trap: "
+99 -5
View File
@@ -149,6 +149,7 @@ lower_type proc(state @State, type typecheck.Type) ir.Type {
.u64: .u64
.array: .array
.record: .record
.pointer: .pointer
.float: .float
.bool: .bool
.string: .string
@@ -590,6 +591,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
.assignment_stmt: {
assignment :: ast.assignment_stmt(tree, id)
if (ast.expr_kind(tree, assignment.target) == .index
or ast.expr_kind(tree, assignment.target) == .unary
or (ast.expr_kind(tree, assignment.target) == .selection
and sema.binding(state.resolution, assignment.target).kind == .none)) {
value :: try lower_expression(state, assignment.value)
@@ -924,10 +926,20 @@ lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.Va
@hide:file
is_place proc(state @State, id ast.NodeId) bool {
match ast.expr_kind(state.tree, id) {
.index: return is_place(state, ast.index_expr(state.tree, id).base)
.unary: return ast.unary_expr(state.tree, id).operator == .dereference
.index: {
base :: ast.index_expr(state.tree, id).base
return typecheck.type_of(state.checked, base).kind == .pointer or is_place(state, base)
}
.selection: {
if (sema.binding(state.resolution, id).kind == .none) {
return is_place(state, ast.selection_expr(state.tree, id).base)
base :: ast.selection_expr(state.tree, id).base
checked :: typecheck.type_of(state.checked, base)
if (checked.kind == .pointer) {
pointer :: typecheck.pointer_type(state.checked, checked.pointer_type)
return pointer.element.kind == .record
}
return is_place(state, base)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return false
@@ -945,14 +957,37 @@ is_place proc(state @State, id ast.NodeId) bool {
}
}
@hide:file
pointer_address proc(
state @mut State, pointer ir.ValueId, element typecheck.Type, span diagnostics.Span,
) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
return try emit(state, ir.Instruction{
op = .pointer_address, type = .address, left = pointer,
pointee = lower_type(state, element), array_type = try array_shape(state, element), span = span,
})
}
@hide:file
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id)
checked :: typecheck.type_of(state.checked, id)
if (ast.expr_kind(state.tree, id) == .unary) {
expression :: ast.unary_expr(state.tree, id)
pointer :: try lower_expression(state, expression.operand)
if (state.terminated or trap_type(state, id)) return ir.no_value
return try pointer_address(state, pointer, checked, span)
}
if (ast.expr_kind(state.tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none) {
expression :: ast.selection_expr(state.tree, id)
base :: try lower_place(state, expression.base)
base_type :: typecheck.type_of(state.checked, expression.base)
base := ir.no_value
if (base_type.kind == .pointer) {
pointer :: try lower_expression(state, expression.base)
if (state.terminated or trap_type(state, id)) return ir.no_value
element :: typecheck.pointer_type(state.checked, base_type.pointer_type).element
base = try pointer_address(state, pointer, element, span)
} else base = try lower_place(state, expression.base)
if (state.terminated or trap_type(state, id)) return ir.no_value
return try emit(state, ir.Instruction{
op = .field_address,
@@ -966,7 +1001,23 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
}
if (ast.expr_kind(state.tree, id) == .index) {
expression :: ast.index_expr(state.tree, id)
base :: try lower_place(state, expression.base)
base_type :: typecheck.type_of(state.checked, expression.base)
base := ir.no_value
if (base_type.kind == .pointer) {
pointer :: try lower_expression(state, expression.base)
if (state.terminated or trap_type(state, id)) return ir.no_value
descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type)
if (descriptor.many) {
index :: try lower_expression(state, expression.index)
if (state.terminated) return ir.no_value
offset :: try emit(state, ir.Instruction{
op = .pointer_offset, type = .pointer, left = pointer, right = index,
pointee = lower_type(state, checked), array_type = try array_shape(state, checked), span = span,
})
return try pointer_address(state, offset, checked, span)
}
base = try pointer_address(state, pointer, descriptor.element, span)
} else base = try lower_place(state, expression.base)
if (state.terminated) return ir.no_value
index :: try lower_expression(state, expression.index)
if (state.terminated or trap_type(state, id)) return ir.no_value
@@ -1014,7 +1065,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
# ranges have no physical value; only lower_for decomposes an iterable.
if (!trap_type(state, id)) unreachable
}
.array_type, .record_type, .enum_type, .union_type, .distinct_type: {
.array_type, .pointer_type, .record_type, .enum_type, .union_type, .distinct_type: {
if (!trap_type(state, id)) {
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
}
@@ -1091,6 +1142,27 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
.identifier, .selection: {
if (ast.expr_kind(tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none) {
selection :: ast.selection_expr(tree, id)
base_type :: typecheck.type_of(state.checked, selection.base)
if (base_type.kind == .pointer) {
descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type)
if (!descriptor.many and descriptor.element.kind == .array) {
pointer :: try lower_expression(state, selection.base)
if (state.terminated or trap_type(state, id)) return ir.no_value
member :: ast.source_text(tree, ast.node_span(tree, selection.member))
array :: typecheck.array_type(state.checked, descriptor.element.array_type)
if (mem.eql(member, "len")) return try constant(state, ir.Value{
type = .usize, data = ir.ValueData{ unsigned = u64(array.len) },
}, span)
if (mem.eql(member, "ptr")) {
zero :: try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = 0 } }, span)
return try emit(state, ir.Instruction{
op = .pointer_offset, type = .pointer, left = pointer, right = zero,
pointee = lower_type(state, array.element), array_type = try array_shape(state, array.element), span = span,
})
}
}
}
if (is_place(state, id)) {
address :: try lower_place(state, id)
if (state.terminated) return ir.no_value
@@ -1120,6 +1192,27 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
}
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .address_of) {
if (!is_place(state, expression.operand)) {
_ = try lower_expression(state, expression.operand)
if (!state.terminated) _ = trap_type(state, id)
return ir.no_value
}
address :: try lower_place(state, expression.operand)
if (state.terminated or trap_type(state, id)) return ir.no_value
element :: typecheck.type_of(state.checked, expression.operand)
return try emit(state, ir.Instruction{
op = .pointer_from_address, type = .pointer, left = address,
pointee = lower_type(state, element), array_type = try array_shape(state, element), span = span,
})
}
if (expression.operator == .dereference) {
address :: try lower_place(state, id)
if (state.terminated) return ir.no_value
return try emit(state, ir.Instruction{
op = .load_address, type = type, array_type = shape, left = address, span = span,
})
}
if (expression.operator == .negate and ir.integer_bits(type) != 0 and !ir.unsigned_integer(type) and ast.expr_kind(tree, expression.operand) == .literal_int) {
text :: ast.source_text(tree, ast.node_span(tree, expression.operand))
magnitude_limit :: ir.integer_max(type) + 1
@@ -1134,6 +1227,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
op ir.Op :: match expression.operator {
.negate: .negate
.logical_not: .logical_not
.address_of, .dereference: unreachable
}
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
}
+1
View File
@@ -208,6 +208,7 @@ print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.
.float: try io.print("{d}", output, {value.data.real})
.bool: try io.print("{}", output, {value.data.boolean})
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
.pointer: try io.write_all(output, "<pointer>")
.array, .record: {
shape :: module.arrays.items[usize(value.array_type)]
count :: if (value.type == .array) shape.len else shape.fields.len
+53 -8
View File
@@ -240,6 +240,8 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
or next_n_is(state, 1, .struct)
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
or next_n_is(state, 1, .distinct)
or next_n_is(state, 1, .at)
or (next_n_is(state, 1, .star) and pointer_binding_starts(state))
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
or next_n_is(state, 1, .double_colon)
or next_n_is(state, 1, .colon_equal)
@@ -254,7 +256,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
.return: return try parse_return(state, allow_else)
.break, .continue: return try parse_loop_control(state, allow_else)
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct,
.enum, .union, .distinct, .alias, .dot, .match: {
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star: {
expression :: try parse_expr(state)
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
return try ast.add_expression_stmt(state.builder, expression)
@@ -603,6 +605,18 @@ parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
@hide:file
parse_type proc(state @mut State) ast.NodeId ! ParseError {
if (next_is(state, .at) or next_is(state, .star)) {
token :: current(state)
state.next_token += 1
mutable :: next_is(state, .mut)
if (mutable) state.next_token += 1
element :: try parse_type(state)
return try ast.add_pointer_type(state.builder, ast.Span{
start = token.span.start,
end = ast.builder_span(state.builder, element).end,
source = token.span.source,
}, element, mutable, token.kind == .star)
}
if next_is(state, .open_bracket) {
open :: try consume(state, .open_bracket)
count ?ast.NodeId := null
@@ -653,6 +667,7 @@ starts_type proc(kind lexer.TokenKind) bool {
return (
kind == .ident or kind == .open_bracket or kind == .struct
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
or kind == .at or kind == .star
)
}
@@ -979,11 +994,7 @@ parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseEr
value ?ast.NodeId := null
named :: member_name(current(state).kind) and (
(!declaration and next_n_is(state, 1, .equal))
or (declaration and (
next_n_is(state, 1, .ident) or next_n_is(state, 1, .open_bracket)
or next_n_is(state, 1, .struct) or next_n_is(state, 1, .enum)
or next_n_is(state, 1, .union) or next_n_is(state, 1, .distinct)
))
or (declaration and starts_type(state.tokens[state.next_token + 1].kind))
)
if (named) {
token :: if (declaration) try consume(state, .ident) else try consume_member(state, false)
@@ -1067,7 +1078,11 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
state.next_token += 1
operand :: try parse_expr_bp(state, right_bp)
operand_span :: ast.builder_span(state.builder, operand)
operator ast.UnaryOperator :: if (token.kind == .bang) .logical_not else .negate
operator ast.UnaryOperator :: match token.kind {
.bang: .logical_not
.ampersand: .address_of
else: .negate
}
yield try ast.add_unary(
state.builder,
ast.Span{
@@ -1086,6 +1101,16 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
while true {
skip_continuation_newlines(state)
token = current(state)
if (token.kind == .caret) {
state.next_token += 1
left = try ast.add_unary(state.builder, ast.Span{
start = state.tokens[expression_start].span.start,
end = token.span.end,
source = token.span.source,
}, .dereference, left)
left_grouped = false
continue
}
if (token.kind == .open_paren) {
left = try parse_call(state, left, state.tokens[expression_start].span.start)
left_grouped = false
@@ -1324,6 +1349,23 @@ invalid_array_literal proc(state @mut State, start, end u32) ast.NodeId ! ParseE
}, diagnostic)
}
@hide:file
pointer_binding_starts proc(state @State) bool {
depth := usize(0)
for (state.next_token + 2)..(state.tokens.len) |i| {
kind :: state.tokens[i].kind
if (kind == .open_curly or kind == .open_bracket or kind == .open_paren) depth += 1
if (kind == .close_curly or kind == .close_bracket or kind == .close_paren) {
if (depth == 0) return false
depth -= 1
}
if (depth != 0) continue
if (kind == .double_colon or kind == .colon_equal) return true
if (kind == .newline or kind == .eof or kind == .equal) return false
}
return false
}
@hide:file
named_binding_starts proc(state @State) bool {
return (
@@ -1331,6 +1373,8 @@ named_binding_starts proc(state @State) bool {
and (
next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc)
or next_n_is(state, 1, .at)
or (next_n_is(state, 1, .star) and pointer_binding_starts(state))
)
)
}
@@ -1339,6 +1383,7 @@ named_binding_starts proc(state @State) bool {
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state)
match token.kind {
.at, .star: return try parse_type(state)
.struct: return try parse_record_type(state)
.enum: return try parse_enum_type(state)
.union: return try parse_union_type(state)
@@ -1620,7 +1665,7 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
@hide:file
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
return match kind {
.minus, .bang: BindingPowerScalar(50)
.minus, .bang, .ampersand: BindingPowerScalar(50)
else: null
}
}
+302 -45
View File
@@ -18,6 +18,7 @@ TypeKind :: enum {
function
range
array
pointer
record
enumeration
type_value
@@ -72,11 +73,21 @@ ArrayType :: struct {
mutable bool
}
PointerId :: distinct u32
no_pointer :: maxval!(PointerId)
PointerType :: struct {
element Type
mutable bool
many bool
}
Type :: struct {
kind TypeKind = .none
function ast.NodeId = ast.NodeId(maxval!(u32))
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
array_type ArrayId = no_array
pointer_type PointerId = no_pointer
range_element TypeKind = .none
record_type RecordId = no_record
enum_type EnumId = no_enum
@@ -88,6 +99,7 @@ Type :: struct {
Storage :: struct {
types []mut Type
arrays arraylist.ArrayList(ArrayType)
pointers arraylist.ArrayList(PointerType)
records arraylist.ArrayList(RecordType)
enums arraylist.ArrayList(EnumType)
backings []mut Type
@@ -151,6 +163,7 @@ Cell :: struct {
producer ast.NodeId = ast.NodeId(maxval!(u32))
capture ast.NodeId = ast.NodeId(maxval!(u32))
record_checking bool = false
record_pointer_depth usize = 0
nominal bool = false
match_pattern bool = false
first_edge usize = no_index
@@ -179,6 +192,7 @@ State :: struct {
groups []mut GroupLink
edges arraylist.ArrayList(Edge)
arrays arraylist.ArrayList(ArrayType)
pointers arraylist.ArrayList(PointerType)
records arraylist.ArrayList(RecordType)
initializers arraylist.ArrayList(RecordInitializer)
enums arraylist.ArrayList(EnumType)
@@ -191,6 +205,7 @@ State :: struct {
slots []mut usize
collecting_assignment bool = false
has_assignments bool = false
pointer_depth usize = 0
}
#! borrows the AST, resolution, and diagnostic store only during checking.
@@ -235,6 +250,7 @@ check proc(
groups = groups,
edges = arraylist.init(allocator),
arrays = arraylist.init(allocator),
pointers = arraylist.init(allocator),
records = arraylist.init(allocator),
initializers = arraylist.init(allocator),
slots = slots,
@@ -247,6 +263,7 @@ check proc(
defer arraylist.deinit(&state.edges)
defer arraylist.deinit(&state.variants)
errdefer arraylist.deinit(&state.arrays)
errdefer arraylist.deinit(&state.pointers)
errdefer {
for (state.enums.items) |descriptor| mem.free(allocator, descriptor.members)
arraylist.deinit(&state.enums)
@@ -278,7 +295,7 @@ check proc(
binding :: sema.binding(resolution, target)
if (binding.kind == .invalid) {
cells[i].type = invalid_type(binding.data.diagnostic)
} else if (binding.kind == .declaration and !mutable_target(&state, target)) {
} else if (binding.kind == .declaration and !mutable_target(&state, target, false)) {
cells[i].type = invalid_type(try diagnostics.add(
diagnostic_store, .invalid_assignment_target, ast.node_span(tree, target),
))
@@ -511,6 +528,7 @@ check proc(
return Result{ storage = Storage{
types = types,
arrays = state.arrays,
pointers = state.pointers,
records = state.records,
initializers = state.initializers,
enums = state.enums,
@@ -544,6 +562,10 @@ array_type proc(result @Result, id ArrayId) ArrayType {
return result.storage.arrays.items[usize(id)]
}
pointer_type proc(result @Result, id PointerId) PointerType {
return result.storage.pointers.items[usize(id)]
}
record_type_count proc(result @Result) usize {
return result.storage.records.items.len
}
@@ -621,6 +643,7 @@ initializer_field proc(result @Result, literal ast.NodeId, index usize) ast.Node
deinit proc(result @mut Result) void {
mem.free(result.storage.allocator, result.storage.types)
arraylist.deinit(&result.storage.arrays)
arraylist.deinit(&result.storage.pointers)
for (result.storage.enums.items) |descriptor| mem.free(result.storage.allocator, descriptor.members)
arraylist.deinit(&result.storage.enums)
mem.free(result.storage.allocator, result.storage.backings)
@@ -741,6 +764,12 @@ collect_invalid proc(state @mut State, id ast.NodeId) void {
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
state.cells[usize(id)].role = .annotation
if (ast.expr_kind(state.tree, id) == .pointer_type) {
type :: try check_pointer_type(state, id)
state.types[usize(id)] = type
state.cells[usize(id)].type = type
return type
}
if (type_syntax(ast.expr_kind(state.tree, id))) {
type :: try check_type_expression(state, id, false)
state.types[usize(id)] = type
@@ -1047,7 +1076,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
if (expression.operator == .negate) state.cells[usize(id)].producer = id
if (expression.operator == .logical_not) {
state.cells[usize(id)].type = Type{ kind = .bool }
} else {
} else if (expression.operator == .negate) {
state.cells[usize(id)].numeric = true
}
operand :: expression.operand
@@ -1106,7 +1135,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
.array_type: {
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
}
.record_type, .union_type, .enum_type, .distinct_type: {
.record_type, .union_type, .enum_type, .distinct_type, .pointer_type: {
type := try check_type_expression(state, id, state.cells[usize(id)].nominal)
if (type.kind != .invalid) {
type.value_kind = type.kind
@@ -1240,7 +1269,8 @@ bool_equality proc(state @mut State, id ast.NodeId) bool {
return (
(expression.operator == .equal or expression.operator == .not_equal)
and ((left.kind == .bool and right.kind == .bool)
or left.kind == .enumeration or right.kind == .enumeration)
or left.kind == .enumeration or right.kind == .enumeration
or left.kind == .pointer or right.kind == .pointer)
)
}
@@ -1308,6 +1338,14 @@ numeric_constraint proc(state @mut State, expression, operand, operation ast.Nod
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(id)].kind == .invalid) return
index :: root(state, usize(id))
if (expected.kind == .pointer and state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .unary
and ast.unary_expr(state.tree, id).operator == .address_of) {
state.cells[index].expected = expected
try demand(state, ast.unary_expr(state.tree, id).operand,
state.pointers.items[usize(expected.pointer_type)].element, code)
return
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
state.cells[index].expected = expected
for 0..ast.match_arm_count(state.tree, id) |i| {
@@ -1404,24 +1442,78 @@ require_bool proc(state @mut State, operand ast.NodeId) void ! (mem.AllocError |
}
@hide:file
mutable_target proc(state @State, id ast.NodeId) bool {
if (ast.expr_kind(state.tree, id) == .index) {
view :: ast.index_expr(state.tree, id)
base :: state.types[usize(view.base)]
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base)
mutable_target proc(state @mut State, id ast.NodeId, allow_global bool) bool {
kind :: ast.expr_kind(state.tree, id)
if (kind == .unary) {
view :: ast.unary_expr(state.tree, id)
base :: group_type(state, view.operand)
return (
view.operator == .dereference and base.kind == .pointer
and state.pointers.items[usize(base.pointer_type)].mutable
)
}
if (ast.expr_kind(state.tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none) {
if (kind == .index) {
view :: ast.index_expr(state.tree, id)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.mutable and (pointer.many
or (pointer.element.kind == .array and state.arrays.items[usize(pointer.element.array_type)].mutable))
}
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base, allow_global)
}
if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
view :: ast.selection_expr(state.tree, id)
return state.types[usize(view.base)].kind == .record and mutable_target(state, view.base)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.element.kind == .record and pointer.mutable
}
return base.kind == .record and mutable_target(state, view.base, allow_global)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return false
target :: binding.data.declaration
if (state.cells[usize(target)].role != .declaration or !state.cells[usize(target)].local) return false
if (state.cells[usize(target)].role != .declaration or (!allow_global and !state.cells[usize(target)].local)) return false
return ast.statement_decl(state.tree, target).mutability == .variable
}
@hide:file
indexable_type proc(state @State, type Type) bool {
if (type.nominal != ast.NodeId(maxval!(u32))) return false
if (type.kind == .array) return true
if (type.kind != .pointer) return false
pointer :: state.pointers.items[usize(type.pointer_type)]
return pointer.many or pointer.element.kind == .array
}
@hide:file
addressable_target proc(state @mut State, id ast.NodeId) bool {
kind :: ast.expr_kind(state.tree, id)
if (kind == .unary) {
view :: ast.unary_expr(state.tree, id)
return view.operator == .dereference and group_type(state, view.operand).kind == .pointer
}
if (kind == .index) {
view :: ast.index_expr(state.tree, id)
base :: group_type(state, view.base)
return indexable_type(state, base) and (base.kind == .pointer or addressable_target(state, view.base))
}
if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
view :: ast.selection_expr(state.tree, id)
base :: group_type(state, view.base)
if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.element.kind == .record
}
return base.kind == .record and addressable_target(state, view.base)
}
binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return false
role :: state.cells[usize(binding.data.declaration)].role
return role == .declaration or role == .parameter or role == .loop_capture
}
@hide:file
same_signature proc(state @State, left, right ast.NodeId) bool {
tree :: state.tree
@@ -1437,11 +1529,22 @@ same_signature proc(state @State, left, right ast.NodeId) bool {
return true
}
@hide:file
assignable_type proc(state @State, actual, expected Type) bool {
if (same_type(state, actual, expected)) return true
if (actual.kind != .pointer or expected.kind != .pointer or actual.nominal != expected.nominal) return false
a :: state.pointers.items[usize(actual.pointer_type)]
b :: state.pointers.items[usize(expected.pointer_type)]
if (a.many != b.many or (!a.mutable and b.mutable)) return false
if (b.mutable) return same_type(state, a.element, b.element)
return assignable_type(state, a.element, b.element)
}
@hide:file
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
actual :: state.types[usize(value)]
if (actual.kind == .invalid or expected.kind == .invalid) return
if (!same_type(state, actual, expected)) {
if (!assignable_type(state, actual, expected)) {
try fail(state, value, .type_mismatch, ast.node_span(state.tree, value))
}
}
@@ -1465,11 +1568,18 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
assignment :: ast.assignment_stmt(tree, id)
target_type :: state.types[usize(assignment.target)]
if (target_type.kind == .invalid) return
if (mutable_target(state, assignment.target)) {
if (mutable_target(state, assignment.target, false)) {
try compatible(state, assignment.value, target_type)
} else {
code :: if (ast.expr_kind(tree, assignment.target) == .index)
diagnostics.Code.invalid_index_assignment else diagnostics.Code.invalid_assignment_target
kind :: ast.expr_kind(tree, assignment.target)
code := if (kind == .index) diagnostics.Code.invalid_index_assignment else diagnostics.Code.invalid_assignment_target
if (kind == .unary and ast.unary_expr(tree, assignment.target).operator == .dereference) {
code = .invalid_pointer_assignment
} else if (kind == .index and state.types[usize(ast.index_expr(tree, assignment.target).base)].kind == .pointer) {
code = .invalid_pointer_assignment
} else if (kind == .selection and state.types[usize(ast.selection_expr(tree, assignment.target).base)].kind == .pointer) {
code = .invalid_pointer_assignment
}
try fail(state, id, code, ast.node_span(tree, assignment.target))
}
}
@@ -1481,7 +1591,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
actual :: state.types[usize(value)]
if (actual.kind == .invalid) {
state.types[usize(id)] = actual
} else if (!same_type(state, actual, expected)) {
} else if (!assignable_type(state, actual, expected)) {
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
state.types[usize(id)] = state.types[usize(value)]
}
@@ -1497,6 +1607,19 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
try require_bool(state, expression.operand)
return
}
if (expression.operator == .address_of) {
if (!addressable_target(state, expression.operand)) {
try fail(state, id, .invalid_address_operand, expression.span)
}
return
}
if (expression.operator == .dereference) {
if (state.types[usize(expression.operand)].kind != .invalid
and state.types[usize(expression.operand)].kind != .pointer) {
try fail(state, id, .invalid_dereference, expression.span)
}
return
}
operand :: state.types[usize(expression.operand)]
if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) {
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
@@ -1513,8 +1636,8 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
right :: state.types[usize(expression.right)]
if (left.kind == .invalid or right.kind == .invalid) return
if ((expression.operator == .equal or expression.operator == .not_equal)
and (left.kind == .bool or left.kind == .enumeration)
and same_type(state, left, right)) return
and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer)
and (assignable_type(state, left, right) or assignable_type(state, right, left))) return
if (!numeric_kind(left.kind) or !numeric_kind(right.kind) or !same_type(state, left, right)) {
try fail(state, id, .invalid_operand, expression.span)
} else if (expression.operator == .divide and left.kind != .float) {
@@ -1572,8 +1695,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
if (index.kind != .invalid and (index.kind != .usize or index.nominal != ast.NodeId(maxval!(u32)))) {
try fail(state, view.index, .invalid_index_type, ast.node_span(tree, view.index))
}
if (base.kind != .invalid
and (base.kind != .array or base.nominal != ast.NodeId(maxval!(u32)))) {
if (base.kind != .invalid and !indexable_type(state, base)) {
try fail(state, id, .not_indexable, ast.node_span(tree, view.base))
}
}
@@ -1758,6 +1880,35 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
}
}
@hide:file
intern_pointer proc(state @mut State, descriptor PointerType) Type ! mem.AllocError {
for state.pointers.items |existing, i| {
if (existing.mutable == descriptor.mutable and existing.many == descriptor.many
and same_type(state, existing.element, descriptor.element)) {
return Type{ kind = .pointer, pointer_type = PointerId(i) }
}
}
try arraylist.append(&state.pointers, descriptor)
return Type{ kind = .pointer, pointer_type = PointerId(state.pointers.items.len - 1) }
}
@hide:file
check_pointer_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
view :: ast.pointer_type(state.tree, id)
state.pointer_depth += 1
defer state.pointer_depth -= 1
element :: try check_annotation(state, view.element, true)
if (element.kind == .invalid) return element
binding :: sema.binding(state.resolution, view.element)
if (element.kind == .none or element.kind == .void or element.kind == .range
or element.kind == .type_value or element.kind == .function
or has_inferred_count(state, element)
or (binding.kind == .builtin and (binding.data.builtin == .int or binding.data.builtin == .float))) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_pointer_type, ast.node_span(state.tree, id)))
}
return try intern_pointer(state, PointerType{ element = element, mutable = view.mutable, many = view.many })
}
@hide:file
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
for state.arrays.items |existing, i| {
@@ -1775,6 +1926,11 @@ same_type proc(state @State, left, right Type) bool {
if (left.kind != right.kind) return false
if (left.nominal != right.nominal) return false
if (left.kind == .enumeration) return left.enum_type == right.enum_type
if (left.kind == .pointer) {
a :: state.pointers.items[usize(left.pointer_type)]
b :: state.pointers.items[usize(right.pointer_type)]
return a.mutable == b.mutable and a.many == b.many and same_type(state, a.element, b.element)
}
if (left.kind == .type_value) {
a := left
b := right
@@ -1881,6 +2037,33 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
if (state.cells[index].type.kind == .invalid) return state.cells[index].type
state.cells[index].visiting = true
defer state.cells[index].visiting = false
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .unary) {
view :: ast.unary_expr(state.tree, id)
if (view.operator == .address_of or view.operator == .dereference) {
operand :: try infer_shape(state, view.operand, defaults)
if (operand.kind == .none) return Type{}
if (operand.kind == .invalid) {
seed(state, id, operand)
return operand
}
if (view.operator == .dereference) {
if (operand.kind != .pointer) {
try fail(state, id, .invalid_dereference, view.span)
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
element :: state.pointers.items[usize(operand.pointer_type)].element
seed(state, id, element)
return element
}
mutable :: mutable_target(state, view.operand, true)
expected :: state.cells[index].expected
many :: expected.kind == .pointer and state.pointers.items[usize(expected.pointer_type)].many
type :: try intern_pointer(state, PointerType{ element = operand, mutable = mutable, many = many })
seed(state, id, type)
return type
}
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .match_expr) {
return try infer_match(state, id, defaults)
}
@@ -1899,7 +2082,28 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
enum_shape :: try check_annotation(state, view.base, false)
if (enum_shape.kind == .enumeration) return try select_enum_member(state, id, view.member, enum_shape)
}
base :: try infer_shape(state, view.base, defaults)
base := try infer_shape(state, view.base, defaults)
if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)]
if (!pointer.many and pointer.element.kind == .array
and ast.expr_kind(state.tree, view.member) == .identifier) {
member :: ast.source_text(state.tree, ast.node_span(state.tree, view.member))
if (mem.eql(member, "len")) {
type :: Type{ kind = .usize }
seed(state, id, type)
return type
}
if (mem.eql(member, "ptr")) {
array :: state.arrays.items[usize(pointer.element.array_type)]
type :: try intern_pointer(state, PointerType{
element = array.element, mutable = pointer.mutable and array.mutable, many = true,
})
seed(state, id, type)
return type
}
}
if (!pointer.many or pointer.element.kind == .record) base = pointer.element
}
if (base.kind == .record and base.nominal == ast.NodeId(maxval!(u32))) {
fields :: state.records.items[usize(base.record_type)].fields
slot :: selected_field(state, fields, view.member)
@@ -1987,7 +2191,15 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
view :: ast.index_expr(state.tree, id)
base :: try infer_shape(state, view.base, defaults)
base := try infer_shape(state, view.base, defaults)
if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)]
if (pointer.many) {
seed(state, id, pointer.element)
return pointer.element
}
base = pointer.element
}
if (base.kind == .array and base.nominal == ast.NodeId(maxval!(u32))) {
element :: state.arrays.items[usize(base.array_type)].element
seed(state, id, element)
@@ -2057,7 +2269,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
}
if (assignment_values and state.cells[i].role == .assignment_stmt) {
view :: ast.assignment_stmt(tree, id)
if (mutable_target(state, view.target)) try context_demand(state, view.value, state.types[usize(view.target)], .type_mismatch, early)
if (mutable_target(state, view.target, false)) try context_demand(state, view.value, state.types[usize(view.target)], .type_mismatch, early)
}
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
if (state.backings[i].kind != .none) {
@@ -2093,7 +2305,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
@hide:file
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
if (early and expected.kind != .array and expected.kind != .record
and expected.kind != .enumeration and !integer_kind(expected.kind)) return
and expected.kind != .pointer and expected.kind != .enumeration and !integer_kind(expected.kind)) return
try demand(state, id, expected, code)
}
@@ -2281,18 +2493,35 @@ intern_record proc(state @mut State, declaration ast.NodeId, fields []mut Record
@hide:file
check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .record or cached.kind == .type_value) {
if (cached.kind == .invalid) return cached
if (!state.cells[usize(id)].record_checking and (cached.kind == .record or cached.kind == .type_value)) {
cached.kind = .record
return cached
}
if (cached.kind == .invalid) return cached
if (state.cells[usize(id)].record_checking) {
if (cached.kind == .record and state.pointer_depth > state.cells[usize(id)].record_pointer_depth) {
cached.kind = .record
return cached
}
type :: invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
state.cells[usize(id)].type = type
return type
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
state.cells[usize(id)].record_pointer_depth = state.pointer_depth
record_id := no_record
result_type := Type{}
if (nominal) {
record_id = RecordId(state.records.items.len)
empty :: try mem.alloc(RecordField, state.records.allocator, 0)
arraylist.append(&state.records, RecordType{ declaration = id, fields = empty }) catch |err| {
mem.free(state.records.allocator, empty)
return err
}
result_type = Type{ kind = .record, record_type = record_id }
state.cells[usize(id)].type = result_type
}
count :: ast.record_field_count(state.tree, id)
fields :: try mem.alloc(RecordField, state.records.allocator, count)
owned := true
@@ -2319,10 +2548,12 @@ check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.All
state.cells[usize(id)].type = failure
return failure
}
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
type :: try intern_record(state, declaration, fields)
if (nominal) {
mem.free(state.records.allocator, state.records.items[usize(record_id)].fields)
state.records.items[usize(record_id)].fields = fields
} else result_type = try intern_record(state, ast.NodeId(maxval!(u32)), fields)
owned = false
state.cells[usize(id)].type = type
state.cells[usize(id)].type = result_type
state.cells[usize(id)].record_checking = false
saved_assignment :: state.collecting_assignment
state.collecting_assignment = false
@@ -2332,7 +2563,7 @@ check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.All
try collect_expression(state, value)
}
}
return type
return result_type
}
@hide:file
@@ -2457,6 +2688,7 @@ type_syntax proc(kind ast.ExprKind) bool {
return (
kind == .record_type or kind == .union_type or kind == .enum_type
or kind == .distinct_type or kind == .alias_expr
or kind == .pointer_type
)
}
@@ -2483,16 +2715,22 @@ immediate_backing proc(state @State, type Type) Type {
@hide:file
check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
kind :: ast.expr_kind(state.tree, id)
if (kind == .record_type) return try check_record(state, id, nominal)
if (kind == .pointer_type) return try check_pointer_type(state, id)
if (kind == .union_type) {
type :: try check_union(state, id, nominal)
state.cells[usize(id)].type = type
return type
}
cached := state.cells[usize(id)].type
if (cached.kind == .type_value) {
cached.kind = cached.value_kind
return cached
}
if (cached.kind != .none) return cached
kind :: ast.expr_kind(state.tree, id)
if (kind == .record_type) return try check_record(state, id, nominal)
if (kind == .union_type or kind == .enum_type) {
type :: if (kind == .union_type) try check_union(state, id, nominal) else try check_enum(state, id)
if (kind == .enum_type) {
type :: try check_enum(state, id)
state.cells[usize(id)].type = type
return type
}
@@ -2630,11 +2868,33 @@ check_enum proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagno
@hide:file
check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
cached := state.cells[usize(id)].type
if (cached.kind == .invalid) return cached
if (!state.cells[usize(id)].record_checking and (cached.kind == .record or cached.kind == .type_value)) {
cached.kind = .record
return cached
}
if (state.cells[usize(id)].record_checking) {
if (state.pointer_depth > state.cells[usize(id)].record_pointer_depth) {
cached.kind = .record
return cached
}
return invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
}
state.cells[usize(id)].record_checking = true
defer state.cells[usize(id)].record_checking = false
state.cells[usize(id)].record_pointer_depth = state.pointer_depth
record_id :: RecordId(state.records.items.len)
empty :: try mem.alloc(RecordField, state.records.allocator, 0)
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
arraylist.append(&state.records, RecordType{
declaration = declaration, fields = empty, is_union = true,
}) catch |err| {
mem.free(state.records.allocator, empty)
return err
}
result_type :: Type{ kind = .record, record_type = record_id }
state.cells[usize(id)].type = result_type
count :: ast.record_field_count(state.tree, id)
fields :: try mem.alloc(RecordField, state.records.allocator, count)
owned := true
@@ -2688,16 +2948,13 @@ check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.Allo
members_owned = false
tag = Type{ kind = .enumeration, enum_type = EnumId(state.enums.items.len - 1) }
}
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
try arraylist.append(&state.records, RecordType{
declaration = declaration,
fields = fields, is_union = true, tag_type = tag,
declared_tag_type = declared_tag,
})
mem.free(state.records.allocator, state.records.items[usize(record_id)].fields)
state.records.items[usize(record_id)].fields = fields
state.records.items[usize(record_id)].tag_type = tag
state.records.items[usize(record_id)].declared_tag_type = declared_tag
owned = false
type :: Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
state.cells[usize(id)].type = type
return type
state.cells[usize(id)].type = result_type
return result_type
}
@hide:file
+1
View File
@@ -571,6 +571,7 @@ resolve_expr proc(
try resolve_expr(state, scope, ast.call_arg(tree, id, i))
}
}
.pointer_type: try resolve_expr(state, scope, ast.pointer_type(tree, id).element)
.array_type: {
type :: ast.array_type(tree, id)
if (type.count) |count| try resolve_expr(state, scope, count)