main
Download
848 lines 32.4 KiBCModified 2026-02-20 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
// Logic for handling type_t types

#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdint.h>
#include <sys/param.h>

#include "environment.h"
#include "stdlib/integers.h"
#include "stdlib/tables.h"
#include "stdlib/text.h"
#include "stdlib/util.h"
#include "types.h"

Text_t arg_types_to_text(arg_t *args, const char *separator) {
    Text_t text = EMPTY_TEXT;
    for (arg_t *arg = args; arg; arg = arg->next) {
        text = Texts(text, type_to_text(arg->type));
        if (arg->next) text = Texts(text, separator);
    }
    return text;
}

Text_t type_to_text(type_t *t) {
    if (!t) return Text("(Unknown type)");

    switch (t->tag) {
    case UnknownType: return Text("???");
    case AbortType: return Text("Abort");
    case ReturnType: {
        type_t *ret = Match(t, ReturnType)->ret;
        return Texts("Return(", ret ? type_to_text(ret) : Text("Void"), ")");
    }
    case VoidType: return Text("Void");
    case MemoryType: return Text("Memory");
    case BoolType: return Text("Bool");
    case ByteType: return Text("Byte");
    case CStringType: return Text("CString");
    case PathType: return Text("Path");
    case TextType: return Match(t, TextType)->lang ? Text$from_str(Match(t, TextType)->lang) : Text("Text");
    case BigIntType: return Text("Int");
    case IntType: return Texts("Int", (int32_t)Match(t, IntType)->bits);
    case NumType: return Match(t, NumType)->bits == TYPE_NBITS32 ? Text("Num32") : Text("Num");
    case ListType: {
        DeclareMatch(list, t, ListType);
        return Texts("[", type_to_text(list->item_type), "]");
    }
    case TableType: {
        DeclareMatch(table, t, TableType);
        return (table->value_type && table->value_type != PRESENT_TYPE)
                   ? Texts("{", type_to_text(table->key_type), ":", type_to_text(table->value_type), "}")
                   : Texts("{", type_to_text(table->key_type), "}");
    }
    case ClosureType: {
        return type_to_text(Match(t, ClosureType)->fn);
    }
    case FunctionType: {
        DeclareMatch(fn, t, FunctionType);
        Text_t text = Texts("func(", arg_types_to_text(fn->args, ","));
        if (fn->ret && fn->ret->tag != VoidType) text = Texts(text, fn->args ? " -> " : "-> ", type_to_text(fn->ret));
        text = Texts(text, ")");
        return text;
    }
    case StructType: {
        DeclareMatch(struct_, t, StructType);
        return Text$replace(Text$from_str(struct_->name), Text("$"), Text("."));
    }
    case PointerType: {
        DeclareMatch(ptr, t, PointerType);
        Text_t sigil = ptr->is_stack ? Text("&") : Text("@");
        return Texts(sigil, type_to_text(ptr->pointed));
    }
    case EnumType: {
        DeclareMatch(enum_, t, EnumType);
        if (enum_->name != NULL && strncmp(enum_->name, "enum$", strlen("enum$")) != 0)
            return Text$replace(Text$from_str(enum_->name), Text("$"), Text("."));
        Text_t text = Text("enum(");
        for (tag_t *tag = enum_->tags; tag; tag = tag->next) {
            text = Texts(text, Text$from_str(tag->name));
            if (tag->type && Match(tag->type, StructType)->fields) {
                text = Texts(text, "(");
                for (arg_t *field = Match(tag->type, StructType)->fields; field; field = field->next) {
                    text = Texts(text, Text$from_str(field->name), ":", type_to_text(field->type));
                    if (field->next) text = Texts(text, ", ");
                }
                text = Texts(text, ")");
            }
            if (tag->next) text = Texts(text, ", ");
        }
        return Texts(text, ")");
    }
    case OptionalType: {
        type_t *opt = Match(t, OptionalType)->type;
        if (opt) return Texts(type_to_text(opt), "?");
        else return Text("(Unknown optional type)");
    }
    case TypeInfoType: {
        return Texts("Type$info(", Match(t, TypeInfoType)->name, ")");
    }
    case ModuleType: {
        return Texts("Module(", Match(t, ModuleType)->name, ")");
    }
    default: {
        raise(SIGABRT);
        return Texts("Unknown type: ", (int32_t)t->tag);
    }
    }
}

PUREFUNC const char *get_type_name(type_t *t) {
    switch (t->tag) {
    case TextType: return Match(t, TextType)->lang;
    case StructType: return Match(t, StructType)->name;
    case EnumType: return Match(t, EnumType)->name;
    default: return NULL;
    }
}

bool type_eq(type_t *a, type_t *b) {
    if (a == b) return true;
    if (!a && !b) return true;
    if (!a || !b) return false;
    if (a->tag != b->tag) return false;
    return Text$equal_values(type_to_text(a), type_to_text(b));
}

bool type_is_a(type_t *t, type_t *req) {
    if (type_eq(t, req)) return true;
    if (req->tag == OptionalType && Match(req, OptionalType)->type) return type_is_a(t, Match(req, OptionalType)->type);
    if (t->tag == PointerType && req->tag == PointerType) {
        DeclareMatch(t_ptr, t, PointerType);
        DeclareMatch(req_ptr, req, PointerType);
        if (type_eq(t_ptr->pointed, req_ptr->pointed))
            return (!t_ptr->is_stack && req_ptr->is_stack) || (!t_ptr->is_stack);
    }
    return false;
}

type_t *non_optional(type_t *t) {
    return t->tag == OptionalType ? Match(t, OptionalType)->type : t;
}

PUREFUNC type_t *value_type(type_t *t) {
    while (t->tag == PointerType)
        t = Match(t, PointerType)->pointed;
    return t;
}

PUREFUNC bool is_discardable_type(type_t *t) {
    if (t->tag == StructType) {
        return (Match(t, StructType)->fields == NULL);
    }
    return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType);
}

type_t *type_or_type(type_t *a, type_t *b) {
    if (!a) return b;
    if (!b) return a;
    if (a->tag == OptionalType && !Match(a, OptionalType)->type)
        return b->tag == OptionalType ? b : Type(OptionalType, b);
    if (b->tag == OptionalType && !Match(b, OptionalType)->type)
        return a->tag == OptionalType ? a : Type(OptionalType, a);
    if (a->tag == ReturnType && b->tag == ReturnType)
        return Type(ReturnType, .ret = type_or_type(Match(a, ReturnType)->ret, Match(b, ReturnType)->ret));
    if ((a->tag == VoidType && is_discardable_type(b)) || (is_discardable_type(a) && b->tag == VoidType))
        return Type(VoidType);

    if (is_incomplete_type(a) && type_eq(b, most_complete_type(a, b))) return b;
    if (is_incomplete_type(b) && type_eq(a, most_complete_type(a, b))) return a;

    if (type_is_a(b, a)) return a;
    if (type_is_a(a, b)) return b;
    if (a->tag == AbortType || a->tag == ReturnType) return non_optional(b);
    if (b->tag == AbortType || b->tag == ReturnType) return non_optional(a);
    if ((a->tag == IntType || a->tag == NumType) && (b->tag == IntType || b->tag == NumType)) {
        switch (compare_precision(a, b)) {
        case NUM_PRECISION_EQUAL:
        case NUM_PRECISION_MORE: return a;
        case NUM_PRECISION_LESS: return b;
        default: return NULL;
        }
        return NULL;
    }
    return NULL;
}

static PUREFUNC INLINE double type_min_magnitude(type_t *t) {
    switch (t->tag) {
    case BoolType: return (double)false;
    case ByteType: return 0;
    case BigIntType: return -1. / 0.;
    case IntType: {
        switch (Match(t, IntType)->bits) {
        case TYPE_IBITS8: return (double)INT8_MIN;
        case TYPE_IBITS16: return (double)INT16_MIN;
        case TYPE_IBITS32: return (double)INT32_MIN;
        case TYPE_IBITS64: return (double)INT64_MIN;
        default: errx(1, "Invalid integer bit size");
        }
    }
    case NumType: return -1. / 0.;
    default: return (double)NAN;
    }
}

static PUREFUNC INLINE double type_max_magnitude(type_t *t) {
    switch (t->tag) {
    case BoolType: return (double)true;
    case ByteType: return (double)UINT8_MAX;
    case BigIntType: return 1. / 0.;
    case IntType: {
        switch (Match(t, IntType)->bits) {
        case TYPE_IBITS8: return (double)INT8_MAX;
        case TYPE_IBITS16: return (double)INT16_MAX;
        case TYPE_IBITS32: return (double)INT32_MAX;
        case TYPE_IBITS64: return (double)INT64_MAX;
        default: errx(1, "Invalid integer bit size");
        }
    }
    case NumType: return 1. / 0.;
    default: return (double)NAN;
    }
}

PUREFUNC precision_cmp_e compare_precision(type_t *a, type_t *b) {
    if (a == NULL || b == NULL) return NUM_PRECISION_INCOMPARABLE;

    if (is_int_type(a) && b->tag == NumType) return NUM_PRECISION_LESS;
    else if (a->tag == NumType && is_int_type(b)) return NUM_PRECISION_MORE;

    double a_min = type_min_magnitude(a), b_min = type_min_magnitude(b), a_max = type_max_magnitude(a),
           b_max = type_max_magnitude(b);

    if (isnan(a_min) || isnan(b_min) || isnan(a_max) || isnan(b_max)) return NUM_PRECISION_INCOMPARABLE;
    else if (a_min == b_min && a_max == b_max) return NUM_PRECISION_EQUAL;
    else if (a_min <= b_min && b_max <= a_max) return NUM_PRECISION_MORE;
    else if (b_min <= a_min && a_max <= b_max) return NUM_PRECISION_LESS;
    else return NUM_PRECISION_INCOMPARABLE;
}

bool _has_heap_memory(type_t *t, Table_t *visited) {
    if (!t) return false;
    Text_t type_text = type_to_text(t);
    if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false;
    Table$set(visited, &type_text, NULL, Set$info(&Text$info));

    switch (t->tag) {
    case ListType: return true;
    case TableType: return true;
    case PointerType: return true;
    case OptionalType: return _has_heap_memory(Match(t, OptionalType)->type, visited);
    case BigIntType: return true;
    case StructType: {
        for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
            if (_has_heap_memory(field->type, visited)) return true;
        }
        return false;
    }
    case EnumType: {
        for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
            if (tag->type && _has_heap_memory(tag->type, visited)) return true;
        }
        return false;
    }
    default: return false;
    }
}

bool has_heap_memory(type_t *t) {
    Table_t visited = EMPTY_TABLE;
    return _has_heap_memory(t, &visited);
}

bool _has_refcounts(type_t *t, Table_t *visited) {
    if (!t) return false;
    Text_t type_text = type_to_text(t);
    if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false;
    Table$set(visited, &type_text, NULL, Set$info(&Text$info));

    switch (t->tag) {
    case ListType: return true;
    case TableType: return true;
    case OptionalType: return _has_refcounts(Match(t, OptionalType)->type, visited);
    case StructType: {
        for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
            if (_has_refcounts(field->type, visited)) return true;
        }
        return false;
    }
    case EnumType: {
        for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
            if (tag->type && _has_refcounts(tag->type, visited)) return true;
        }
        return false;
    }
    default: return false;
    }
}

bool has_refcounts(type_t *t) {
    Table_t visited = EMPTY_TABLE;
    return _has_refcounts(t, &visited);
}

bool _has_stack_memory(type_t *t, Table_t *visited) {
    if (!t) return false;
    Text_t type_text = type_to_text(t);
    if (Table$get(*visited, &type_text, Set$info(&Text$info))) return false;
    Table$set(visited, &type_text, NULL, Set$info(&Text$info));

    switch (t->tag) {
    case PointerType: return Match(t, PointerType)->is_stack;
    case OptionalType: return _has_stack_memory(Match(t, OptionalType)->type, visited);
    case ListType: return _has_stack_memory(Match(t, ListType)->item_type, visited);
    case TableType:
        return _has_stack_memory(Match(t, TableType)->key_type, visited)
               || _has_stack_memory(Match(t, TableType)->value_type, visited);
    case StructType: {
        for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
            if (_has_stack_memory(field->type, visited)) return true;
        }
        return false;
    }
    case EnumType: {
        for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
            if (tag->type && _has_stack_memory(tag->type, visited)) return true;
        }
        return false;
    }
    default: return false;
    }
}

bool has_stack_memory(type_t *t) {
    Table_t visited = EMPTY_TABLE;
    return _has_stack_memory(t, &visited);
}

PUREFUNC const char *enum_single_value_tag(type_t *enum_type, type_t *t) {
    const char *found = NULL;
    for (tag_t *tag = Match(enum_type, EnumType)->tags; tag; tag = tag->next) {
        if (tag->type->tag != StructType) continue;
        DeclareMatch(s, tag->type, StructType);
        if (!s->fields || s->fields->next || !s->fields->type) continue;

        if (can_promote(t, s->fields->type)) {
            if (found) // Ambiguous case, multiple matches
                return NULL;
            found = tag->name;
            // Continue searching to check for ambiguous cases
        }
    }
    return found;
}

PUREFUNC bool can_promote(type_t *actual, type_t *needed) {
    if (!actual || !needed) return false;

    // No promotion necessary:
    if (type_eq(actual, needed)) return true;

    // Serialization/deserialization
    if (type_eq(actual, Type(ListType, Type(ByteType))) || type_eq(needed, Type(ListType, Type(ByteType)))) return true;

    if (actual->tag == NumType && needed->tag == IntType) return false;

    if (actual->tag == IntType && (needed->tag == NumType || needed->tag == BigIntType)) return true;

    if (actual->tag == BigIntType && needed->tag == NumType) return true;

    if (actual->tag == IntType && needed->tag == IntType) {
        precision_cmp_e cmp = compare_precision(actual, needed);
        return cmp == NUM_PRECISION_EQUAL || cmp == NUM_PRECISION_LESS;
    }

    if (needed->tag == EnumType) return (enum_single_value_tag(needed, actual) != NULL);

    // Text to C String
    if (actual->tag == TextType && !Match(actual, TextType)->lang && needed->tag == CStringType) return true;

    // Automatic dereferencing:
    if (actual->tag == PointerType && can_promote(Match(actual, PointerType)->pointed, needed)) return true;

    if (actual->tag == OptionalType) {
        if (needed->tag == BoolType) return true;

        // Ambiguous `none` to concrete optional
        if (Match(actual, OptionalType)->type == NULL) return (needed->tag == OptionalType);

        // Optional num -> num
        if (needed->tag == NumType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == NumType)
            return can_promote(Match(actual, OptionalType)->type, needed);
    }

    // Optional promotion:
    if (needed->tag == OptionalType && Match(needed, OptionalType)->type != NULL
        && can_promote(actual, Match(needed, OptionalType)->type))
        return true;

    if (needed->tag == PointerType && actual->tag == PointerType) {
        DeclareMatch(needed_ptr, needed, PointerType);
        DeclareMatch(actual_ptr, actual, PointerType);

        if (actual_ptr->is_stack && !needed_ptr->is_stack)
            // Can't use &x for a function that wants a @Foo or ?Foo
            return false;

        if (needed_ptr->pointed->tag == TableType && actual_ptr->pointed->tag == TableType)
            return can_promote(actual_ptr->pointed, needed_ptr->pointed);
        else if (needed_ptr->pointed->tag != MemoryType && !type_eq(needed_ptr->pointed, actual_ptr->pointed))
            // Can't use @Foo for a function that wants @Baz
            // But you *can* use @Foo for a function that wants @Memory
            return false;
        else return true;
    }

    // Empty literals:
    if (actual->tag == ListType && needed->tag == ListType && Match(actual, ListType)->item_type == NULL)
        return true; // [] -> [T]
    if (actual->tag == TableType && needed->tag == TableType && Match(actual, TableType)->key_type == NULL
        && Match(actual, TableType)->value_type == NULL)
        return true; // {} -> {K:V}

    // Cross-promotion between tables with default values and without
    if (needed->tag == TableType && actual->tag == TableType) {
        DeclareMatch(actual_table, actual, TableType);
        DeclareMatch(needed_table, needed, TableType);
        if (type_eq(needed_table->key_type, actual_table->key_type)
            && type_eq(needed_table->value_type, actual_table->value_type))
            return true;
    }

    if (actual->tag == FunctionType && needed->tag == ClosureType)
        return can_promote(actual, Match(needed, ClosureType)->fn);

    if (actual->tag == ClosureType && needed->tag == ClosureType)
        return can_promote(Match(actual, ClosureType)->fn, Match(needed, ClosureType)->fn);

    if (actual->tag == FunctionType && needed->tag == FunctionType) {
        for (arg_t *actual_arg = Match(actual, FunctionType)->args, *needed_arg = Match(needed, FunctionType)->args;
             actual_arg || needed_arg; actual_arg = actual_arg->next, needed_arg = needed_arg->next) {
            if (!actual_arg || !needed_arg) return false;
            if (type_eq(actual_arg->type, needed_arg->type)) continue;
            if (actual_arg->type->tag == PointerType && needed_arg->type->tag == PointerType
                && can_promote(actual_arg->type, needed_arg->type))
                continue;
            return false;
        }
        type_t *actual_ret = Match(actual, FunctionType)->ret;
        if (!actual_ret) actual_ret = Type(VoidType);
        type_t *needed_ret = Match(needed, FunctionType)->ret;
        if (!needed_ret) needed_ret = Type(VoidType);

        return ((type_eq(actual_ret, needed_ret))
                || (actual_ret->tag == PointerType && needed_ret->tag == PointerType
                    && can_promote(actual_ret, needed_ret)));
    }

    return false;
}

PUREFUNC bool is_int_type(type_t *t) {
    return t->tag == IntType || t->tag == BigIntType || t->tag == ByteType;
}

PUREFUNC bool is_numeric_type(type_t *t) {
    return t->tag == IntType || t->tag == BigIntType || t->tag == NumType || t->tag == ByteType;
}

PUREFUNC bool is_packed_data(type_t *t) {
    if (t->tag == IntType || t->tag == NumType || t->tag == ByteType || t->tag == PointerType || t->tag == BoolType
        || t->tag == FunctionType) {
        return true;
    } else if (t->tag == StructType) {
        size_t offset = 0;
        for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
            if (!is_packed_data(field->type)) return false;
            size_t align = type_align(field->type);
            if (align > 0 && offset % align != 0) return false;
            offset += type_size(field->type);
        }
        size_t overall_align = type_align(t);
        return overall_align == 0 || (offset % overall_align == 0);
    } else if (t->tag == EnumType) {
        size_t offset = sizeof(int32_t);
        for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
            if (!is_packed_data(tag->type)) return false;
            size_t align = type_align(tag->type);
            if (align > 0 && offset % align != 0) return false;
            offset += type_size(tag->type);
        }
        size_t overall_align = type_align(t);
        return overall_align == 0 || (offset % overall_align == 0);
    } else {
        return false;
    }
}

PUREFUNC size_t unpadded_struct_size(type_t *t) {
    if (Match(t, StructType)->opaque)
        compiler_err(NULL, NULL, NULL, "The struct type %s is opaque, so I can't get the size of it",
                     Match(t, StructType)->name);
    arg_t *fields = Match(t, StructType)->fields;
    size_t size = 0;
    size_t bit_offset = 0;
    for (arg_t *field = fields; field; field = field->next) {
        type_t *field_type = field->type;
        if (field_type->tag == BoolType) {
            bit_offset += 1;
            if (bit_offset >= 8) {
                size += 1;
                bit_offset = 0;
            }
        } else {
            if (bit_offset > 0) {
                size += 1;
                bit_offset = 0;
            }
            size_t align = type_align(field_type);
            if (align > 1 && size % align > 0) size += align - (size % align); // Padding
            size += type_size(field_type);
        }
    }
    if (bit_offset > 0) {
        size += 1;
    }
    return size;
}

PUREFUNC size_t type_size(type_t *t) {
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-default"
#endif
    switch (t->tag) {
    case UnknownType:
    case AbortType:
    case ReturnType:
    case VoidType: return 0;
    case MemoryType: errx(1, "Memory has undefined type size");
    case BoolType: return sizeof(bool);
    case ByteType: return sizeof(uint8_t);
    case CStringType: return sizeof(char *);
    case PathType: return sizeof(Path_t);
    case BigIntType: return sizeof(Int_t);
    case IntType: {
        switch (Match(t, IntType)->bits) {
        case TYPE_IBITS64: return sizeof(int64_t);
        case TYPE_IBITS32: return sizeof(int32_t);
        case TYPE_IBITS16: return sizeof(int16_t);
        case TYPE_IBITS8: return sizeof(int8_t);
        default: errx(1, "Invalid integer bit size");
        }
    }
    case NumType: return Match(t, NumType)->bits == TYPE_NBITS64 ? sizeof(double) : sizeof(float);
    case TextType: return sizeof(Text_t);
    case ListType: return sizeof(List_t);
    case TableType: return sizeof(Table_t);
    case FunctionType: return sizeof(void *);
    case ClosureType: return sizeof(struct { void *fn, *userdata; });
    case PointerType: return sizeof(void *);
    case OptionalType: {
        type_t *nonnull = Match(t, OptionalType)->type;
        switch (nonnull->tag) {
        case IntType:
            switch (Match(nonnull, IntType)->bits) {
            case TYPE_IBITS64: return sizeof(OptionalInt64_t);
            case TYPE_IBITS32: return sizeof(OptionalInt32_t);
            case TYPE_IBITS16: return sizeof(OptionalInt16_t);
            case TYPE_IBITS8: return sizeof(OptionalInt8_t);
            default: errx(1, "Invalid integer bit size");
            }
        case StructType: {
            arg_t *fields = new (arg_t, .name = "value", .type = nonnull,
                                 .next = new (arg_t, .name = "has_value", .type = Type(BoolType)));
            return type_size(Type(StructType, .fields = fields));
        }
        default: return type_size(nonnull);
        }
    }
    case StructType: {
        if (Match(t, StructType)->opaque)
            compiler_err(NULL, NULL, NULL, "The struct type %s is opaque, so I can't get the size of it",
                         Match(t, StructType)->name);
        size_t size = unpadded_struct_size(t);
        size_t align = type_align(t);
        if (size > 0 && align > 0 && (size % align) > 0) size = (size + align) - (size % align);
        return size;
    }
    case EnumType: {
        size_t max_align = 0;
        size_t max_size = 0;
        for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
            size_t align = type_align(tag->type);
            if (align > max_align) max_align = align;
            size_t size = type_size(tag->type);
            if (size > max_size) max_size = size;
        }
        size_t size = sizeof(UnknownType); // generic enum
        if (max_align > 1 && size % max_align > 0) // Padding before first union field
            size += max_align - (size % max_align);
        size += max_size;
        size_t align = MAX(__alignof__(UnknownType), max_align);
        if (size % align > 0) // Padding after union
            size += align - (size % align);
        return size;
    }
    case TypeInfoType: return sizeof(TypeInfo_t);
    case ModuleType: return 0;
    }
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
    errx(1, "This should not be reachable");
    return 0;
}

PUREFUNC size_t type_align(type_t *t) {
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-default"
#endif
    switch (t->tag) {
    case UnknownType:
    case AbortType:
    case ReturnType:
    case VoidType: return 0;
    case MemoryType: errx(1, "Memory has undefined type alignment");
    case BoolType: return __alignof__(bool);
    case ByteType: return __alignof__(uint8_t);
    case CStringType: return __alignof__(char *);
    case PathType: return __alignof__(Path_t);
    case BigIntType: return __alignof__(Int_t);
    case IntType: {
        switch (Match(t, IntType)->bits) {
        case TYPE_IBITS64: return __alignof__(int64_t);
        case TYPE_IBITS32: return __alignof__(int32_t);
        case TYPE_IBITS16: return __alignof__(int16_t);
        case TYPE_IBITS8: return __alignof__(int8_t);
        default: return 0;
        }
    }
    case NumType: return Match(t, NumType)->bits == TYPE_NBITS64 ? __alignof__(double) : __alignof__(float);
    case TextType: return __alignof__(Text_t);
    case ListType: return __alignof__(List_t);
    case TableType: return __alignof__(Table_t);
    case FunctionType: return __alignof__(void *);
    case ClosureType: return __alignof__(struct { void *fn, *userdata; });
    case PointerType: return __alignof__(void *);
    case OptionalType: {
        type_t *nonnull = Match(t, OptionalType)->type;
        switch (nonnull->tag) {
        case IntType:
            switch (Match(nonnull, IntType)->bits) {
            case TYPE_IBITS64: return __alignof__(OptionalInt64_t);
            case TYPE_IBITS32: return __alignof__(OptionalInt32_t);
            case TYPE_IBITS16: return __alignof__(OptionalInt16_t);
            case TYPE_IBITS8: return __alignof__(OptionalInt8_t);
            default: errx(1, "Invalid integer bit size");
            }
        case StructType: return MAX(1, type_align(nonnull));
        default: return type_align(nonnull);
        }
    }
    case StructType: {
        if (Match(t, StructType)->opaque)
            compiler_err(NULL, NULL, NULL, "The struct type %s is opaque, so I can't get the alignment of it",
                         Match(t, StructType)->name);
        arg_t *fields = Match(t, StructType)->fields;
        size_t align = t->tag == StructType ? 0 : sizeof(void *);
        for (arg_t *field = fields; field; field = field->next) {
            size_t field_align = type_align(field->type);
            if (field_align > align) align = field_align;
        }
        return align;
    }
    case EnumType: {
        size_t align = __alignof__(UnknownType);
        for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
            size_t tag_align = type_align(tag->type);
            if (tag_align > align) align = tag_align;
        }
        return align;
    }
    case TypeInfoType: return __alignof__(TypeInfo_t);
    case ModuleType: return 0;
    }
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
    errx(1, "This should not be reachable");
    return 0;
}

type_t *get_field_type(type_t *t, const char *field_name) {
    t = value_type(t);
    switch (t->tag) {
    case PointerType: return get_field_type(Match(t, PointerType)->pointed, field_name);
    case TextType: {
        if (Match(t, TextType)->lang && streq(field_name, "text")) return TEXT_TYPE;
        else if (streq(field_name, "length")) return INT_TYPE;
        return NULL;
    }
    case StructType: {
        DeclareMatch(struct_t, t, StructType);
        for (arg_t *field = struct_t->fields; field; field = field->next) {
            if (streq(field->name, field_name)) return field->type;
        }
        return NULL;
    }
    case EnumType: {
        DeclareMatch(e, t, EnumType);
        for (tag_t *tag = e->tags; tag; tag = tag->next) {
            if (!streq(field_name, tag->name)) continue;
            return Type(OptionalType, tag->type);
        }
        return NULL;
    }
    case TableType: {
        if (streq(field_name, "length")) return INT_TYPE;
        else if (streq(field_name, "keys")) return Type(ListType, Match(t, TableType)->key_type);
        else if (streq(field_name, "values")) return Type(ListType, Match(t, TableType)->value_type);
        else if (streq(field_name, "fallback")) return Type(OptionalType, .type = t);
        return NULL;
    }
    case ListType: {
        if (streq(field_name, "length")) return INT_TYPE;
        return NULL;
    }
    default: return NULL;
    }
}

PUREFUNC type_t *get_iterated_type(type_t *t) {
    type_t *iter_value_t = value_type(t);
    switch (iter_value_t->tag) {
    case BigIntType:
    case IntType: return iter_value_t; break;
    case ListType: return Match(iter_value_t, ListType)->item_type; break;
    case TableType: return NULL;
    case FunctionType:
    case ClosureType: {
        // Iterator function
        __typeof(iter_value_t->__data.FunctionType) *fn =
            iter_value_t->tag == ClosureType ? Match(Match(iter_value_t, ClosureType)->fn, FunctionType)
                                             : Match(iter_value_t, FunctionType);
        if (fn->args || fn->ret->tag != OptionalType) return NULL;
        return Match(fn->ret, OptionalType)->type;
    }
    default: return NULL;
    }
}

CONSTFUNC bool is_incomplete_type(type_t *t) {
    if (t == NULL) return true;
    switch (t->tag) {
    case ReturnType: return is_incomplete_type(Match(t, ReturnType)->ret);
    case OptionalType: return is_incomplete_type(Match(t, OptionalType)->type);
    case ListType: return is_incomplete_type(Match(t, ListType)->item_type);
    case TableType: {
        DeclareMatch(table, t, TableType);
        return is_incomplete_type(table->key_type) || is_incomplete_type(table->value_type);
    }
    case FunctionType: {
        DeclareMatch(fn, t, FunctionType);
        for (arg_t *arg = fn->args; arg; arg = arg->next) {
            if (arg->type == NULL || is_incomplete_type(arg->type)) return true;
        }
        return fn->ret ? is_incomplete_type(fn->ret) : false;
    }
    case ClosureType: return is_incomplete_type(Match(t, ClosureType)->fn);
    case PointerType: return is_incomplete_type(Match(t, PointerType)->pointed);
    default: return false;
    }
}

CONSTFUNC type_t *most_complete_type(type_t *t1, type_t *t2) {
    if (!t1) return t2;
    if (!t2) return t1;

    if (is_incomplete_type(t1) && is_incomplete_type(t2)) return NULL;
    else if (!is_incomplete_type(t1) && !is_incomplete_type(t2) && type_eq(t1, t2)) return t1;

    if (t1->tag != t2->tag) return NULL;

    switch (t1->tag) {
    case ReturnType: {
        type_t *ret = most_complete_type(Match(t1, ReturnType)->ret, Match(t1, ReturnType)->ret);
        return ret ? Type(ReturnType, ret) : NULL;
    }
    case OptionalType: {
        type_t *opt = most_complete_type(Match(t1, OptionalType)->type, Match(t2, OptionalType)->type);
        return opt ? Type(OptionalType, opt) : NULL;
    }
    case ListType: {
        type_t *item = most_complete_type(Match(t1, ListType)->item_type, Match(t2, ListType)->item_type);
        return item ? Type(ListType, item) : NULL;
    }
    case TableType: {
        DeclareMatch(table1, t1, TableType);
        DeclareMatch(table2, t2, TableType);
        ast_t *default_value = table1->default_value ? table1->default_value : table2->default_value;
        type_t *key = most_complete_type(table1->key_type, table2->key_type);
        type_t *value = most_complete_type(table1->value_type, table2->value_type);
        return (key && value) ? Type(TableType, key, value, table1->env, default_value) : NULL;
    }
    case FunctionType: {
        DeclareMatch(fn1, t1, FunctionType);
        DeclareMatch(fn2, t2, FunctionType);
        arg_t *args = NULL;
        for (arg_t *arg1 = fn1->args, *arg2 = fn2->args; arg1 || arg2; arg1 = arg1->next, arg2 = arg2->next) {
            if (!arg1 || !arg2) return NULL;

            type_t *arg_type = most_complete_type(arg1->type, arg2->type);
            if (!arg_type) return NULL;
            args = new (arg_t, .type = arg_type, .next = args);
        }
        REVERSE_LIST(args);
        type_t *ret = most_complete_type(fn1->ret, fn2->ret);
        return ret ? Type(FunctionType, .args = args, .ret = ret) : NULL;
    }
    case ClosureType: {
        type_t *fn = most_complete_type(Match(t1, ClosureType)->fn, Match(t1, ClosureType)->fn);
        return fn ? Type(ClosureType, fn) : NULL;
    }
    case PointerType: {
        DeclareMatch(ptr1, t1, PointerType);
        DeclareMatch(ptr2, t2, PointerType);
        if (ptr1->is_stack != ptr2->is_stack) return NULL;
        type_t *pointed = most_complete_type(ptr1->pointed, ptr2->pointed);
        return pointed ? Type(PointerType, .is_stack = ptr1->is_stack, .pointed = pointed) : NULL;
    }
    default: {
        if (is_incomplete_type(t1) || is_incomplete_type(t2)) return NULL;
        return type_eq(t1, t2) ? t1 : NULL;
    }
    }
}

type_t *_make_function_type(type_t *ret, int n, arg_t args[n]) {
    arg_t *arg_pointers = GC_MALLOC(sizeof(arg_t[n]));
    for (int i = 0; i < n; i++) {
        arg_pointers[i] = args[i];
        if (i + 1 < n) arg_pointers[i].next = &arg_pointers[i + 1];
    }
    return Type(FunctionType, .ret = ret, .args = &arg_pointers[0]);
}