1987 lines
55 KiB
Odin
1987 lines
55 KiB
Odin
package types
|
|
|
|
import "../target"
|
|
import "core:fmt"
|
|
import "core:mem"
|
|
|
|
// Type is a compact ID. Builtin scalar types occupy the stable low range;
|
|
// recursive and nominal types are interned in Store starting at DYNAMIC_START.
|
|
Type :: distinct u32
|
|
|
|
INVALID :: Type(0)
|
|
VOID :: Type(1)
|
|
INT :: Type(2)
|
|
|
|
I8 :: Type(3)
|
|
I16 :: Type(4)
|
|
I32 :: Type(5)
|
|
I64 :: Type(6)
|
|
U8 :: Type(7)
|
|
U16 :: Type(8)
|
|
U32 :: Type(9)
|
|
U64 :: Type(10)
|
|
ISIZE :: Type(11)
|
|
USIZE :: Type(12)
|
|
F32 :: Type(13)
|
|
F64 :: Type(14)
|
|
|
|
C_CHAR :: Type(15)
|
|
C_SCHAR :: Type(16)
|
|
C_UCHAR :: Type(17)
|
|
C_SHORT :: Type(18)
|
|
C_USHORT :: Type(19)
|
|
C_INT :: Type(20)
|
|
C_UINT :: Type(21)
|
|
C_LONG :: Type(22)
|
|
C_ULONG :: Type(23)
|
|
C_LONGLONG :: Type(24)
|
|
C_ULONGLONG :: Type(25)
|
|
C_FLOAT :: Type(26)
|
|
C_DOUBLE :: Type(27)
|
|
C_LONGDOUBLE :: Type(28)
|
|
|
|
BOOL :: Type(29)
|
|
FLOAT :: Type(30)
|
|
RANGE :: Type(31)
|
|
ANYOPAQUE :: Type(32)
|
|
UINT :: Type(33)
|
|
NORETURN :: Type(34)
|
|
|
|
DYNAMIC_START :: Type(64)
|
|
|
|
Numeric_Category :: enum u8 {
|
|
None,
|
|
Signed_Integer,
|
|
Unsigned_Integer,
|
|
Float,
|
|
}
|
|
|
|
Kind :: enum u8 {
|
|
Invalid,
|
|
Void,
|
|
Noreturn,
|
|
Anyopaque,
|
|
Int_Constraint,
|
|
Uint_Constraint,
|
|
Float_Constraint,
|
|
Range_Constraint,
|
|
Scalar,
|
|
Array,
|
|
Pointer,
|
|
Slice,
|
|
Range,
|
|
Optional,
|
|
Function,
|
|
Named,
|
|
Alias,
|
|
Distinct,
|
|
Enum,
|
|
Struct,
|
|
Union,
|
|
Fallible,
|
|
Sum,
|
|
Type_Call,
|
|
}
|
|
|
|
Node :: struct {
|
|
kind: Kind,
|
|
child: Type,
|
|
extra: Type,
|
|
count: u64,
|
|
count_expr: u32,
|
|
sentinel: u64,
|
|
explicit_size: u64,
|
|
field_start: u32,
|
|
field_count: u32,
|
|
explicit_alignment: u32,
|
|
pkg: u32,
|
|
name: u32,
|
|
qualifier: u32,
|
|
file: u32,
|
|
mutable: bool,
|
|
many: bool,
|
|
has_sentinel: bool,
|
|
inferred_count: bool,
|
|
unresolved_count: bool,
|
|
c_abi: bool,
|
|
variadic: bool,
|
|
c_layout: bool,
|
|
tuple: bool,
|
|
opaque: bool,
|
|
declared: bool,
|
|
package_hidden: bool,
|
|
explicit_backing: bool,
|
|
}
|
|
|
|
Field :: struct {
|
|
name: u32,
|
|
type: Type,
|
|
offset: u64,
|
|
}
|
|
|
|
Enum_Member :: struct {
|
|
name: u32,
|
|
value: i128,
|
|
}
|
|
|
|
Variant :: struct {
|
|
name: u32,
|
|
payload: Type,
|
|
id: u16,
|
|
}
|
|
|
|
Sum_Variant :: struct {
|
|
name: u32,
|
|
payload: Type,
|
|
id: u16,
|
|
}
|
|
|
|
Compose_Error :: enum u8 {
|
|
None,
|
|
Unsupported,
|
|
Conflict,
|
|
}
|
|
|
|
Store :: struct {
|
|
nodes: [dynamic]Node,
|
|
fields: [dynamic]Field,
|
|
enum_members: [dynamic]Enum_Member,
|
|
variants: [dynamic]Variant,
|
|
selected: target.Target,
|
|
allocator: mem.Allocator,
|
|
}
|
|
|
|
init_store :: proc(allocator := context.allocator) -> Store {
|
|
store: Store
|
|
store.nodes.allocator = allocator
|
|
store.fields.allocator = allocator
|
|
store.enum_members.allocator = allocator
|
|
store.variants.allocator = allocator
|
|
store.selected = target.DEFAULT
|
|
store.allocator = allocator
|
|
return store
|
|
}
|
|
|
|
destroy_store :: proc(store: ^Store) {
|
|
delete(store.nodes)
|
|
delete(store.fields)
|
|
delete(store.enum_members)
|
|
delete(store.variants)
|
|
}
|
|
|
|
clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
|
|
store := init_store(allocator)
|
|
append(&store.nodes, ..source.nodes[:])
|
|
append(&store.fields, ..source.fields[:])
|
|
append(&store.enum_members, ..source.enum_members[:])
|
|
append(&store.variants, ..source.variants[:])
|
|
store.selected = source.selected
|
|
return store
|
|
}
|
|
|
|
intern :: proc(store: ^Store, candidate: Node) -> Type {
|
|
if candidate.kind != .Struct && candidate.kind != .Union &&
|
|
candidate.kind != .Named && candidate.kind != .Distinct && candidate.kind != .Enum {
|
|
for existing, index in store.nodes {
|
|
if existing == candidate {
|
|
return DYNAMIC_START+Type(index)
|
|
}
|
|
}
|
|
}
|
|
id := DYNAMIC_START+Type(len(store.nodes))
|
|
append(&store.nodes, candidate)
|
|
return id
|
|
}
|
|
|
|
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff, package_hidden := false) -> Type {
|
|
normalized_file := file if qualifier != 0 else u32(0)
|
|
for existing, index in store.nodes {
|
|
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
|
|
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
|
|
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
|
|
(qualifier == 0 || existing.file == normalized_file) {
|
|
return DYNAMIC_START+Type(index)
|
|
}
|
|
}
|
|
return intern(store, Node{
|
|
kind=.Named,
|
|
pkg=pkg,
|
|
name=name,
|
|
qualifier=qualifier,
|
|
file=normalized_file,
|
|
})
|
|
}
|
|
|
|
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
|
|
for existing, index in store.nodes {
|
|
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
|
|
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
|
|
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
|
|
(!existing.package_hidden || file != 0xffff_ffff) {
|
|
return DYNAMIC_START+Type(index)
|
|
}
|
|
}
|
|
return INVALID
|
|
}
|
|
|
|
define_alias :: proc(store: ^Store, id, child: Type, package_hidden := false) -> bool {
|
|
existing, ok := node(store, id)
|
|
if !ok || existing.kind != .Named || existing.declared {
|
|
return false
|
|
}
|
|
index := int(id-DYNAMIC_START)
|
|
store.nodes[index].kind = .Alias
|
|
store.nodes[index].child = child
|
|
store.nodes[index].package_hidden = package_hidden
|
|
store.nodes[index].declared = true
|
|
return true
|
|
}
|
|
|
|
define_distinct :: proc(store: ^Store, id, child: Type, package_hidden := false) -> bool {
|
|
existing, ok := node(store, id)
|
|
if !ok || existing.kind != .Named || existing.declared {
|
|
return false
|
|
}
|
|
index := int(id-DYNAMIC_START)
|
|
store.nodes[index].kind = .Distinct
|
|
store.nodes[index].child = child
|
|
store.nodes[index].package_hidden = package_hidden
|
|
store.nodes[index].declared = true
|
|
return true
|
|
}
|
|
|
|
define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, explicit_backing: bool, package_hidden := false) -> bool {
|
|
existing, ok := node(store, id)
|
|
if !ok || existing.kind != .Named || existing.declared {
|
|
return false
|
|
}
|
|
index := int(id-DYNAMIC_START)
|
|
store.nodes[index].kind = .Enum
|
|
store.nodes[index].child = backing
|
|
store.nodes[index].field_start = u32(len(store.enum_members))
|
|
store.nodes[index].field_count = u32(len(members))
|
|
store.nodes[index].explicit_backing = explicit_backing
|
|
store.nodes[index].package_hidden = package_hidden
|
|
store.nodes[index].declared = true
|
|
append(&store.enum_members, ..members)
|
|
return true
|
|
}
|
|
|
|
define_record :: proc(
|
|
store: ^Store,
|
|
id: Type,
|
|
fields: []Field,
|
|
c_layout, opaque: bool,
|
|
is_union := false,
|
|
explicit_size: u64 = 0,
|
|
explicit_alignment: u32 = 0,
|
|
tag: Type = INVALID,
|
|
declared_tag: Type = INVALID,
|
|
tuple := false,
|
|
package_hidden := false,
|
|
) -> bool {
|
|
existing, ok := node(store, id)
|
|
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
|
|
(existing.declared && !existing.opaque) {
|
|
return false
|
|
}
|
|
index := int(id-DYNAMIC_START)
|
|
store.nodes[index].kind = .Union if is_union else .Struct
|
|
store.nodes[index].c_layout = c_layout
|
|
store.nodes[index].tuple = tuple
|
|
store.nodes[index].opaque = opaque
|
|
store.nodes[index].package_hidden = package_hidden
|
|
store.nodes[index].declared = true
|
|
store.nodes[index].explicit_size = explicit_size
|
|
store.nodes[index].explicit_alignment = explicit_alignment
|
|
// A tagged union stashes its discriminant enum in `child` (untagged unions and
|
|
// structs leave it INVALID); the per-variant tag value is derived from the enum
|
|
// member whose name matches the variant, so no extra storage is needed.
|
|
store.nodes[index].child = tag
|
|
store.nodes[index].extra = declared_tag
|
|
store.nodes[index].field_start = u32(len(store.fields))
|
|
store.nodes[index].field_count = u32(len(fields))
|
|
append(&store.fields, ..fields)
|
|
return true
|
|
}
|
|
|
|
// enum_anonymous interns an unnamed enum (used as the synthesized discriminant of a
|
|
// `union(enum)` tagged union). Members carry the variant names with dense 0-based
|
|
// values, so the same name→member→value lookup used for `union(Enum)` resolves tags.
|
|
enum_anonymous :: proc(store: ^Store, members: []Enum_Member, backing: Type) -> Type {
|
|
start := u32(len(store.enum_members))
|
|
append(&store.enum_members, ..members)
|
|
return intern(store, Node{
|
|
kind=.Enum,
|
|
child=backing,
|
|
field_start=start,
|
|
field_count=u32(len(members)),
|
|
declared=true,
|
|
})
|
|
}
|
|
|
|
union_anonymous :: proc(store: ^Store, fields: []Field, tag: Type) -> Type {
|
|
start := u32(len(store.fields))
|
|
append(&store.fields, ..fields)
|
|
return intern(store, Node{
|
|
kind=.Union,
|
|
child=tag,
|
|
field_start=start,
|
|
field_count=u32(len(fields)),
|
|
declared=true,
|
|
})
|
|
}
|
|
|
|
anonymous_struct_fields_equal :: proc(store: ^Store, item: Node, fields: []Field) -> bool {
|
|
if item.field_count != u32(len(fields)) {
|
|
return false
|
|
}
|
|
start := int(item.field_start)
|
|
end := start+int(item.field_count)
|
|
if start < 0 || end > len(store.fields) {
|
|
return false
|
|
}
|
|
for field, index in fields {
|
|
existing := store.fields[start+index]
|
|
if existing.name != field.name || existing.type != field.type {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
struct_anonymous :: proc(store: ^Store, fields: []Field, tuple := false) -> Type {
|
|
for existing, index in store.nodes {
|
|
if existing.kind == .Struct && existing.name == 0 && existing.declared &&
|
|
!existing.c_layout && existing.tuple == tuple && !existing.opaque &&
|
|
anonymous_struct_fields_equal(store, existing, fields) {
|
|
return DYNAMIC_START+Type(index)
|
|
}
|
|
}
|
|
start := u32(len(store.fields))
|
|
append(&store.fields, ..fields)
|
|
return intern(store, Node{
|
|
kind=.Struct,
|
|
field_start=start,
|
|
field_count=u32(len(fields)),
|
|
tuple=tuple,
|
|
declared=true,
|
|
})
|
|
}
|
|
|
|
// Generated structs are nominal per comptime type-expression specialization.
|
|
// The checker owns canonicalization; this routine deliberately creates a fresh node.
|
|
struct_generated :: proc(store: ^Store, fields: []Field, tuple := false, c_layout := false) -> Type {
|
|
start := u32(len(store.fields))
|
|
append(&store.fields, ..fields)
|
|
id := DYNAMIC_START+Type(len(store.nodes))
|
|
append(&store.nodes, Node{
|
|
kind=.Struct,
|
|
field_start=start,
|
|
field_count=u32(len(fields)),
|
|
tuple=tuple,
|
|
c_layout=c_layout,
|
|
declared=true,
|
|
})
|
|
return id
|
|
}
|
|
|
|
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
|
|
for variant in store.variants {
|
|
if variant.name == name && variant.payload == payload {
|
|
return variant.id, true
|
|
}
|
|
}
|
|
next := len(store.variants)+1
|
|
if next > 0xffff {
|
|
return 0, false
|
|
}
|
|
id := u16(next)
|
|
append(&store.variants, Variant{name=name, payload=payload, id=id})
|
|
return id, true
|
|
}
|
|
|
|
fallible :: proc(store: ^Store, success, error: Type) -> Type {
|
|
return intern(store, Node{kind=.Fallible, child=success, extra=error, declared=true})
|
|
}
|
|
|
|
fallible_success :: proc(value: Type, store: ^Store) -> Type {
|
|
item, ok := node(store, value)
|
|
return item.child if ok && item.kind == .Fallible else INVALID
|
|
}
|
|
|
|
fallible_error :: proc(value: Type, store: ^Store) -> Type {
|
|
item, ok := node(store, value)
|
|
return item.extra if ok && item.kind == .Fallible else INVALID
|
|
}
|
|
|
|
sum_syntax :: proc(store: ^Store, left, right: Type) -> Type {
|
|
return intern(store, Node{kind=.Sum, child=left, extra=right})
|
|
}
|
|
|
|
append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Variant) -> bool {
|
|
item, ok := node(store, value)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.kind == .Enum {
|
|
if item.explicit_backing {
|
|
return false
|
|
}
|
|
for member in enum_members_for(store, value) {
|
|
if member.value <= 0 || member.value > 0xffff {
|
|
return false
|
|
}
|
|
append(out, Sum_Variant{name=member.name, payload=VOID, id=u16(member.value)})
|
|
}
|
|
return true
|
|
}
|
|
if item.kind != .Union || !is_enum(item.child, store) || item.c_layout {
|
|
return false
|
|
}
|
|
tag_members := enum_members_for(store, item.child)
|
|
for field in fields_for(store, value) {
|
|
found := false
|
|
for member in tag_members {
|
|
if member.name == field.name {
|
|
if member.value <= 0 || member.value > 0xffff {
|
|
return false
|
|
}
|
|
append(out, Sum_Variant{name=field.name, payload=field.type, id=u16(member.value)})
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
compose_sum :: proc(store: ^Store, left, right: Type) -> (Type, Compose_Error) {
|
|
variants: [dynamic]Sum_Variant
|
|
variants.allocator = store.allocator
|
|
defer delete(variants)
|
|
if !append_sum_variants(store, left, &variants) {
|
|
return INVALID, .Unsupported
|
|
}
|
|
right_variants: [dynamic]Sum_Variant
|
|
right_variants.allocator = store.allocator
|
|
defer delete(right_variants)
|
|
if !append_sum_variants(store, right, &right_variants) {
|
|
return INVALID, .Unsupported
|
|
}
|
|
for candidate in right_variants {
|
|
merged := false
|
|
for existing in variants {
|
|
if existing.id == candidate.id {
|
|
merged = true
|
|
break
|
|
}
|
|
if existing.name == candidate.name && existing.payload != candidate.payload {
|
|
return INVALID, .Conflict
|
|
}
|
|
}
|
|
if !merged {
|
|
append(&variants, candidate)
|
|
}
|
|
}
|
|
all_void := true
|
|
for variant in variants {
|
|
all_void = all_void && variant.payload == VOID
|
|
}
|
|
members := make([]Enum_Member, len(variants), store.allocator)
|
|
defer delete(members, store.allocator)
|
|
for variant, index in variants {
|
|
members[index] = Enum_Member{name=variant.name, value=i128(variant.id)}
|
|
}
|
|
if all_void {
|
|
return enum_anonymous(store, members, U16), .None
|
|
}
|
|
fields := make([]Field, len(variants), store.allocator)
|
|
defer delete(fields, store.allocator)
|
|
for variant, index in variants {
|
|
fields[index] = Field{name=variant.name, type=variant.payload}
|
|
}
|
|
tag := enum_anonymous(store, members, U16)
|
|
return union_anonymous(store, fields, tag), .None
|
|
}
|
|
|
|
sum_has_name :: proc(store: ^Store, value: Type, name: u32) -> bool {
|
|
variants: [dynamic]Sum_Variant
|
|
variants.allocator = store.allocator
|
|
defer delete(variants)
|
|
if !append_sum_variants(store, value, &variants) {
|
|
return false
|
|
}
|
|
for variant in variants {
|
|
if variant.name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
can_sum_widen :: proc(from, to: Type, store: ^Store) -> bool {
|
|
if from == to {
|
|
return true
|
|
}
|
|
from_variants: [dynamic]Sum_Variant
|
|
from_variants.allocator = store.allocator
|
|
defer delete(from_variants)
|
|
to_variants: [dynamic]Sum_Variant
|
|
to_variants.allocator = store.allocator
|
|
defer delete(to_variants)
|
|
if !append_sum_variants(store, from, &from_variants) ||
|
|
!append_sum_variants(store, to, &to_variants) {
|
|
return false
|
|
}
|
|
for needed in from_variants {
|
|
found := false
|
|
for available in to_variants {
|
|
if available.id == needed.id && available.payload == needed.payload {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Reports whether every selected variant from `from` is represented identically in
|
|
// `to`. The checker uses this only after control flow has proven that the source value
|
|
// is one of `selected`; it is not a general implicit narrowing rule.
|
|
selected_sum_fits :: proc(from, to: Type, selected: []u32, store: ^Store) -> bool {
|
|
if len(selected) == 0 {
|
|
return false
|
|
}
|
|
from_variants: [dynamic]Sum_Variant
|
|
from_variants.allocator = store.allocator
|
|
defer delete(from_variants)
|
|
to_variants: [dynamic]Sum_Variant
|
|
to_variants.allocator = store.allocator
|
|
defer delete(to_variants)
|
|
if !append_sum_variants(store, from, &from_variants) ||
|
|
!append_sum_variants(store, to, &to_variants) {
|
|
return false
|
|
}
|
|
for name in selected {
|
|
source_variant: Sum_Variant
|
|
source_found := false
|
|
for variant in from_variants {
|
|
if variant.name == name {
|
|
source_variant = variant
|
|
source_found = true
|
|
break
|
|
}
|
|
}
|
|
if !source_found {
|
|
return false
|
|
}
|
|
matched := false
|
|
for variant in to_variants {
|
|
if variant.id == source_variant.id && variant.payload == source_variant.payload {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
if !matched {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Sum_Project is emitted only with a checker proof. This weaker structural predicate is
|
|
// retained by the backend as a defensive check that the two sums share a valid variant.
|
|
can_sum_project :: proc(from, to: Type, store: ^Store) -> bool {
|
|
from_variants: [dynamic]Sum_Variant
|
|
from_variants.allocator = store.allocator
|
|
defer delete(from_variants)
|
|
to_variants: [dynamic]Sum_Variant
|
|
to_variants.allocator = store.allocator
|
|
defer delete(to_variants)
|
|
if !append_sum_variants(store, from, &from_variants) ||
|
|
!append_sum_variants(store, to, &to_variants) {
|
|
return false
|
|
}
|
|
for source_variant in from_variants {
|
|
for target_variant in to_variants {
|
|
if target_variant.id == source_variant.id && target_variant.payload == source_variant.payload {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
|
|
return define_record(store, id, fields, c_layout, opaque)
|
|
}
|
|
|
|
fields_for :: proc(store: ^Store, value: Type) -> []Field {
|
|
item, ok := node(store, value)
|
|
if !ok || (item.kind != .Struct && item.kind != .Union) {
|
|
return nil
|
|
}
|
|
start := int(item.field_start)
|
|
end := start+int(item.field_count)
|
|
if start < 0 || end > len(store.fields) {
|
|
return nil
|
|
}
|
|
return store.fields[start:end]
|
|
}
|
|
|
|
field_layout_precedes :: proc(
|
|
store: ^Store,
|
|
value: Type,
|
|
left, right: int,
|
|
selected := target.DEFAULT,
|
|
) -> bool {
|
|
fields := fields_for(store, value)
|
|
item, ok := node(store, value)
|
|
if !ok || item.kind != .Struct || item.c_layout ||
|
|
left < 0 || left >= len(fields) || right < 0 || right >= len(fields) {
|
|
return left < right
|
|
}
|
|
left_alignment := alignment_of(fields[left].type, store, selected)
|
|
right_alignment := alignment_of(fields[right].type, store, selected)
|
|
return left_alignment > right_alignment ||
|
|
(left_alignment == right_alignment && left < right)
|
|
}
|
|
|
|
physical_field_index :: proc(
|
|
store: ^Store,
|
|
value: Type,
|
|
logical_index: int,
|
|
selected := target.DEFAULT,
|
|
) -> int {
|
|
fields := fields_for(store, value)
|
|
if logical_index < 0 || logical_index >= len(fields) {
|
|
return logical_index
|
|
}
|
|
result := 0
|
|
for _, other_index in fields {
|
|
if field_layout_precedes(store, value, other_index, logical_index, selected) {
|
|
result += 1
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
params_for :: proc(store: ^Store, value: Type) -> []Field {
|
|
item, ok := node(store, value)
|
|
if !ok || item.kind != .Function {
|
|
return nil
|
|
}
|
|
start := int(item.field_start)
|
|
end := start+int(item.field_count)
|
|
if start < 0 || end > len(store.fields) {
|
|
return nil
|
|
}
|
|
return store.fields[start:end]
|
|
}
|
|
|
|
enum_members_for :: proc(store: ^Store, value: Type) -> []Enum_Member {
|
|
item, ok := node(store, value)
|
|
if !ok || item.kind != .Enum {
|
|
return nil
|
|
}
|
|
start := int(item.field_start)
|
|
end := start+int(item.field_count)
|
|
if start < 0 || end > len(store.enum_members) {
|
|
return nil
|
|
}
|
|
return store.enum_members[start:end]
|
|
}
|
|
|
|
kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
|
switch value {
|
|
case INVALID:
|
|
return .Invalid
|
|
case VOID:
|
|
return .Void
|
|
case NORETURN:
|
|
return .Noreturn
|
|
case ANYOPAQUE:
|
|
return .Anyopaque
|
|
case INT:
|
|
return .Int_Constraint
|
|
case UINT:
|
|
return .Uint_Constraint
|
|
case FLOAT:
|
|
return .Float_Constraint
|
|
case RANGE:
|
|
return .Range_Constraint
|
|
case BOOL:
|
|
return .Scalar
|
|
}
|
|
if value >= I8 && value <= C_LONGDOUBLE {
|
|
return .Scalar
|
|
}
|
|
if store != nil && value >= DYNAMIC_START {
|
|
index := int(value-DYNAMIC_START)
|
|
if index >= 0 && index < len(store.nodes) {
|
|
return store.nodes[index].kind
|
|
}
|
|
}
|
|
return .Invalid
|
|
}
|
|
|
|
node :: proc(store: ^Store, value: Type) -> (Node, bool) {
|
|
if store == nil || value < DYNAMIC_START {
|
|
return {}, false
|
|
}
|
|
index := int(value-DYNAMIC_START)
|
|
if index < 0 || index >= len(store.nodes) {
|
|
return {}, false
|
|
}
|
|
return store.nodes[index], true
|
|
}
|
|
|
|
is_valid :: proc(value: Type) -> bool {
|
|
return value != INVALID
|
|
}
|
|
|
|
is_void :: proc(value: Type) -> bool {
|
|
return value == VOID
|
|
}
|
|
|
|
is_noreturn :: proc(value: Type) -> bool {
|
|
return value == NORETURN
|
|
}
|
|
|
|
is_anyopaque :: proc(value: Type) -> bool {
|
|
return value == ANYOPAQUE
|
|
}
|
|
|
|
is_bool :: proc(value: Type) -> bool {
|
|
return value == BOOL
|
|
}
|
|
|
|
is_constraint :: proc(value: Type) -> bool {
|
|
return value == INT || value == UINT || value == FLOAT || value == RANGE
|
|
}
|
|
|
|
// constraint_target reports the concrete type a constraint binding (local, or a
|
|
// param/result monomorphized per call site) takes for an inferred value, or
|
|
// INVALID if the value's family is incompatible. FLOAT accepts integers by
|
|
// defaulting them to f64: a constant integer becomes a float literal in
|
|
// build_constant_expr, while a runtime integer then fails the cross-family f64
|
|
// coercion in coerce_expr (the intended mismatch error). RANGE accepts any range
|
|
// type, keeping its inferred element type.
|
|
constraint_target :: proc(constraint, inferred: Type, store: ^Store = nil) -> Type {
|
|
switch constraint {
|
|
case INT:
|
|
return inferred if is_concrete_integer(inferred) else INVALID
|
|
case UINT:
|
|
return inferred if is_unsigned(inferred) else INVALID
|
|
case FLOAT:
|
|
if is_float(inferred) {
|
|
return inferred
|
|
}
|
|
return F64 if is_concrete_integer(inferred) else INVALID
|
|
case RANGE:
|
|
return inferred if is_range(inferred, store) else INVALID
|
|
}
|
|
return INVALID
|
|
}
|
|
|
|
// constraint_accepts reports strict family membership, used when widening a
|
|
// constraint binding across assignments (no integer-to-float defaulting here).
|
|
constraint_accepts :: proc(constraint, concrete: Type, store: ^Store = nil) -> bool {
|
|
switch constraint {
|
|
case INT:
|
|
return is_concrete_integer(concrete)
|
|
case UINT:
|
|
return is_unsigned(concrete)
|
|
case FLOAT:
|
|
return is_float(concrete)
|
|
case RANGE:
|
|
return is_range(concrete, store)
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_c :: proc(value: Type) -> bool {
|
|
return value >= C_CHAR && value <= C_LONGDOUBLE
|
|
}
|
|
|
|
as_c_primitive :: proc(value: Type) -> (target.C_Primitive, bool) {
|
|
switch value {
|
|
case C_CHAR: return .Char, true
|
|
case C_SCHAR: return .Schar, true
|
|
case C_UCHAR: return .Uchar, true
|
|
case C_SHORT: return .Short, true
|
|
case C_USHORT: return .Ushort, true
|
|
case C_INT: return .Int, true
|
|
case C_UINT: return .Uint, true
|
|
case C_LONG: return .Long, true
|
|
case C_ULONG: return .Ulong, true
|
|
case C_LONGLONG: return .Longlong, true
|
|
case C_ULONGLONG: return .Ulonglong, true
|
|
case C_FLOAT: return .Float, true
|
|
case C_DOUBLE: return .Double, true
|
|
case C_LONGDOUBLE: return .Longdouble, true
|
|
}
|
|
return {}, false
|
|
}
|
|
|
|
category :: proc(value: Type, selected := target.DEFAULT) -> Numeric_Category {
|
|
switch value {
|
|
case I8, I16, I32, I64, ISIZE:
|
|
return .Signed_Integer
|
|
case U8, U16, U32, U64, USIZE:
|
|
return .Unsigned_Integer
|
|
case F32, F64:
|
|
return .Float
|
|
case:
|
|
primitive, ok := as_c_primitive(value)
|
|
if !ok {
|
|
return .None
|
|
}
|
|
switch target.c_primitive_layout(selected, primitive).kind {
|
|
case .Signed_Integer: return .Signed_Integer
|
|
case .Unsigned_Integer: return .Unsigned_Integer
|
|
case .Float: return .Float
|
|
}
|
|
}
|
|
return .None
|
|
}
|
|
|
|
bits :: proc(value: Type, selected := target.DEFAULT) -> int {
|
|
switch value {
|
|
case BOOL:
|
|
return 1
|
|
case I8, U8:
|
|
return 8
|
|
case I16, U16:
|
|
return 16
|
|
case I32, U32, F32:
|
|
return 32
|
|
case I64, U64, F64:
|
|
return 64
|
|
case ISIZE, USIZE:
|
|
return target.pointer_bits(selected)
|
|
case:
|
|
primitive, ok := as_c_primitive(value)
|
|
return target.c_primitive_layout(selected, primitive).bits if ok else 0
|
|
}
|
|
}
|
|
|
|
alignment :: proc(value: Type, selected := target.DEFAULT) -> int {
|
|
if primitive, ok := as_c_primitive(value); ok {
|
|
return target.c_primitive_layout(selected, primitive).alignment
|
|
}
|
|
width := bits(value, selected)/8
|
|
return min(max(width, 1), 8)
|
|
}
|
|
|
|
representation :: proc(value: Type, selected := target.DEFAULT) -> Type {
|
|
if value == ISIZE {
|
|
return I64
|
|
}
|
|
if value == USIZE {
|
|
return U64
|
|
}
|
|
primitive, ok := as_c_primitive(value)
|
|
if !ok {
|
|
return value
|
|
}
|
|
layout := target.c_primitive_layout(selected, primitive)
|
|
if layout.kind == .Float {
|
|
return F32 if layout.bits == 32 else F64
|
|
}
|
|
if layout.kind == .Signed_Integer {
|
|
switch layout.bits {
|
|
case 8: return I8
|
|
case 16: return I16
|
|
case 32: return I32
|
|
case: return I64
|
|
}
|
|
}
|
|
switch layout.bits {
|
|
case 8: return U8
|
|
case 16: return U16
|
|
case 32: return U32
|
|
case: return U64
|
|
}
|
|
}
|
|
|
|
is_concrete_scalar :: proc(value: Type) -> bool {
|
|
return kind(value) == .Scalar
|
|
}
|
|
|
|
is_concrete :: proc(value: Type, store: ^Store = nil) -> bool {
|
|
value_kind := kind(value, store)
|
|
if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer ||
|
|
value_kind == .Slice || value_kind == .Range || value_kind == .Optional ||
|
|
value_kind == .Enum || value_kind == .Fallible {
|
|
return true
|
|
}
|
|
if value_kind == .Struct || value_kind == .Union {
|
|
item, ok := node(store, value)
|
|
return ok && item.declared
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_pointer :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Pointer
|
|
}
|
|
|
|
is_array :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Array
|
|
}
|
|
|
|
is_slice :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Slice
|
|
}
|
|
|
|
is_range :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Range
|
|
}
|
|
|
|
is_optional :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Optional
|
|
}
|
|
|
|
is_function :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Function
|
|
}
|
|
|
|
is_struct :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Struct
|
|
}
|
|
|
|
is_record :: proc(value: Type, store: ^Store) -> bool {
|
|
value_kind := kind(value, store)
|
|
return value_kind == .Struct || value_kind == .Union
|
|
}
|
|
|
|
is_union :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Union
|
|
}
|
|
|
|
// A tagged union is a `.Union` whose `child` is a valid enum (the discriminant).
|
|
is_tagged_union :: proc(value: Type, store: ^Store) -> bool {
|
|
item, ok := node(store, value)
|
|
return ok && item.kind == .Union && is_enum(item.child, store)
|
|
}
|
|
|
|
union_tag_enum :: proc(value: Type, store: ^Store) -> Type {
|
|
if !is_tagged_union(value, store) {
|
|
return INVALID
|
|
}
|
|
item, _ := node(store, value)
|
|
return item.child
|
|
}
|
|
|
|
union_declared_tag_enum :: proc(value: Type, store: ^Store) -> Type {
|
|
if !is_tagged_union(value, store) {
|
|
return INVALID
|
|
}
|
|
item, _ := node(store, value)
|
|
return item.extra if is_valid(item.extra) else item.child
|
|
}
|
|
|
|
// union_payload_offset is the byte offset of a tagged union's payload carrier (after
|
|
// the discriminant), shared by `size` and the LLVM emitter so construction, field
|
|
// access, and layout agree. Zero for untagged unions.
|
|
union_payload_offset :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
|
if !is_tagged_union(value, store) {
|
|
return 0
|
|
}
|
|
item, _ := node(store, value)
|
|
tag_size := size(item.child, store, selected)
|
|
payload_align: u64 = 1
|
|
for field in fields_for(store, value) {
|
|
payload_align = max(payload_align, u64(alignment_of(field.type, store, selected)))
|
|
}
|
|
return (tag_size+payload_align-1)/payload_align*payload_align
|
|
}
|
|
|
|
sum_tag_type :: proc(value: Type, store: ^Store) -> Type {
|
|
if is_enum(value, store) {
|
|
return value
|
|
}
|
|
if is_tagged_union(value, store) {
|
|
return union_tag_enum(value, store)
|
|
}
|
|
return INVALID
|
|
}
|
|
|
|
is_distinct :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Distinct
|
|
}
|
|
|
|
is_enum :: proc(value: Type, store: ^Store) -> bool {
|
|
return kind(value, store) == .Enum
|
|
}
|
|
|
|
resolve_alias :: proc(value: Type, store: ^Store, depth := 0) -> Type {
|
|
if depth > 64 {
|
|
return INVALID
|
|
}
|
|
item, ok := node(store, value)
|
|
if !ok || item.kind != .Alias {
|
|
return value
|
|
}
|
|
return resolve_alias(item.child, store, depth+1)
|
|
}
|
|
|
|
is_c_record_field_type :: proc(value: Type, store: ^Store, depth := 0) -> bool {
|
|
if depth > 256 {
|
|
return false
|
|
}
|
|
resolved := resolve_alias(value, store)
|
|
if contains_distinct(resolved, store) {
|
|
return false
|
|
}
|
|
if is_concrete_scalar(resolved) || is_pointer(resolved, store) || is_optional_pointer(resolved, store) {
|
|
return true
|
|
}
|
|
item, ok := node(store, resolved)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.kind == .Enum {
|
|
return item.explicit_backing && is_concrete_integer(item.child)
|
|
}
|
|
if item.kind == .Array {
|
|
return item.count > 0 && !item.has_sentinel && !item.inferred_count &&
|
|
is_c_record_field_type(item.child, store, depth+1)
|
|
}
|
|
if item.kind != .Struct && item.kind != .Union {
|
|
return false
|
|
}
|
|
if !item.c_layout || !item.declared || item.opaque || item.field_count == 0 {
|
|
return false
|
|
}
|
|
for field in fields_for(store, resolved) {
|
|
if !is_c_record_field_type(field.type, store, depth+1) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
is_optional_pointer :: proc(value: Type, store: ^Store) -> bool {
|
|
item, ok := node(store, value)
|
|
return ok && item.kind == .Optional && is_pointer(item.child, store)
|
|
}
|
|
|
|
is_comptime_only :: proc(value: Type, store: ^Store, depth := 0) -> bool {
|
|
if depth > 256 {
|
|
return false
|
|
}
|
|
item, ok := node(store, value)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.kind == .Function {
|
|
return true
|
|
}
|
|
if item.kind == .Pointer || item.kind == .Slice || item.kind == .Range || item.kind == .Fallible {
|
|
return false
|
|
}
|
|
if item.kind == .Array || item.kind == .Optional || item.kind == .Distinct || item.kind == .Enum || item.kind == .Alias {
|
|
return is_comptime_only(item.child, store, depth+1)
|
|
}
|
|
if item.kind == .Struct || item.kind == .Union {
|
|
for field in fields_for(store, value) {
|
|
if is_comptime_only(field.type, store, depth+1) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
|
|
if depth > 256 {
|
|
return false
|
|
}
|
|
value_kind := kind(value, store)
|
|
if value_kind == .Scalar || value_kind == .Pointer {
|
|
return true
|
|
}
|
|
if value_kind == .Slice || value_kind == .Array || value_kind == .Range {
|
|
item, ok := node(store, value)
|
|
return ok && is_runtime_value(item.child, store, depth+1) && !contains_c_struct_by_value(value, store)
|
|
}
|
|
if value_kind == .Optional {
|
|
item, ok := node(store, value)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if is_pointer(item.child, store) {
|
|
return true
|
|
}
|
|
return is_runtime_value(item.child, store, depth+1) && !contains_c_struct_by_value(value, store)
|
|
}
|
|
if value_kind == .Struct || value_kind == .Union {
|
|
item, ok := node(store, value)
|
|
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0) &&
|
|
!is_comptime_only(value, store)
|
|
}
|
|
if value_kind == .Fallible {
|
|
item, ok := node(store, value)
|
|
if !ok || !is_runtime_value(item.extra, store, depth+1) {
|
|
return false
|
|
}
|
|
return is_void(item.child) || is_runtime_value(item.child, store, depth+1)
|
|
}
|
|
if value_kind == .Distinct || value_kind == .Enum {
|
|
item, ok := node(store, value)
|
|
return ok && item.declared && is_runtime_value(item.child, store, depth+1)
|
|
}
|
|
return false
|
|
}
|
|
|
|
distinct_backing :: proc(value: Type, store: ^Store) -> (Type, bool) {
|
|
item, ok := node(store, value)
|
|
if !ok || item.kind != .Distinct || !item.declared {
|
|
return INVALID, false
|
|
}
|
|
return item.child, true
|
|
}
|
|
|
|
distinct_scalar_backing :: proc(value: Type, store: ^Store) -> (Type, bool) {
|
|
_, ok := distinct_backing(value, store)
|
|
if !ok {
|
|
return INVALID, false
|
|
}
|
|
backing := runtime_representation(value, store)
|
|
return backing, is_concrete_scalar(backing)
|
|
}
|
|
|
|
can_retype_distinct :: proc(from, to: Type, store: ^Store) -> bool {
|
|
if backing, ok := distinct_backing(to, store); ok && equal(from, backing) {
|
|
return true
|
|
}
|
|
backing, ok := distinct_backing(from, store)
|
|
if !ok || !equal(to, backing) {
|
|
return false
|
|
}
|
|
_, scalar := distinct_scalar_backing(from, store)
|
|
return scalar
|
|
}
|
|
|
|
runtime_representation :: proc(value: Type, store: ^Store, depth := 0) -> Type {
|
|
if depth > 256 {
|
|
return INVALID
|
|
}
|
|
item, ok := node(store, value)
|
|
if !ok || (item.kind != .Distinct && item.kind != .Enum) {
|
|
return value
|
|
}
|
|
return runtime_representation(item.child, store, depth+1)
|
|
}
|
|
|
|
sum_payload_size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
|
if is_enum(value, store) {
|
|
return 0
|
|
}
|
|
if !is_tagged_union(value, store) {
|
|
return size(value, store, selected)
|
|
}
|
|
result: u64
|
|
for field in fields_for(store, value) {
|
|
result = max(result, size(field.type, store, selected))
|
|
}
|
|
return result
|
|
}
|
|
|
|
sum_payload_alignment :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
|
if is_enum(value, store) {
|
|
return 1
|
|
}
|
|
if !is_tagged_union(value, store) {
|
|
return u64(alignment_of(value, store, selected))
|
|
}
|
|
result: u64 = 1
|
|
for field in fields_for(store, value) {
|
|
result = max(result, u64(alignment_of(field.type, store, selected)))
|
|
}
|
|
return result
|
|
}
|
|
|
|
fallible_payload_offset :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
|
item, ok := node(store, value)
|
|
if !ok || item.kind != .Fallible {
|
|
return 0
|
|
}
|
|
payload_align := max(
|
|
u64(1),
|
|
max(
|
|
u64(alignment_of(item.child, store, selected)) if !is_void(item.child) else u64(1),
|
|
sum_payload_alignment(item.extra, store, selected),
|
|
),
|
|
)
|
|
tag_size := size(U16, store, selected)
|
|
return (tag_size+payload_align-1)/payload_align*payload_align
|
|
}
|
|
|
|
fallible_payload_size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
|
item, ok := node(store, value)
|
|
if !ok || item.kind != .Fallible {
|
|
return 0
|
|
}
|
|
success_size := u64(0) if is_void(item.child) else size(item.child, store, selected)
|
|
return max(success_size, sum_payload_size(item.extra, store, selected))
|
|
}
|
|
|
|
contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
|
|
if depth > 256 {
|
|
return true
|
|
}
|
|
item, ok := node(store, value)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.kind == .Pointer {
|
|
return false
|
|
}
|
|
if item.kind == .Struct {
|
|
if item.opaque || (item.c_layout && item.field_count == 0) {
|
|
return true
|
|
}
|
|
for field in fields_for(store, value) {
|
|
if contains_c_struct_by_value(field.type, store, depth+1) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if item.kind == .Union {
|
|
return item.opaque || (item.c_layout && item.field_count == 0)
|
|
}
|
|
if item.kind == .Array || item.kind == .Slice || item.kind == .Range || item.kind == .Optional {
|
|
return contains_c_struct_by_value(item.child, store, depth+1)
|
|
}
|
|
if item.kind == .Fallible {
|
|
return contains_c_struct_by_value(item.child, store, depth+1) ||
|
|
contains_c_struct_by_value(item.extra, store, depth+1)
|
|
}
|
|
return false
|
|
}
|
|
|
|
is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) -> bool {
|
|
if allow_void && is_void(value) {
|
|
return true
|
|
}
|
|
if contains_distinct(value, store) {
|
|
return false
|
|
}
|
|
if item, ok := node(store, value); ok && item.kind == .Enum {
|
|
return item.explicit_backing && is_concrete_integer(item.child)
|
|
}
|
|
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) ||
|
|
(is_c_struct(value, store) && is_runtime_value(value, store))
|
|
}
|
|
|
|
// contains_distinct reports whether a `distinct` type is reachable from `value`
|
|
// (by value, behind a pointer, through fields/params/children). A pointer to a
|
|
// distinct type still counts — distinct types do not cross the C ABI even behind
|
|
// indirection. The `seen` set makes the graph walk terminate on self-referential
|
|
// records (e.g. `?*mut Node` inside `Node`), which previously recursed until the
|
|
// depth cap and wrongly reported `true`.
|
|
contains_distinct :: proc(value: Type, store: ^Store) -> bool {
|
|
seen: [dynamic]Type
|
|
defer delete(seen)
|
|
return contains_distinct_seen(value, store, &seen)
|
|
}
|
|
|
|
contains_distinct_seen :: proc(value: Type, store: ^Store, seen: ^[dynamic]Type) -> bool {
|
|
for visited in seen {
|
|
if visited == value {
|
|
return false
|
|
}
|
|
}
|
|
item, ok := node(store, value)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if item.kind == .Distinct {
|
|
return true
|
|
}
|
|
append(seen, value)
|
|
if item.kind == .Struct || item.kind == .Union {
|
|
for field in fields_for(store, value) {
|
|
if contains_distinct_seen(field.type, store, seen) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
if item.kind == .Function {
|
|
for param in params_for(store, value) {
|
|
if contains_distinct_seen(param.type, store, seen) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return (is_valid(item.child) && contains_distinct_seen(item.child, store, seen)) ||
|
|
(is_valid(item.extra) && contains_distinct_seen(item.extra, store, seen))
|
|
}
|
|
|
|
is_c_integer_promotion_candidate :: proc(value: Type) -> bool {
|
|
return value >= C_CHAR && value <= C_USHORT
|
|
}
|
|
|
|
c_vararg_promotion :: proc(value: Type, selected := target.DEFAULT, store: ^Store = nil) -> Type {
|
|
if store != nil {
|
|
if item, ok := node(store, value); ok && item.kind == .Enum {
|
|
return c_vararg_promotion(item.child, selected, store)
|
|
}
|
|
}
|
|
if !is_concrete_scalar(value) {
|
|
return value
|
|
}
|
|
if is_float(value, selected) && bits(value, selected) < bits(C_DOUBLE, selected) {
|
|
return C_DOUBLE
|
|
}
|
|
if is_concrete_integer(value) {
|
|
value_bits := bits(value, selected)
|
|
int_bits := bits(C_INT, selected)
|
|
if value_bits < int_bits {
|
|
return C_INT
|
|
}
|
|
if is_c_integer_promotion_candidate(value) && value_bits == int_bits {
|
|
return C_INT if is_signed(value, selected) else C_UINT
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
|
|
is_c_vararg_type :: proc(value: Type, store: ^Store) -> bool {
|
|
if item, ok := node(store, value); ok && item.kind == .Enum {
|
|
return item.explicit_backing && is_concrete_integer(item.child)
|
|
}
|
|
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store)
|
|
}
|
|
|
|
child_type :: proc(value: Type, store: ^Store) -> Type {
|
|
item, ok := node(store, value)
|
|
return item.child if ok else INVALID
|
|
}
|
|
|
|
logical_count :: proc(value: Type, store: ^Store) -> u64 {
|
|
item, ok := node(store, value)
|
|
return item.count if ok else 0
|
|
}
|
|
|
|
physical_count :: proc(value: Type, store: ^Store) -> u64 {
|
|
item, ok := node(store, value)
|
|
if !ok {
|
|
return 0
|
|
}
|
|
return item.count + (u64(1) if item.has_sentinel else u64(0))
|
|
}
|
|
|
|
is_mutable :: proc(value: Type, store: ^Store) -> bool {
|
|
item, ok := node(store, value)
|
|
return ok && item.mutable
|
|
}
|
|
|
|
is_many_pointer :: proc(value: Type, store: ^Store) -> bool {
|
|
item, ok := node(store, value)
|
|
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
|
|
}
|
|
|
|
function_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, function_item: Node, function_type: Type, ok: bool) {
|
|
pointer_node, pointer_ok := node(store, value)
|
|
if !pointer_ok || pointer_node.kind != .Pointer {
|
|
return {}, {}, INVALID, false
|
|
}
|
|
function_node, function_ok := node(store, pointer_node.child)
|
|
if !function_ok || function_node.kind != .Function {
|
|
return {}, {}, INVALID, false
|
|
}
|
|
return pointer_node, function_node, pointer_node.child, true
|
|
}
|
|
|
|
callable_function :: proc(value: Type, store: ^Store) -> (pointer_item, function_item: Node, function_type: Type, ok: bool) {
|
|
item, item_ok := node(store, value)
|
|
if item_ok && item.kind == .Function {
|
|
return {}, item, value, true
|
|
}
|
|
return function_pointer(value, store)
|
|
}
|
|
|
|
can_coerce_function_pointer :: proc(actual, expected: Type, store: ^Store) -> bool {
|
|
actual_item, actual_ok := node(store, actual)
|
|
_, _, expected_function, expected_ok := function_pointer(expected, store)
|
|
return actual_ok && actual_item.kind == .Function && expected_ok && equal(actual, expected_function)
|
|
}
|
|
|
|
replace_pointer_child :: proc(store: ^Store, value, child: Type) -> (Type, bool) {
|
|
item, ok := node(store, value)
|
|
if !ok {
|
|
return INVALID, false
|
|
}
|
|
if item.kind == .Optional {
|
|
replaced, replaced_ok := replace_pointer_child(store, item.child, child)
|
|
if !replaced_ok || !is_pointer(replaced, store) {
|
|
return INVALID, false
|
|
}
|
|
return optional(store, replaced), true
|
|
}
|
|
if item.kind != .Pointer {
|
|
return INVALID, false
|
|
}
|
|
item.child = child
|
|
return intern(store, item), true
|
|
}
|
|
|
|
restore_mutability :: proc(store: ^Store, value: Type) -> (Type, bool) {
|
|
item, ok := node(store, resolve_alias(value, store))
|
|
if !ok {
|
|
return INVALID, false
|
|
}
|
|
if item.kind == .Optional {
|
|
restored, restored_ok := restore_mutability(store, item.child)
|
|
if !restored_ok || !is_pointer(restored, store) {
|
|
return INVALID, false
|
|
}
|
|
return optional(store, restored), true
|
|
}
|
|
if item.kind != .Pointer && item.kind != .Slice {
|
|
return INVALID, false
|
|
}
|
|
item.mutable = true
|
|
return intern(store, item), true
|
|
}
|
|
|
|
same_constcast_shape :: proc(from, to: Type, store: ^Store) -> bool {
|
|
from_item, from_ok := node(store, resolve_alias(from, store))
|
|
to_item, to_ok := node(store, resolve_alias(to, store))
|
|
if !from_ok || !to_ok {
|
|
return false
|
|
}
|
|
if from_item.kind == .Optional || to_item.kind == .Optional {
|
|
return from_item.kind == .Optional && to_item.kind == .Optional &&
|
|
is_pointer(from_item.child, store) && is_pointer(to_item.child, store) &&
|
|
same_constcast_shape(from_item.child, to_item.child, store)
|
|
}
|
|
return (from_item.kind == .Pointer || from_item.kind == .Slice) &&
|
|
from_item.kind == to_item.kind &&
|
|
from_item.child == to_item.child &&
|
|
from_item.many == to_item.many &&
|
|
to_item.mutable &&
|
|
from_item.has_sentinel == to_item.has_sentinel &&
|
|
(!from_item.has_sentinel || from_item.sentinel == to_item.sentinel)
|
|
}
|
|
|
|
same_pointer_shape :: proc(left, right: Type, store: ^Store) -> bool {
|
|
left_item, left_ok := node(store, left)
|
|
right_item, right_ok := node(store, right)
|
|
if !left_ok || !right_ok {
|
|
return false
|
|
}
|
|
if left_item.kind == .Optional || right_item.kind == .Optional {
|
|
return left_item.kind == .Optional && right_item.kind == .Optional &&
|
|
same_pointer_shape(left_item.child, right_item.child, store)
|
|
}
|
|
return left_item.kind == .Pointer && right_item.kind == .Pointer &&
|
|
left_item.many == right_item.many &&
|
|
left_item.mutable == right_item.mutable &&
|
|
left_item.has_sentinel == right_item.has_sentinel &&
|
|
(!left_item.has_sentinel || left_item.sentinel == right_item.sentinel)
|
|
}
|
|
|
|
is_c_struct :: proc(value: Type, store: ^Store) -> bool {
|
|
item, ok := node(store, value)
|
|
return ok && (item.kind == .Struct || item.kind == .Union) && item.c_layout
|
|
}
|
|
|
|
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 {
|
|
return intern(store, Node{
|
|
kind=.Slice,
|
|
child=child,
|
|
mutable=mutable,
|
|
has_sentinel=has_sentinel,
|
|
sentinel=sentinel,
|
|
})
|
|
}
|
|
|
|
range :: proc(store: ^Store, child: Type) -> Type {
|
|
return intern(store, Node{kind=.Range, child=child})
|
|
}
|
|
|
|
array :: proc(
|
|
store: ^Store,
|
|
child: Type,
|
|
count: u64,
|
|
mutable: bool,
|
|
has_sentinel := false,
|
|
sentinel: u64 = 0,
|
|
) -> Type {
|
|
return intern(store, Node{
|
|
kind=.Array,
|
|
child=child,
|
|
count=count,
|
|
mutable=mutable,
|
|
has_sentinel=has_sentinel,
|
|
sentinel=sentinel,
|
|
})
|
|
}
|
|
|
|
optional :: proc(store: ^Store, child: Type) -> Type {
|
|
return intern(store, Node{kind=.Optional, child=child})
|
|
}
|
|
|
|
function_params_equal :: proc(store: ^Store, item: Node, params: []Type) -> bool {
|
|
if item.field_count != u32(len(params)) {
|
|
return false
|
|
}
|
|
start := int(item.field_start)
|
|
end := start+int(item.field_count)
|
|
if start < 0 || end > len(store.fields) {
|
|
return false
|
|
}
|
|
for param, index in params {
|
|
if store.fields[start+index].type != param {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
function :: proc(store: ^Store, params: []Type, result: Type, c_abi, variadic: bool) -> Type {
|
|
for existing, index in store.nodes {
|
|
if existing.kind == .Function &&
|
|
existing.child == result &&
|
|
existing.c_abi == c_abi &&
|
|
existing.variadic == variadic &&
|
|
function_params_equal(store, existing, params) {
|
|
return DYNAMIC_START+Type(index)
|
|
}
|
|
}
|
|
start := len(store.fields)
|
|
for param in params {
|
|
append(&store.fields, Field{type=param})
|
|
}
|
|
return intern(store, Node{
|
|
kind=.Function,
|
|
child=result,
|
|
field_start=u32(start),
|
|
field_count=u32(len(params)),
|
|
c_abi=c_abi,
|
|
variadic=variadic,
|
|
})
|
|
}
|
|
|
|
with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type {
|
|
item, ok := node(store, value)
|
|
if !ok || item.kind != .Array {
|
|
return value
|
|
}
|
|
item.count = count
|
|
item.inferred_count = false
|
|
item.unresolved_count = false
|
|
item.count_expr = 0
|
|
return intern(store, item)
|
|
}
|
|
|
|
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 == .Optional || to_node.kind == .Optional) {
|
|
return from_node.kind == .Optional && to_node.kind == .Optional &&
|
|
can_weaken_pointer(from_node.child, to_node.child, store)
|
|
}
|
|
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
|
|
anyopaque_erasure := to_node.child == ANYOPAQUE &&
|
|
(is_runtime_value(from_node.child, store) ||
|
|
is_opaque_struct(from_node.child, store))
|
|
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 || anyopaque_erasure || 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 == .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_slice_c_string :: 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 == .Slice && to_node.kind == .Pointer && to_node.many &&
|
|
from_node.child == U8 && to_node.child == C_CHAR &&
|
|
from_node.has_sentinel && from_node.sentinel == 0 && !from_node.mutable &&
|
|
!to_node.mutable
|
|
}
|
|
|
|
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 {
|
|
item, ok := node(store, value)
|
|
return ok && (item.kind == .Struct || item.kind == .Union) && item.opaque
|
|
}
|
|
|
|
size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
|
#partial switch kind(value, store) {
|
|
case .Scalar:
|
|
return u64((bits(value, selected)+7)/8)
|
|
case .Pointer:
|
|
return u64(target.pointer_bits(selected)/8)
|
|
case .Slice:
|
|
return u64(target.pointer_bits(selected)/8*2)
|
|
case .Range:
|
|
child_size := size(child_type(value, store), store, selected)
|
|
child_align := u64(alignment_of(child_type(value, store), store, selected))
|
|
raw_size := child_size*2+1
|
|
return (raw_size+child_align-1)/child_align*child_align
|
|
case .Array:
|
|
item, _ := node(store, value)
|
|
return physical_count(value, store)*size(item.child, store, selected)
|
|
case .Optional:
|
|
item, _ := node(store, value)
|
|
if is_pointer(item.child, store) {
|
|
return u64(target.pointer_bits(selected)/8)
|
|
}
|
|
child_size := size(item.child, store, selected)
|
|
child_align := u64(alignment_of(item.child, store, selected))
|
|
return ((child_size+1+child_align-1)/child_align)*child_align
|
|
case .Function:
|
|
return 0
|
|
case .Distinct, .Enum:
|
|
return size(child_type(value, store), store, selected)
|
|
case .Struct:
|
|
item, _ := node(store, value)
|
|
if item.explicit_size > 0 {
|
|
return item.explicit_size
|
|
}
|
|
offset: u64
|
|
max_align: u64 = 1
|
|
if !item.c_layout {
|
|
for field in fields_for(store, value) {
|
|
offset += size(field.type, store, selected)
|
|
max_align = max(max_align, u64(alignment_of(field.type, store, selected)))
|
|
}
|
|
} else {
|
|
for field in fields_for(store, value) {
|
|
field_align := u64(alignment_of(field.type, store, selected))
|
|
offset = (offset+field_align-1)/field_align*field_align
|
|
offset += size(field.type, store, selected)
|
|
max_align = max(max_align, field_align)
|
|
}
|
|
}
|
|
return (offset+max_align-1)/max_align*max_align
|
|
case .Union:
|
|
item, _ := node(store, value)
|
|
if item.explicit_size > 0 {
|
|
return item.explicit_size
|
|
}
|
|
carrier_size: u64
|
|
max_align: u64 = 1
|
|
for field in fields_for(store, value) {
|
|
carrier_size = max(carrier_size, size(field.type, store, selected))
|
|
max_align = max(max_align, u64(alignment_of(field.type, store, selected)))
|
|
}
|
|
if is_enum(item.child, store) {
|
|
payload_offset := union_payload_offset(value, store, selected)
|
|
total_align := max(max_align, u64(alignment_of(item.child, store, selected)))
|
|
return (payload_offset+carrier_size+total_align-1)/total_align*total_align
|
|
}
|
|
return (carrier_size+max_align-1)/max_align*max_align
|
|
case .Fallible:
|
|
payload_offset := fallible_payload_offset(value, store, selected)
|
|
payload_size := fallible_payload_size(value, store, selected)
|
|
total_align := u64(alignment_of(value, store, selected))
|
|
return (payload_offset+payload_size+total_align-1)/total_align*total_align
|
|
case:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> int {
|
|
#partial switch kind(value, store) {
|
|
case .Scalar:
|
|
return alignment(value, selected)
|
|
case .Pointer, .Slice:
|
|
return target.pointer_bits(selected)/8
|
|
case .Array, .Range, .Optional:
|
|
return alignment_of(child_type(value, store), store, selected)
|
|
case .Function:
|
|
return 1
|
|
case .Distinct, .Enum:
|
|
return alignment_of(child_type(value, store), store, selected)
|
|
case .Struct:
|
|
item, _ := node(store, value)
|
|
if item.explicit_alignment > 0 {
|
|
return int(item.explicit_alignment)
|
|
}
|
|
result := 1
|
|
for field in fields_for(store, value) {
|
|
result = max(result, alignment_of(field.type, store, selected))
|
|
}
|
|
return result
|
|
case .Union:
|
|
item, _ := node(store, value)
|
|
if item.explicit_alignment > 0 {
|
|
return int(item.explicit_alignment)
|
|
}
|
|
result := 1
|
|
for field in fields_for(store, value) {
|
|
result = max(result, alignment_of(field.type, store, selected))
|
|
}
|
|
if is_enum(item.child, store) {
|
|
result = max(result, alignment_of(item.child, store, selected))
|
|
}
|
|
return result
|
|
case .Fallible:
|
|
item, _ := node(store, value)
|
|
result := alignment_of(U16, store, selected)
|
|
if !is_void(item.child) {
|
|
result = max(result, alignment_of(item.child, store, selected))
|
|
}
|
|
result = max(result, int(sum_payload_alignment(item.extra, store, selected)))
|
|
return result
|
|
case:
|
|
return 1
|
|
}
|
|
}
|
|
|
|
is_concrete_integer :: proc(value: Type) -> bool {
|
|
category := category(value)
|
|
return kind(value) == .Scalar &&
|
|
(category == .Signed_Integer || category == .Unsigned_Integer)
|
|
}
|
|
|
|
is_float :: proc(value: Type, selected := target.DEFAULT) -> bool {
|
|
return kind(value) == .Scalar && category(value, selected) == .Float
|
|
}
|
|
|
|
is_signed :: proc(value: Type, selected := target.DEFAULT) -> bool {
|
|
return kind(value) == .Scalar && category(value, selected) == .Signed_Integer
|
|
}
|
|
|
|
is_unsigned :: proc(value: Type, selected := target.DEFAULT) -> bool {
|
|
return kind(value) == .Scalar && category(value, selected) == .Unsigned_Integer
|
|
}
|
|
|
|
equal :: proc(a, b: Type) -> bool {
|
|
return a == b
|
|
}
|
|
|
|
same_numeric_family :: proc(a, b: Type) -> bool {
|
|
if category(a) != category(b) {
|
|
return false
|
|
}
|
|
// C primitives are intentionally distinct semantic types. Exact-width
|
|
// Brolang scalars may widen only to other Brolang scalars.
|
|
return !is_c(a) && !is_c(b)
|
|
}
|
|
|
|
can_widen :: proc(from, to: Type) -> bool {
|
|
if equal(from, to) {
|
|
return true
|
|
}
|
|
return is_concrete_scalar(from) &&
|
|
is_concrete_scalar(to) &&
|
|
same_numeric_family(from, to) &&
|
|
bits(from) < bits(to)
|
|
}
|
|
|
|
// can_coerce_c_integer reports whether `from` may implicitly convert to `to`
|
|
// under C's integer conversion rules. Brolang keeps its own exact-width scalars
|
|
// strict (`u32 -> i32` is rejected), but C interop types deliberately follow C:
|
|
// virtually every C library relies on it — e.g. an unsigned-backed enum constant
|
|
// (`c_uint`) passed to an `int` (`c_int`) parameter — so disallowing it would
|
|
// make C interop cumbersome. Scope: widening (sext/zext) and same-width
|
|
// signedness changes (no-op reinterpret); narrowing is intentionally excluded so
|
|
// lossy conversions stay an error, matching brolang's trap-on-narrow philosophy.
|
|
can_coerce_c_integer :: proc(from, to: Type, selected := target.DEFAULT) -> bool {
|
|
return from != to && is_c(from) && is_c(to) &&
|
|
is_concrete_integer(from) && is_concrete_integer(to) &&
|
|
bits(from, selected) <= bits(to, selected)
|
|
}
|
|
|
|
can_coerce_c_scalar :: proc(from, to: Type, selected := target.DEFAULT) -> bool {
|
|
return from != to && !is_c(from) && is_c(to) &&
|
|
is_concrete_scalar(from) && is_concrete_scalar(to) &&
|
|
category(from, selected) == category(to, selected) &&
|
|
category(from, selected) != .None &&
|
|
bits(from, selected) <= bits(to, selected)
|
|
}
|
|
|
|
widest :: proc(a, b: Type) -> Type {
|
|
if is_noreturn(a) {
|
|
return b
|
|
}
|
|
if is_noreturn(b) {
|
|
return a
|
|
}
|
|
if equal(a, b) && is_concrete_scalar(a) {
|
|
return a
|
|
}
|
|
if !is_concrete_scalar(a) || !is_concrete_scalar(b) || !same_numeric_family(a, b) {
|
|
return INVALID
|
|
}
|
|
if bits(a) >= bits(b) {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
smallest_signed_for_literal :: proc(value: i64) -> Type {
|
|
if value >= -128 && value <= 127 {
|
|
return I8
|
|
}
|
|
if value >= -32768 && value <= 32767 {
|
|
return I16
|
|
}
|
|
if value >= -2147483648 && value <= 2147483647 {
|
|
return I32
|
|
}
|
|
return I64
|
|
}
|
|
|
|
smallest_unsigned_for_literal :: proc(value: u64) -> Type {
|
|
if value <= 255 {
|
|
return U8
|
|
}
|
|
if value <= 65535 {
|
|
return U16
|
|
}
|
|
if value <= 4294967295 {
|
|
return U32
|
|
}
|
|
return U64
|
|
}
|
|
|
|
name :: proc(value: Type) -> string {
|
|
switch value {
|
|
case INVALID: return "<invalid>"
|
|
case VOID: return "void"
|
|
case NORETURN: return "noreturn"
|
|
case ANYOPAQUE: return "anyopaque"
|
|
case BOOL: return "bool"
|
|
case INT: return "int"
|
|
case UINT: return "uint"
|
|
case FLOAT: return "float"
|
|
case RANGE: return "range"
|
|
case I8: return "i8"
|
|
case I16: return "i16"
|
|
case I32: return "i32"
|
|
case I64: return "i64"
|
|
case U8: return "u8"
|
|
case U16: return "u16"
|
|
case U32: return "u32"
|
|
case U64: return "u64"
|
|
case ISIZE: return "isize"
|
|
case USIZE: return "usize"
|
|
case F32: return "f32"
|
|
case F64: return "f64"
|
|
case C_CHAR: return "c_char"
|
|
case C_SCHAR: return "c_schar"
|
|
case C_UCHAR: return "c_uchar"
|
|
case C_SHORT: return "c_short"
|
|
case C_USHORT: return "c_ushort"
|
|
case C_INT: return "c_int"
|
|
case C_UINT: return "c_uint"
|
|
case C_LONG: return "c_long"
|
|
case C_ULONG: return "c_ulong"
|
|
case C_LONGLONG: return "c_longlong"
|
|
case C_ULONGLONG: return "c_ulonglong"
|
|
case C_FLOAT: return "c_float"
|
|
case C_DOUBLE: return "c_double"
|
|
case C_LONGDOUBLE: return "c_longdouble"
|
|
case:
|
|
return "fallible" if kind(value) == .Fallible else fmt.tprintf("<type %d>", value)
|
|
}
|
|
}
|