feat: support source pointers and references
This commit is contained in:
+44
-4
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
|
||||
@@ -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
@@ -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)^
|
||||
}
|
||||
|
||||
@@ -295,6 +295,8 @@ scan proc(
|
||||
',': .comma
|
||||
'|': .pipe
|
||||
'@': .at
|
||||
'&': .ampersand
|
||||
'^': .caret
|
||||
'(': .open_paren
|
||||
')': .close_paren
|
||||
'[': .open_bracket
|
||||
|
||||
@@ -48,7 +48,8 @@ TokenKind :: enum {
|
||||
star, slash
|
||||
|
||||
range, range_inclusive
|
||||
pipe, at
|
||||
at, ampersand, caret
|
||||
pipe
|
||||
|
||||
# logical
|
||||
bang
|
||||
|
||||
@@ -168,6 +168,27 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
|
||||
try io.print(" %r{} = insertvalue %address %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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user