dev
Download
162 lines 7.5 KiBCModified 2026-02-20 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
// This file defines how to compile optionals and null

#include "../environment.h"
#include "../naming.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/text.h"
#include "../stdlib/util.h"
#include "../typecheck.h"
#include "../types.h"
#include "compilation.h"
#include "indexing.h"

Text_t optional_into_nonnone(type_t *t, Text_t value) {
    if (t->tag == OptionalType) t = Match(t, OptionalType)->type;
    switch (t->tag) {
    case IntType:
    case ByteType: return Texts(value, ".value");
    case StructType: return Texts(value, ".value");
    default: return value;
    }
}

public
Text_t promote_to_optional(type_t *t, Text_t code) {
    if (t->tag == IntType) {
        switch (Match(t, IntType)->bits) {
        case TYPE_IBITS8: return Texts("((OptionalInt8_t){.has_value=true, .value=", code, "})");
        case TYPE_IBITS16: return Texts("((OptionalInt16_t){.has_value=true, .value=", code, "})");
        case TYPE_IBITS32: return Texts("((OptionalInt32_t){.has_value=true, .value=", code, "})");
        case TYPE_IBITS64: return Texts("((OptionalInt64_t){.has_value=true, .value=", code, "})");
        default: errx(1, "Unsupported in type: %s", Text$as_c_string(type_to_text(t)));
        }
        return code;
    } else if (t->tag == ByteType) {
        return Texts("((OptionalByte_t){.has_value=true, .value=", code, "})");
    } else if (t->tag == StructType) {
        return Texts("((", compile_type(Type(OptionalType, .type = t)), "){.has_value=true, .value=", code, "})");
    } else {
        return code;
    }
}

public
Text_t compile_none(type_t *t) {
    if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type");

    if (t->tag == OptionalType) t = Match(t, OptionalType)->type;

    if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type");

    switch (t->tag) {
    case BigIntType: return Text("NONE_INT");
    case IntType: {
        switch (Match(t, IntType)->bits) {
        case TYPE_IBITS8: return Text("NONE_INT8");
        case TYPE_IBITS16: return Text("NONE_INT16");
        case TYPE_IBITS32: return Text("NONE_INT32");
        case TYPE_IBITS64: return Text("NONE_INT64");
        default: errx(1, "Invalid integer bit size");
        }
        break;
    }
    case BoolType: return Text("NONE_BOOL");
    case ByteType: return Text("NONE_BYTE");
    case ListType: return Text("NONE_LIST");
    case TableType: return Text("NONE_TABLE");
    case TextType: return Text("NONE_TEXT");
    case CStringType: return Text("NULL");
    case PathType: return Text("NONE_PATH");
    case PointerType: return Texts("((", compile_type(t), ")NULL)");
    case ClosureType: return Text("NONE_CLOSURE");
    case NumType: return Text("nan(\"none\")");
    case StructType: return Texts("((", compile_type(Type(OptionalType, .type = t)), "){.has_value=false})");
    case EnumType: {
        env_t *enum_env = Match(t, EnumType)->env;
        return Texts("((", compile_type(t), "){", namespace_name(enum_env, enum_env->namespace, Text("none")), "})");
    }
    default: compiler_err(NULL, NULL, NULL, "none isn't implemented for this type: ", type_to_text(t));
    }
    return EMPTY_TEXT;
}

public
Text_t check_none(type_t *t, Text_t value) {
    t = Match(t, OptionalType)->type;
    // NOTE: these use statement expressions ({...;}) because some compilers
    // complain about excessive parens around equality comparisons
    switch (t->tag) {
    case PointerType:
    case FunctionType:
    case CStringType:
    case PathType: return Texts("(", value, " == NULL)");
    case BigIntType: return Texts("((", value, ").small == 0)");
    case ClosureType: return Texts("((", value, ").fn == NULL)");
    case NumType: return Texts(Match(t, NumType)->bits == TYPE_NBITS64 ? "Num$isnan(" : "Num32$isnan(", value, ")");
    case ListType: return Texts("((", value, ").data == NULL)");
    case TableType: return Texts("((", value, ").entries.data == NULL)");
    case BoolType: return Texts("((", value, ") == NONE_BOOL)");
    case TextType: return Texts("((", value, ").tag == TEXT_NONE)");
    case IntType:
    case ByteType:
    case StructType: return Texts("!(", value, ").has_value");
    case EnumType: return Texts("((", value, ").$tag == 0)");
    default: {
        print_err("Optional check not implemented for: ", type_to_text(t));
        return EMPTY_TEXT;
    }
    }
}

public
Text_t compile_non_optional(env_t *env, ast_t *ast) {
    ast_t *value = Match(ast, NonOptional)->value;
    if (value->tag == Index && Match(value, Index)->index != NULL) return compile_indexing(env, value, true);
    type_t *value_t = get_type(env, value);
    if (value_t->tag == PointerType) {
        // Dereference pointers automatically
        return compile_non_optional(env, WrapAST(ast, NonOptional, WrapAST(ast, Index, .indexed = value)));
    }
    int64_t line = get_line_number(ast->file, ast->start);
    if (value_t->tag == EnumType) {
        // For this case:
        //   enum Foo(FirstField, SecondField(msg:Text))
        //   e := ...
        //   e!
        // We desugar into `e.FirstField!` using the first enum field
        tag_t *first_tag = Match(value_t, EnumType)->tags;
        if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum");
        return compile_non_optional(
            env, WrapAST(ast, NonOptional, WrapAST(value, FieldAccess, .fielded = value, .field = first_tag->name)));
    } else if (value->tag == FieldAccess
               && value_type(get_type(env, Match(value, FieldAccess)->fielded))->tag == EnumType) {
        type_t *enum_t = value_type(get_type(env, Match(value, FieldAccess)->fielded));
        DeclareMatch(e, enum_t, EnumType);
        DeclareMatch(f, value, FieldAccess);
        for (tag_t *tag = e->tags; tag; tag = tag->next) {
            if (streq(f->field, tag->name)) {
                Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name));
                return Texts(
                    "({ ", compile_declaration(enum_t, Text("_test_enum")), " = ",
                    compile_to_pointer_depth(env, f->fielded, 0, true), ";",
                    "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", "fail_source(",
                    quoted_str(f->fielded->file->filename), ", ", (int64_t)(f->fielded->start - f->fielded->file->text),
                    ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ",
                    "Text$concat(Text(\"This was expected to be ", tag->name, ", but it was: \"), ",
                    expr_as_text(Text("_test_enum"), enum_t, Text("false")), ", Text(\"\\n\")));\n}\n",
                    compile_maybe_incref(
                        env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type),
                    "; })");
            }
        }
        code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t));
    } else {
        Text_t value_code = compile(env, value);
        return Texts("({ ", compile_declaration(value_t, Text("opt")), " = ", value_code, "; ", "if unlikely (",
                     check_none(value_t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(",
                     quoted_str(value->file->filename), ", ", (int64_t)(value->start - value->file->text), ", ",
                     (int64_t)(value->end - value->file->text), ", ",
                     "Text(\"This was expected to be a value, but it's `none`\\n\"));\n",
                     optional_into_nonnone(value_t, Text("opt")), "; })");
    }
}