sentinel pointers and c strings

This commit is contained in:
2026-06-14 19:21:49 +02:00
parent 2d3d0bd266
commit 3b7c3fcbd0
16 changed files with 512 additions and 55 deletions
+76 -26
View File
@@ -221,6 +221,19 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_runtime_value(value, &checker.module.types)
}
string_literal_type :: proc(checker: ^Checker, string_id: u64) -> types.Type {
length: u64
if string_id < u64(len(checker.ast_module.strings)) {
length = u64(len(checker.ast_module.strings[string_id]))
}
array := types.array(&checker.module.types, types.U8, length, false, true, 0)
return types.pointer(&checker.module.types, array, false, false)
}
container_pointer_type :: proc(store: ^types.Store, item: types.Node) -> types.Type {
return types.pointer(store, item.child, item.mutable, true, item.has_sentinel, item.sentinel)
}
resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: ast.Expr_Id) -> types.Type {
item, ok := types.node(&checker.module.types, value)
if !ok || item.kind != .Array || !item.inferred_count ||
@@ -665,7 +678,7 @@ validate_type_nodes :: proc(checker: ^Checker) {
source.addf(
checker.diagnostics,
source.Span{},
"sentinel value does not fit array or slice element type %s",
"sentinel value does not fit array, slice, or pointer element type %s",
types.name(item.child),
)
}
@@ -809,7 +822,7 @@ infer_compound_expr :: proc(
store := &checker.module.types
#partial switch expr.kind {
case .String:
return types.slice(store, types.U8, false, true, 0)
return string_literal_type(checker, expr.integer)
case .Array:
element := types.INVALID
for arg in expr.args {
@@ -835,11 +848,12 @@ infer_compound_expr :: proc(
case .Index:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
return types.child_type(value, store)
item, ok := types.container(value, store)
return item.child if ok else types.INVALID
case .Slice:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
item, ok := types.node(store, value)
if !ok || (item.kind != .Array && item.kind != .Slice) {
item, ok := types.container(value, store)
if !ok || item.kind == .Pointer {
return types.INVALID
}
for bound in expr.args {
@@ -851,14 +865,15 @@ infer_compound_expr :: proc(
return types.slice(store, item.child, item.mutable, preserve, item.sentinel)
case .Field:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
item, has_item := types.node(store, value)
field_name := symbol_text(checker, expr.name)
item, has_item := types.container(value, store)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
return types.USIZE
}
if field_name == "ptr" {
return types.pointer(store, item.child, item.mutable, true)
if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(value, store)) {
return container_pointer_type(store, item)
}
}
if types.is_pointer(value, store) {
@@ -950,13 +965,14 @@ infer_expr :: proc(
last = find_infer_local(locals, expr.name)
} else {
base_type := find_infer_local(locals, expr.qualifier)
item, has_item := types.node(&checker.module.types, base_type)
item, has_item := types.container(base_type, &checker.module.types)
field_name := symbol_text(checker, expr.name)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
last = types.USIZE
} else if field_name == "ptr" {
last = types.pointer(&checker.module.types, item.child, item.mutable, true)
} else if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) {
last = container_pointer_type(&checker.module.types, item)
}
}
if types.is_pointer(base_type, &checker.module.types) {
@@ -1296,11 +1312,35 @@ coerce_expr :: proc(
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.can_weaken_slice(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{
kind=.Weaken_Slice,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.can_decay_array_pointer(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{
kind=.Decay_Array_Pointer,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.is_optional(expected, &checker.module.types) {
child := types.child_type(expected, &checker.module.types)
if types.equal(actual, child) ||
types.can_widen(actual, child) ||
types.can_weaken_pointer(actual, child, &checker.module.types) {
types.can_weaken_pointer(actual, child, &checker.module.types) ||
types.can_weaken_slice(actual, child, &checker.module.types) ||
types.can_decay_array_pointer(actual, child, &checker.module.types) {
value := coerce_expr(checker, expr_id, child, span)
return add_hir_expr(checker, hir.Expr{
kind=.Optional_Some,
@@ -1475,7 +1515,8 @@ hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: [
return types.is_mutable(pointer_type, &checker.module.types)
case .Index:
container_type := checker.module.exprs[expr.left].type
return types.is_mutable(container_type, &checker.module.types)
item, ok := types.container(container_type, &checker.module.types)
return ok && item.mutable
case .Field:
base_type := checker.module.exprs[expr.left].type
if types.is_pointer(base_type, &checker.module.types) {
@@ -1539,7 +1580,7 @@ build_compound_expr :: proc(
store := &checker.module.types
#partial switch expr.kind {
case .String:
string_type := types.slice(store, types.U8, false, true, 0)
string_type := string_literal_type(checker, expr.integer)
return add_hir_expr(checker, hir.Expr{
kind=.String, span=expr.span, type=string_type, integer=i64(expr.integer),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
@@ -1639,9 +1680,9 @@ build_compound_expr :: proc(
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
container_type := checker.module.exprs[container].type
item, ok := types.node(store, container_type)
if !ok || (item.kind != .Array && item.kind != .Slice && !(item.kind == .Pointer && item.many)) {
id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, or many-item pointer")
item, ok := types.container(container_type, store)
if !ok {
id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, pointer-to-array, or many-item pointer")
return invalid_hir_expr(checker, expr.span, id)
}
return add_hir_expr(checker, hir.Expr{
@@ -1651,9 +1692,9 @@ build_compound_expr :: proc(
case .Slice:
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
container_type := checker.module.exprs[container].type
item, ok := types.node(store, container_type)
if !ok || (item.kind != .Array && item.kind != .Slice) {
id := source.add(checker.diagnostics, expr.span, "slicing requires an array or slice")
item, ok := types.container(container_type, store)
if !ok || item.kind == .Pointer {
id := source.add(checker.diagnostics, expr.span, "slicing requires an array, slice, or pointer-to-array")
return invalid_hir_expr(checker, expr.span, id)
}
bounds := make([]hir.Expr_Id, 2, checker.allocator)
@@ -1673,8 +1714,8 @@ build_compound_expr :: proc(
case .Field:
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
base_type := checker.module.exprs[base].type
item, has_item := types.node(store, base_type)
field_name := symbol_text(checker, expr.name)
item, has_item := types.container(base_type, store)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
return add_hir_expr(checker, hir.Expr{
@@ -1682,13 +1723,18 @@ build_compound_expr :: proc(
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if field_name == "ptr" {
if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, store)) {
return add_hir_expr(checker, hir.Expr{
kind=.Slice_Ptr, span=expr.span,
type=types.pointer(store, item.child, item.mutable, true), left=base,
type=container_pointer_type(store, item), left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if field_name == "ptr" && item.kind == .Array {
id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first")
return invalid_hir_expr(checker, expr.span, id)
}
}
if types.is_pointer(base_type, store) {
base_type = types.child_type(base_type, store)
@@ -1848,7 +1894,7 @@ build_expr :: proc(
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
base_type := local.type
item, has_item := types.node(&checker.module.types, base_type)
item, has_item := types.container(base_type, &checker.module.types)
field_name := symbol_text(checker, expr.name)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
@@ -1856,12 +1902,16 @@ build_expr :: proc(
kind=.Length, span=expr.span, type=types.USIZE, left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
} else if field_name == "ptr" {
} else if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) {
last = add_hir_expr(checker, hir.Expr{
kind=.Slice_Ptr, span=expr.span,
type=types.pointer(&checker.module.types, item.child, item.mutable, true), left=base,
type=container_pointer_type(&checker.module.types, item), left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
} else if field_name == "ptr" && item.kind == .Array {
id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first")
last = invalid_hir_expr(checker, expr.span, id)
}
}
if types.is_pointer(base_type, &checker.module.types) {
+2
View File
@@ -95,6 +95,8 @@ Expr_Kind :: enum u8 {
Widen,
C_Vararg_Promote,
Weaken_Pointer,
Weaken_Slice,
Decay_Array_Pointer,
Negate,
Add,
Pointer_Add,
+2
View File
@@ -89,6 +89,8 @@ Opcode :: enum u8 {
Widen,
C_Vararg_Promote,
Weaken_Pointer,
Weaken_Slice,
Decay_Array_Pointer,
Neg_Checked,
Add_Checked,
Pointer_Add,
+54 -14
View File
@@ -112,7 +112,8 @@ valid_value :: proc(
switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Trap, .Return, .Return_Void:
@@ -306,21 +307,17 @@ emit_instruction_stream :: proc(
case .Param, .Const:
case .String:
string_id := int(instruction.integer)
_, array, pointer_ok := types.array_pointer(instruction.type, &emitter.module.types)
if string_id < 0 || string_id >= len(emitter.module.strings) ||
!types.is_slice(instruction.type, &emitter.module.types) {
!pointer_ok || array.child != types.U8 || !array.has_sentinel ||
array.sentinel != 0 || array.count != u64(len(emitter.module.strings[string_id])) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid string literal")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(
&emitter.builder,
" %%string_ptr%d = insertvalue %s poison, ptr @bro.str.%d, 0\n",
instruction_index, type_name, string_id,
)
fmt.sbprintf(
&emitter.builder,
" %%v%d = insertvalue %s %%string_ptr%d, i64 %d, 1\n",
instruction_index, type_name, instruction_index, len(emitter.module.strings[string_id]),
" %%v%d = getelementptr %s, ptr @bro.str.%d, i64 0\n",
instruction_index, llvm_type(types.child_type(instruction.type, &emitter.module.types), &emitter.module.types), string_id,
)
case .Aggregate:
item, ok := types.node(&emitter.module.types, instruction.type)
@@ -465,7 +462,7 @@ emit_instruction_stream :: proc(
continue
}
container := instructions[instruction.a]
container_node, container_ok := types.node(&emitter.module.types, container.type)
container_node, container_ok := types.container(container.type, &emitter.module.types)
if !container_ok {
emit_recovery_value(emitter, instruction_index, instruction, "invalid index container")
continue
@@ -504,7 +501,13 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " unreachable\nindex_continue%d:\n", instruction_index)
}
if container_node.kind == .Array {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(container.type, &emitter.module.types), pointer_name)
array_type := container.type
_, array, array_pointer_ok := types.array_pointer(container.type, &emitter.module.types)
if array_pointer_ok {
array_type = types.child_type(container.type, &emitter.module.types)
container_node = array
}
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(array_type, &emitter.module.types), pointer_name)
} else {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 ", instruction_index, llvm_type(instruction.type, &emitter.module.types), pointer_name)
}
@@ -558,7 +561,7 @@ emit_instruction_stream :: proc(
continue
}
container := instructions[instruction.a]
item, ok := types.node(&emitter.module.types, container.type)
item, ok := types.container(container.type, &emitter.module.types)
if !ok || (item.kind != .Array && item.kind != .Slice) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
continue
@@ -566,7 +569,11 @@ emit_instruction_stream :: proc(
pointer_name := fmt.tprintf("%%v%d", instruction.a)
length_name := fmt.tprintf("%d", item.count)
if item.kind == .Array {
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
array_type := container.type
if types.is_pointer(container.type, &emitter.module.types) {
array_type = types.child_type(container.type, &emitter.module.types)
}
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a)
pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index)
} else if item.kind == .Slice {
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
@@ -621,12 +628,21 @@ emit_instruction_stream :: proc(
continue
}
container_type := instructions[instruction.a].type
_, _, array_pointer_ok := types.array_pointer(container_type, &emitter.module.types)
if types.is_array(container_type, &emitter.module.types) {
fmt.sbprintf(
&emitter.builder,
" %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n",
instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a,
)
} else if array_pointer_ok {
fmt.sbprintf(
&emitter.builder,
" %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n",
instruction_index,
llvm_type(types.child_type(container_type, &emitter.module.types), &emitter.module.types),
instruction.a,
)
} else if types.is_slice(container_type, &emitter.module.types) {
fmt.sbprintf(
&emitter.builder,
@@ -741,6 +757,30 @@ emit_instruction_stream :: proc(
continue
}
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a)
case .Weaken_Slice:
if !valid_instruction(instructions, instruction.a) ||
!types.can_weaken_slice(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice weakening operand")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s %%v%d, %s zeroinitializer\n", instruction_index, type_name, instruction.a, type_name)
case .Decay_Array_Pointer:
if !valid_instruction(instructions, instruction.a) ||
!types.can_decay_array_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid array pointer decay operand")
continue
}
array_type := types.child_type(instructions[instruction.a].type, &emitter.module.types)
array, _ := types.node(&emitter.module.types, array_type)
if types.is_pointer(instruction.type, &emitter.module.types) {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a)
} else {
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%decay_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a)
fmt.sbprintf(&emitter.builder, " %%decay_slice%d = insertvalue %s poison, ptr %%decay_ptr%d, 0\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%decay_slice%d, i64 %d, 1\n", instruction_index, type_name, instruction_index, array.count)
}
case .Neg_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
+18 -3
View File
@@ -227,6 +227,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
_, array, array_ok := types.array_pointer(container_type, &state.hir_module.types)
if array_ok {
return append_instruction(state, ir.Instruction{
op=.Const, span=expr.span, type=types.USIZE, integer=i64(array.count),
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
value := lower_nested_expr(state, expr.left)
return append_instruction(state, ir.Instruction{
op=.Length, span=expr.span, type=types.USIZE,
@@ -324,7 +332,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
})
}
_ = pop(&stack)
case .Widen, .C_Vararg_Promote, .Weaken_Pointer:
case .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
@@ -357,9 +365,16 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
continue
}
if frame.stage == 1 {
op := ir.Opcode.Widen
#partial switch expr.kind {
case .Weaken_Pointer: op = .Weaken_Pointer
case .Weaken_Slice: op = .Weaken_Slice
case .Decay_Array_Pointer: op = .Decay_Array_Pointer
case .C_Vararg_Promote: op = .C_Vararg_Promote
case: op = .Widen
}
last = append_instruction(state, ir.Instruction{
op=.Weaken_Pointer if expr.kind == .Weaken_Pointer else
(.C_Vararg_Promote if expr.kind == .C_Vararg_Promote else .Widen),
op=op,
span=expr.span, type=expr.type, target=ir.INVALID_REF,
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
+11
View File
@@ -166,6 +166,17 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
node := types.Node{}
if _, ok := allow(parser, .Right_Bracket); ok {
node.kind = .Slice
} else if _, ok := allow(parser, .Star); ok {
node.kind = .Pointer
node.many = true
node.has_sentinel = true
if _, ok = allow(parser, .Semicolon); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ';' after '*' in sentinel pointer type")
}
node.sentinel, _ = parse_type_constant(parser)
if _, ok = allow(parser, .Right_Bracket); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ']' after sentinel pointer type")
}
} else if _, ok := allow(parser, .Semicolon); ok {
node.kind = .Slice
node.has_sentinel = true
+93 -5
View File
@@ -488,13 +488,56 @@ is_many_pointer :: proc(value: Type, store: ^Store) -> bool {
return ok && item.kind == .Pointer && item.many
}
array_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, array_item: Node, ok: bool) {
pointer_ok: bool
pointer_item, pointer_ok = node(store, value)
if !pointer_ok || pointer_item.kind != .Pointer || pointer_item.many {
return {}, {}, false
}
array_ok: bool
array_item, array_ok = node(store, pointer_item.child)
if !array_ok || array_item.kind != .Array {
return {}, {}, false
}
return pointer_item, array_item, true
}
container :: proc(value: Type, store: ^Store) -> (Node, bool) {
item, ok := node(store, value)
if !ok {
return {}, false
}
if item.kind == .Array || item.kind == .Slice || (item.kind == .Pointer && item.many) {
return item, true
}
pointer_node, array_node, array_ok := array_pointer(value, store)
if !array_ok {
return {}, false
}
array_node.mutable = pointer_node.mutable && array_node.mutable
return array_node, true
}
is_c_struct :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value)
return ok && item.kind == .Struct && item.c_layout
}
pointer :: proc(store: ^Store, child: Type, mutable, many: bool) -> Type {
return intern(store, Node{kind=.Pointer, child=child, mutable=mutable, many=many})
pointer :: proc(
store: ^Store,
child: Type,
mutable, many: bool,
has_sentinel := false,
sentinel: u64 = 0,
) -> Type {
return intern(store, Node{
kind=.Pointer,
child=child,
mutable=mutable,
many=many,
has_sentinel=has_sentinel,
sentinel=sentinel,
})
}
slice :: proc(store: ^Store, child: Type, mutable: bool, has_sentinel := false, sentinel: u64 = 0) -> Type {
@@ -540,12 +583,57 @@ with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type {
}
can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool {
from_node, from_ok := node(store, from)
to_node, to_ok := node(store, to)
if !from_ok || !to_ok || from_node.kind != .Pointer || to_node.kind != .Pointer ||
from_node.many != to_node.many || (to_node.mutable && !from_node.mutable) {
return false
}
if to_node.has_sentinel &&
(!from_node.has_sentinel || from_node.sentinel != to_node.sentinel) {
return false
}
same_child := from_node.child == to_node.child
c_string := from_node.many && from_node.child == U8 && to_node.child == C_CHAR &&
from_node.has_sentinel && from_node.sentinel == 0 && !to_node.mutable
return same_child || c_string
}
can_weaken_slice :: proc(from, to: Type, store: ^Store) -> bool {
from_node, from_ok := node(store, from)
to_node, to_ok := node(store, to)
return from_ok && to_ok &&
from_node.kind == .Pointer && to_node.kind == .Pointer &&
from_node.child == to_node.child && from_node.many == to_node.many &&
from_node.mutable && !to_node.mutable
from_node.kind == .Slice && to_node.kind == .Slice &&
from_node.child == to_node.child &&
(!to_node.mutable || from_node.mutable) &&
(!to_node.has_sentinel ||
(from_node.has_sentinel && from_node.sentinel == to_node.sentinel))
}
can_decay_array_pointer :: proc(from, to: Type, store: ^Store) -> bool {
from_pointer, array, from_ok := array_pointer(from, store)
to_node, to_ok := node(store, to)
if !from_ok || !to_ok {
return false
}
mutable := from_pointer.mutable && array.mutable
if to_node.mutable && !mutable {
return false
}
if to_node.has_sentinel &&
(!array.has_sentinel || array.sentinel != to_node.sentinel) {
return false
}
if to_node.kind == .Slice {
return array.child == to_node.child
}
if to_node.kind != .Pointer || !to_node.many {
return false
}
same_child := array.child == to_node.child
c_string := array.child == U8 && to_node.child == C_CHAR &&
array.has_sentinel && array.sentinel == 0 && !to_node.mutable
return same_child || c_string
}
is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {