diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2024-09-11 01:31:31 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2024-09-11 01:31:31 -0400 |
| commit | 7126755275f12e6278031e78ff33f65801b133dd (patch) | |
| tree | 7f43f3449eb7bb69b0879dd41eb174e89fdc34cc | |
| parent | 89234e34e292861fccb8e5bdbefc695a7e443eea (diff) | |
Add optional types
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | ast.c | 5 | ||||
| -rw-r--r-- | ast.h | 6 | ||||
| -rw-r--r-- | builtins/functions.c | 11 | ||||
| -rw-r--r-- | builtins/optionals.c | 49 | ||||
| -rw-r--r-- | builtins/optionals.h | 25 | ||||
| -rw-r--r-- | builtins/pointer.c | 9 | ||||
| -rw-r--r-- | builtins/tomo.h | 1 | ||||
| -rw-r--r-- | builtins/types.c | 1 | ||||
| -rw-r--r-- | builtins/types.h | 13 | ||||
| -rw-r--r-- | compile.c | 131 | ||||
| -rw-r--r-- | environment.c | 2 | ||||
| -rw-r--r-- | parse.c | 63 | ||||
| -rw-r--r-- | repl.c | 4 | ||||
| -rw-r--r-- | test/arrays.tm | 8 | ||||
| -rw-r--r-- | test/optionals.tm | 128 | ||||
| -rw-r--r-- | test/tables.tm | 2 | ||||
| -rw-r--r-- | typecheck.c | 298 | ||||
| -rw-r--r-- | types.c | 163 | ||||
| -rw-r--r-- | types.h | 10 |
20 files changed, 557 insertions, 375 deletions
@@ -30,7 +30,8 @@ CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m<flags...>\033[m')" LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl BUILTIN_OBJS=builtins/siphash.o builtins/array.o builtins/bool.o builtins/channel.o builtins/nums.o builtins/functions.o builtins/integers.o \ builtins/pointer.o builtins/memory.o builtins/text.o builtins/thread.o builtins/c_string.o builtins/table.o \ - builtins/types.o builtins/util.o builtins/files.o builtins/range.o builtins/shell.o builtins/path.o builtins/nextline.o + builtins/types.o builtins/util.o builtins/files.o builtins/range.o builtins/shell.o builtins/path.o builtins/nextline.o \ + builtins/optionals.o TESTS=$(patsubst %.tm,%.tm.testresult,$(wildcard test/*.tm)) all: libtomo.so tomo @@ -168,13 +168,14 @@ CORD type_ast_to_xml(type_ast_t *t) #define T(type, ...) case type: { auto data = t->__data.type; (void)data; return CORD_asprintf(__VA_ARGS__); } T(UnknownTypeAST, "<UnknownType/>") T(VarTypeAST, "%s", data.name) - T(PointerTypeAST, "<PointerType is_optional=\"%s\" is_stack=\"%s\" is_readonly=\"%s\">%r</PointerType>", - data.is_optional ? "yes" : "no", data.is_stack ? "yes" : "no", data.is_readonly ? "yes" : "no", type_ast_to_xml(data.pointed)) + T(PointerTypeAST, "<PointerType is_stack=\"%s\" is_readonly=\"%s\">%r</PointerType>", + data.is_stack ? "yes" : "no", data.is_readonly ? "yes" : "no", type_ast_to_xml(data.pointed)) T(ArrayTypeAST, "<ArrayType>%r</ArrayType>", type_ast_to_xml(data.item)) T(SetTypeAST, "<TableType>%r</TableType>", type_ast_to_xml(data.item)) T(ChannelTypeAST, "<ChannelType>%r</ChannelType>", type_ast_to_xml(data.item)) T(TableTypeAST, "<TableType>%r %r</TableType>", type_ast_to_xml(data.key), type_ast_to_xml(data.value)) T(FunctionTypeAST, "<FunctionType>%r %r</FunctionType>", arg_list_to_xml(data.args), type_ast_to_xml(data.ret)) + T(OptionalTypeAST, "<OptionalType>%r</OptionalType>", data.type) #undef T default: return CORD_EMPTY; } @@ -61,6 +61,7 @@ typedef enum { ChannelTypeAST, TableTypeAST, FunctionTypeAST, + OptionalTypeAST, } type_ast_e; typedef struct tag_ast_s { @@ -83,7 +84,7 @@ struct type_ast_s { } VarTypeAST; struct { type_ast_t *pointed; - bool is_optional:1, is_stack:1, is_readonly:1; + bool is_stack:1, is_readonly:1; } PointerTypeAST; struct { type_ast_t *item; @@ -98,6 +99,9 @@ struct type_ast_s { arg_ast_t *args; type_ast_t *ret; } FunctionTypeAST; + struct { + type_ast_t *type; + } OptionalTypeAST; } __data; }; diff --git a/builtins/functions.c b/builtins/functions.c index b4e1a5cc..d6778d4c 100644 --- a/builtins/functions.c +++ b/builtins/functions.c @@ -16,6 +16,7 @@ #include "files.h" #include "functions.h" #include "integers.h" +#include "optionals.h" #include "pointer.h" #include "siphash.h" #include "string.h" @@ -113,6 +114,9 @@ PUREFUNC public uint64_t generic_hash(const void *obj, const TypeInfo *type) case ArrayInfo: return Array$hash(obj, type); case ChannelInfo: return Channel$hash((const channel_t**)obj, type); case TableInfo: return Table$hash(obj, type); + case OptionalInfo: { + errx(1, "Optional hash not implemented"); + } case EmptyStruct: return 0; case CustomInfo: if (!type->CustomInfo.hash) @@ -135,6 +139,9 @@ PUREFUNC public int32_t generic_compare(const void *x, const void *y, const Type case ArrayInfo: return Array$compare(x, y, type); case ChannelInfo: return Channel$compare((const channel_t**)x, (const channel_t**)y, type); case TableInfo: return Table$compare(x, y, type); + case OptionalInfo: { + errx(1, "Optional compare not implemented"); + } case EmptyStruct: return 0; case CustomInfo: if (!type->CustomInfo.compare) @@ -157,6 +164,9 @@ PUREFUNC public bool generic_equal(const void *x, const void *y, const TypeInfo case ChannelInfo: return Channel$equal((const channel_t**)x, (const channel_t**)y, type); case TableInfo: return Table$equal(x, y, type); case EmptyStruct: return true; + case OptionalInfo: { + errx(1, "Optional equal not implemented"); + } case CustomInfo: if (!type->CustomInfo.equal) goto use_generic_compare; @@ -177,6 +187,7 @@ public Text_t generic_as_text(const void *obj, bool colorize, const TypeInfo *ty case ChannelInfo: return Channel$as_text((const channel_t**)obj, colorize, type); case TableInfo: return Table$as_text(obj, colorize, type); case TypeInfoInfo: return Type$as_text(obj, colorize, type); + case OptionalInfo: return Optional$as_text(obj, colorize, type); case EmptyStruct: return colorize ? Text$concat(Text("\x1b[0;1m"), Text$from_str(type->EmptyStruct.name), Text("\x1b[m()")) : Text$concat(Text$from_str(type->EmptyStruct.name), Text("()")); diff --git a/builtins/optionals.c b/builtins/optionals.c new file mode 100644 index 00000000..869ee4cd --- /dev/null +++ b/builtins/optionals.c @@ -0,0 +1,49 @@ +// Optional types + +#include "bool.h" +#include "datatypes.h" +#include "text.h" +#include "util.h" + +public const Array_t NULL_ARRAY = {.length=-1}; +public const Bool_t NULL_BOOL = -1; +public const Int_t NULL_INT = {.small=0}; +public const Table_t NULL_TABLE = {.entries.length=-1}; +public const closure_t NULL_CLOSURE = {.fn=NULL}; +public const Text_t NULL_TEXT = {.length=-1}; + +static inline bool is_null(const void *obj, const TypeInfo *non_optional_type) +{ + if (non_optional_type == &Int$info) + return ((Int_t*)obj)->small == 0; + else if (non_optional_type == &Bool$info) + return *((Bool_t*)obj) == NULL_BOOL; + else if (non_optional_type == &Num$info) + return isnan(*((Num_t*)obj)); + + switch (non_optional_type->tag) { + case PointerInfo: return *(void**)obj == NULL; + case TextInfo: return ((Text_t*)obj)->length < 0; + case ArrayInfo: return ((Array_t*)obj)->length < 0; + case TableInfo: return ((Table_t*)obj)->entries.length < 0; + case FunctionInfo: return *(void**)obj == NULL; + default: { + Text_t t = generic_as_text(NULL, false, non_optional_type); + errx(1, "is_null() not implemented for: %k", &t); + } + } +} + +#pragma GCC diagnostic ignored "-Wstack-protector" +public Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo *type) +{ + if (!obj) + return Text$concat(generic_as_text(obj, colorize, type->OptionalInfo.type), Text("?")); + + if (is_null(obj, type->OptionalInfo.type)) + return Text$concat(colorize ? Text("\x1b[31m!") : Text("!"), generic_as_text(NULL, false, type->OptionalInfo.type), + colorize ? Text("\x1b[m") : Text("")); + return Text$concat(generic_as_text(obj, colorize, type->OptionalInfo.type), colorize ? Text("\x1b[33m?\x1b[m") : Text("?")); +} + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1 diff --git a/builtins/optionals.h b/builtins/optionals.h new file mode 100644 index 00000000..a0b9b0e0 --- /dev/null +++ b/builtins/optionals.h @@ -0,0 +1,25 @@ +#pragma once + +// Optional types + +#include <gc/cord.h> +#include <stdbool.h> +#include <stdint.h> + +#include "types.h" +#include "util.h" + +#define Bool_t bool +#define yes (Bool_t)true +#define no (Bool_t)false + +extern const Bool_t NULL_BOOL; +extern const Table_t NULL_TABLE; +extern const Array_t NULL_ARRAY; +extern const Int_t NULL_INT; +extern const closure_t NULL_CLOSURE; +extern const Text_t NULL_TEXT; + +Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo *type); + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/pointer.c b/builtins/pointer.c index 0d89e8a6..9067ebd7 100644 --- a/builtins/pointer.c +++ b/builtins/pointer.c @@ -27,10 +27,6 @@ public Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo *type text = Text$concat(Text("\x1b[34;1m"), Text$from_str(ptr_info.sigil), typename, Text("\x1b[m")); else text = Text$concat(Text$from_str(ptr_info.sigil), typename); - - if (ptr_info.is_optional) - text = Text$concat(text, Text("?")); - return text; } const void *ptr = *(const void**)x; @@ -55,8 +51,6 @@ public Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo *type Text(".."), Int32$as_text(&depth, false, &Int32$info), colorize ? Text("\x1b[m") : Text("")); - if (ptr_info.is_optional) - text = Text$concat(text, colorize ? Text("\x1b[34;1m?\x1b[m") : Text("?")); return text; } } @@ -73,9 +67,6 @@ public Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo *type text = Text$concat(Text("\x1b[34;1m"), Text$from_str(ptr_info.sigil), Text("\x1b[m"), pointed); else text = Text$concat(Text$from_str(ptr_info.sigil), pointed); - - if (ptr_info.is_optional) - text = Text$concat(text, Text("?")); return text; } diff --git a/builtins/tomo.h b/builtins/tomo.h index 29f71f71..4a52da7c 100644 --- a/builtins/tomo.h +++ b/builtins/tomo.h @@ -21,6 +21,7 @@ #include "macros.h" #include "memory.h" #include "nums.h" +#include "optionals.h" #include "path.h" #include "pointer.h" #include "range.h" diff --git a/builtins/types.c b/builtins/types.c index 6d9e2408..79f65f71 100644 --- a/builtins/types.c +++ b/builtins/types.c @@ -39,6 +39,7 @@ public Text_t Func$as_text(const void *fn, bool colorize, const TypeInfo *type) { (void)fn; Text_t text = Text$from_str(type->FunctionInfo.type_str); + if (fn) text = Text$concat(text, Text(": ...")); if (fn && colorize) text = Text$concat(Text("\x1b[32;1m"), text, Text("\x1b[m")); return text; diff --git a/builtins/types.h b/builtins/types.h index f06730a7..a6cc40c6 100644 --- a/builtins/types.h +++ b/builtins/types.h @@ -17,7 +17,8 @@ typedef Text_t (*text_fn_t)(const void*, bool, const struct TypeInfo*); typedef struct TypeInfo { int64_t size, align; struct { // Anonymous tagged union for convenience - enum { CustomInfo, PointerInfo, TextInfo, ArrayInfo, ChannelInfo, TableInfo, FunctionInfo, TypeInfoInfo, OpaqueInfo, EmptyStruct } tag; + enum { CustomInfo, PointerInfo, TextInfo, ArrayInfo, ChannelInfo, TableInfo, FunctionInfo, + OptionalInfo, TypeInfoInfo, OpaqueInfo, EmptyStruct } tag; union { struct { equal_fn_t equal; @@ -27,7 +28,6 @@ typedef struct TypeInfo { } CustomInfo; struct { const char *sigil; - bool is_optional; const struct TypeInfo *pointed; } PointerInfo; struct { @@ -45,6 +45,9 @@ typedef struct TypeInfo { struct { const char *type_str; } TypeInfoInfo; + struct { + const struct TypeInfo *type; + } OptionalInfo; #pragma GCC diagnostic ignored "-Wpedantic" struct {} OpaqueInfo; struct { @@ -54,8 +57,8 @@ typedef struct TypeInfo { }; } TypeInfo; -#define Pointer$info(sigil_expr, pointed_info, opt) &((TypeInfo){.size=sizeof(void*), .align=__alignof__(void*), \ - .tag=PointerInfo, .PointerInfo={.sigil=sigil_expr, .pointed=pointed_info, .is_optional=opt}}) +#define Pointer$info(sigil_expr, pointed_info) &((TypeInfo){.size=sizeof(void*), .align=__alignof__(void*), \ + .tag=PointerInfo, .PointerInfo={.sigil=sigil_expr, .pointed=pointed_info}}) #define Array$info(item_info) &((TypeInfo){.size=sizeof(Array_t), .align=__alignof__(Array_t), \ .tag=ArrayInfo, .ArrayInfo.item=item_info}) #define Set$info(item_info) &((TypeInfo){.size=sizeof(Table_t), .align=__alignof__(Table_t), \ @@ -70,6 +73,8 @@ typedef struct TypeInfo { .tag=FunctionInfo, .FunctionInfo.type_str=typestr}) #define TypeInfo$info(typestr) &((TypeInfo){.size=sizeof(TypeInfo), .align=__alignof__(TypeInfo), \ .tag=TypeInfoInfo, .TypeInfoInfo.type_str=typestr}) +#define Optional$info(t) &((TypeInfo){.size=(t)->size, .align=(t)->align, \ + .tag=OptionalInfo, .OptionalInfo.type=t}) extern const TypeInfo TypeInfo$info; extern const TypeInfo Void$info; @@ -55,13 +55,17 @@ static bool promote(env_t *env, CORD *code, type_t *actual, type_t *needed) } // Automatic dereferencing: - if (actual->tag == PointerType && !Match(actual, PointerType)->is_optional + if (actual->tag == PointerType && can_promote(Match(actual, PointerType)->pointed, needed)) { *code = CORD_all("*(", *code, ")"); return promote(env, code, Match(actual, PointerType)->pointed, needed); } - // Optional and stack ref promotion: + // Optional promotion: + if (needed->tag == OptionalType && type_eq(actual, Match(needed, OptionalType)->type)) + return true; + + // Stack ref promotion: if (actual->tag == PointerType && needed->tag == PointerType) return true; @@ -194,6 +198,12 @@ CORD compile_type(type_t *t) auto e = Match(t, EnumType); return CORD_all(namespace_prefix(e->env->libname, e->env->namespace->parent), e->name, "_t"); } + case OptionalType: { + type_t *nonnull = Match(t, OptionalType)->type; + if (!supports_optionals(nonnull)) + compiler_err(NULL, NULL, NULL, "Optional types are not supported for: %T", t); + return compile_type(nonnull); + } case TypeInfoType: return "TypeInfo"; default: compiler_err(NULL, NULL, NULL, "Compiling type is not implemented for type with tag %d", t->tag); } @@ -213,11 +223,12 @@ static CORD compile_lvalue(env_t *env, ast_t *ast) if (ast->tag == Index) { auto index = Match(ast, Index); type_t *container_t = get_type(env, index->indexed); - if (!index->index && container_t->tag == PointerType) { - if (Match(container_t, PointerType)->is_optional) - code_err(index->indexed, "This pointer might be null, so it can't be safely used as an assignment target"); + if (container_t->tag == OptionalType) + code_err(index->indexed, "This value might be null, so it can't be safely used as an assignment target"); + + if (!index->index && container_t->tag == PointerType) return compile(env, ast); - } + container_t = value_type(container_t); if (container_t->tag == ArrayType) { CORD target_code = compile_to_pointer_depth(env, index->indexed, 1, false); @@ -269,6 +280,36 @@ static CORD compile_inline_block(env_t *env, ast_t *ast) return code; } +static CORD compile_optional_into_nonnull(env_t *env, binding_t *b) +{ + (void)env; + // TODO: implement + return (b->code); +} + +static CORD compile_optional_check(env_t *env, ast_t *ast) +{ + type_t *t = get_type(env, ast); + t = Match(t, OptionalType)->type; + if (t->tag == PointerType || t->tag == FunctionType) + return CORD_all("(", compile(env, ast), " != NULL)"); + else if (t->tag == BigIntType) + return CORD_all("((", compile(env, ast), ").small != 0)"); + else if (t->tag == ClosureType) + return CORD_all("((", compile(env, ast), ").fn != NULL)"); + else if (t->tag == NumType) + return CORD_all("!isnan(", compile(env, ast), ")"); + else if (t->tag == ArrayType) + return CORD_all("((", compile(env, ast), ").length >= 0)"); + else if (t->tag == TableType || t->tag == SetType) + return CORD_all("((", compile(env, ast), ").entries.length >= 0)"); + else if (t->tag == BoolType) + return CORD_all("((", compile(env, ast), ") != NULL_BOOL)"); + else if (t->tag == TextType) + return CORD_all("((", compile(env, ast), ").length >= 0)"); + errx(1, "Optional check not implemented for: %T", t); +} + CORD compile_statement(env_t *env, ast_t *ast) { switch (ast->tag) { @@ -280,21 +321,6 @@ CORD compile_statement(env_t *env, ast_t *ast) auto when = Match(ast, When); type_t *subject_t = get_type(env, when->subject); - if (subject_t->tag == PointerType) { - ast_t *var = when->clauses->args->ast; - CORD var_code = compile(env, var); - env_t *non_null_scope = fresh_scope(env); - auto ptr = Match(subject_t, PointerType); - type_t *non_optional_t = Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack, - .is_readonly=ptr->is_readonly, .is_optional=false); - set_binding(non_null_scope, Match(var, Var)->name, new(binding_t, .type=non_optional_t, .code=var_code)); - return CORD_all( - "{\n", - compile_declaration(subject_t, var_code), " = ", compile(env, when->subject), ";\n" - "if (", var_code, ")\n", compile_statement(non_null_scope, when->clauses->body), - "\nelse\n", compile_statement(env, when->else_body), "\n}"); - } - auto enum_t = Match(subject_t, EnumType); CORD code = CORD_all("{ ", compile_type(subject_t), " subject = ", compile(env, when->subject), ";\n" "switch (subject.tag) {"); @@ -742,7 +768,7 @@ CORD compile_statement(env_t *env, ast_t *ast) "static Table_t cache = {};\n", compile_type(args_t), " args = {", all_args, "};\n" "const TypeInfo *table_type = Table$info(", compile_type_info(env, args_t), ", ", compile_type_info(env, ret_t), ");\n", - compile_declaration(Type(PointerType, .pointed=ret_t, .is_optional=true), "cached"), " = Table$get_raw(cache, &args, table_type);\n" + compile_declaration(Type(PointerType, .pointed=ret_t), "cached"), " = Table$get_raw(cache, &args, table_type);\n" "if (cached) return *cached;\n", compile_declaration(ret_t, "ret"), " = ", name, "$uncached(", all_args, ");\n", pop_code, @@ -1172,19 +1198,32 @@ CORD compile_statement(env_t *env, ast_t *ast) auto if_ = Match(ast, If); type_t *cond_t = get_type(env, if_->condition); if (cond_t->tag == PointerType) { - if (!Match(cond_t, PointerType)->is_optional) - code_err(if_->condition, "This pointer will always be non-null, so it should not be used in a conditional."); - } else if (cond_t->tag != BoolType && cond_t->tag != TextType) { + code_err(if_->condition, "This pointer will always be non-null, so it should not be used in a conditional."); + } else if (cond_t->tag != BoolType && cond_t->tag != TextType && cond_t->tag != OptionalType) { code_err(if_->condition, "Only boolean values, optional pointers, and text can be used in conditionals (this is a %T)", cond_t); } + env_t *truthy_scope = env; CORD condition; - if (cond_t->tag == TextType) + if (cond_t->tag == TextType) { condition = CORD_all("(", compile(env, if_->condition), ").length"); - else + } else if (cond_t->tag == OptionalType) { + if (if_->condition->tag == Var) { + truthy_scope = fresh_scope(env); + const char *varname = Match(if_->condition, Var)->name; + binding_t *b = get_binding(env, varname); + binding_t *nonnull_b = new(binding_t); + *nonnull_b = *b; + nonnull_b->type = Match(cond_t, OptionalType)->type; + nonnull_b->code = compile_optional_into_nonnull(env, b); + set_binding(truthy_scope, varname, nonnull_b); + } + condition = compile_optional_check(env, if_->condition); + } else { condition = compile(env, if_->condition); + } - CORD code = CORD_all("if (", condition, ")", compile_statement(env, if_->body)); + CORD code = CORD_all("if (", condition, ")", compile_statement(truthy_scope, if_->body)); if (if_->else_body) code = CORD_all(code, "\nelse ", compile_statement(env, if_->else_body)); return code; @@ -1280,6 +1319,7 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color) case TableType: return CORD_asprintf("Table$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case FunctionType: case ClosureType: return CORD_asprintf("Func$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case PointerType: return CORD_asprintf("Pointer$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); + case OptionalType: return CORD_asprintf("Optional$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case StructType: case EnumType: return CORD_asprintf("(%r)->CustomInfo.as_text(stack(%r), %r, %r)", compile_type_info(env, t), expr, color, compile_type_info(env, t)); @@ -1317,8 +1357,6 @@ CORD compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool ++depth; } else { auto ptr = Match(t, PointerType); - if (ptr->is_optional) - code_err(ast, "You can't dereference this value, since it's not guaranteed to be non-null"); val = CORD_all("*(", val, ")"); t = ptr->pointed; --depth; @@ -1327,8 +1365,6 @@ CORD compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool while (t->tag == PointerType) { auto ptr = Match(t, PointerType); - if (ptr->is_optional) - code_err(ast, "You can't dereference this value, since it's not guaranteed to be non-null"); t = ptr->pointed; } @@ -1594,7 +1630,18 @@ CORD compile(env_t *env, ast_t *ast) switch (ast->tag) { case Nil: { type_t *t = parse_type_ast(env, Match(ast, Nil)->type); - return CORD_all("((", compile_type(t), ")NULL)"); + switch (t->tag) { + case BigIntType: return "NULL_INT"; + case BoolType: return "NULL_BOOL"; + case ArrayType: return "NULL_ARRAY"; + case TableType: return "NULL_TABLE"; + case SetType: return "NULL_TABLE"; + case TextType: return "NULL_TEXT"; + case PointerType: return CORD_all("((", compile_type(t), ")NULL)"); + case ClosureType: return "NULL_CLOSURE"; + case NumType: return "nan(\"null\")"; + default: code_err(ast, "Nil isn't implemented for this type: %T", t); + } } case Bool: return Match(ast, Bool)->b ? "yes" : "no"; case Var: { @@ -1668,8 +1715,8 @@ CORD compile(env_t *env, ast_t *ast) return CORD_all("((", compile(env, value), ").entries.length == 0)"); else if (t->tag == TextType) return CORD_all("(", compile(env, value), " == CORD_EMPTY)"); - else if (t->tag == PointerType && Match(t, PointerType)->is_optional) - return CORD_all("(", compile(env, value), " == NULL)"); + else if (t->tag == OptionalType) + return CORD_all("!(", compile_optional_check(env, value), ")"); code_err(ast, "I don't know how to negate values of type %T", t); } @@ -2058,7 +2105,7 @@ CORD compile(env_t *env, ast_t *ast) if (!table->entries) { CORD code = "((Table_t){"; if (table->fallback) - code = CORD_all(code, ".fallback=", compile(env, table->fallback),","); + code = CORD_all(code, ".fallback=heap(", compile(env, table->fallback),")"); return CORD_cat(code, "})"); } @@ -2883,7 +2930,7 @@ CORD compile(env_t *env, ast_t *ast) "values.data += ", CORD_asprintf("%zu", offset), ";\n" "values; })"); } else if (streq(f->field, "fallback")) { - return CORD_all("(", compile_to_pointer_depth(env, f->fielded, 0, false), ").fallback"); + return CORD_all("({ Table_t *_fallback = (", compile_to_pointer_depth(env, f->fielded, 0, false), ").fallback; _fallback ? *_fallback : NULL_TABLE; })"); } code_err(ast, "There is no '%s' field on tables", f->field); } @@ -2903,8 +2950,6 @@ CORD compile(env_t *env, ast_t *ast) if (indexed_type->tag != PointerType) code_err(ast, "Only pointers can use the '[]' operator to dereference the entire value."); auto ptr = Match(indexed_type, PointerType); - if (ptr->is_optional) - code_err(ast, "This pointer is potentially null, so it can't be safely dereferenced"); if (ptr->pointed->tag == ArrayType) { return CORD_all("({ Array_t *arr = ", compile(env, indexing->indexed), "; ARRAY_INCREF(*arr); *arr; })"); } else if (ptr->pointed->tag == TableType || ptr->pointed->tag == SetType) { @@ -3083,10 +3128,9 @@ CORD compile_type_info(env_t *env, type_t *t) auto ptr = Match(t, PointerType); CORD sigil = ptr->is_stack ? "&" : "@"; if (ptr->is_readonly) sigil = CORD_cat(sigil, "%"); - return CORD_asprintf("Pointer$info(%r, %r, %s)", + return CORD_asprintf("Pointer$info(%r, %r)", CORD_quoted(sigil), - compile_type_info(env, ptr->pointed), - ptr->is_optional ? "yes" : "no"); + compile_type_info(env, ptr->pointed)); } case FunctionType: { return CORD_asprintf("Function$info(%r)", CORD_quoted(type_to_cord(t))); @@ -3094,6 +3138,9 @@ CORD compile_type_info(env_t *env, type_t *t) case ClosureType: { return CORD_asprintf("Closure$info(%r)", CORD_quoted(type_to_cord(t))); } + case OptionalType: { + return CORD_asprintf("Optional$info(%r)", compile_type_info(env, Match(t, OptionalType)->type)); + } case TypeInfoType: return "&TypeInfo$info"; case MemoryType: return "&Memory$info"; case VoidType: return "&Void$info"; diff --git a/environment.c b/environment.c index 94401ec9..10b95f8e 100644 --- a/environment.c +++ b/environment.c @@ -287,7 +287,7 @@ env_t *new_compilation_unit(CORD *libname) )}, {"Shell", Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell")), "Shell_t", "Shell$info", TypedArray(ns_entry_t, {"escape_text", "Shell$escape_text", "func(text:Text)->Shell"}, - {"run", "Shell$run", "func(command:Shell, status=!&Int32?)->Text"}, + {"run", "Shell$run", "func(command:Shell, status=!&Int32)->Text"}, )}, {"Text", TEXT_TYPE, "Text_t", "Text$info", TypedArray(ns_entry_t, {"as_c_string", "Text$as_c_string", "func(text:Text)->CString"}, @@ -84,6 +84,7 @@ static ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *lhs); static ast_t *parse_optional_conditional_suffix(parse_ctx_t *ctx, ast_t *lhs); static ast_t *parse_optional_suffix(parse_ctx_t *ctx, ast_t *lhs); static arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos, bool allow_unnamed); +static type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos); static PARSER(parse_array); static PARSER(parse_block); @@ -576,10 +577,14 @@ type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos) { spaces(&pos); bool is_readonly = match(&pos, "%"); spaces(&pos); - type_ast_t *type = expect(ctx, start, &pos, parse_type, + type_ast_t *type = expect(ctx, start, &pos, parse_non_optional_type, "I couldn't parse a pointer type after this point"); - bool optional = match(&pos, "?"); - return NewTypeAST(ctx->file, start, pos, PointerTypeAST, .pointed=type, .is_optional=optional, .is_stack=is_stack, .is_readonly=is_readonly); + type_ast_t *ptr_type = NewTypeAST(ctx->file, start, pos, PointerTypeAST, .pointed=type, .is_stack=is_stack, .is_readonly=is_readonly); + spaces(&pos); + if (match(&pos, "?")) + return NewTypeAST(ctx->file, start, pos, OptionalTypeAST, .type=ptr_type); + else + return ptr_type; } type_ast_t *parse_type_name(parse_ctx_t *ctx, const char *pos) { @@ -598,7 +603,7 @@ type_ast_t *parse_type_name(parse_ctx_t *ctx, const char *pos) { return NewTypeAST(ctx->file, start, pos, VarTypeAST, .name=id); } -type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos) { +type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos) { const char *start = pos; type_ast_t *type = NULL; bool success = (false @@ -620,10 +625,18 @@ type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos) { type->end = pos; } - if (!type) return NULL; + return type; +} +type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + type_ast_t *type = parse_non_optional_type(ctx, pos); pos = type->end; - return type; + spaces(&pos); + if (match(&pos, "?")) + return NewTypeAST(ctx->file, start, pos, OptionalTypeAST, .type=type); + else + return type; } PARSER(parse_num) { @@ -749,7 +762,7 @@ PARSER(parse_table) { return NULL; value_type = expect(ctx, pos-1, &pos, parse_type, "I couldn't parse a value type for this table"); whitespace(&pos); - match(&pos, ";"); + match(&pos, ","); } for (;;) { @@ -891,6 +904,7 @@ PARSER(parse_reduction) { || (new_term=parse_field_suffix(ctx, key)) || (new_term=parse_method_call_suffix(ctx, key)) || (new_term=parse_fncall_suffix(ctx, key)) + || (new_term=parse_optional_suffix(ctx, key)) ); if (progress) key = new_term; } @@ -1134,20 +1148,44 @@ PARSER(parse_heap_alloc) { const char *start = pos; if (!match(&pos, "@")) return NULL; spaces(&pos); - ast_t *val = expect(ctx, start, &pos, parse_term, "I expected an expression for this '@'"); + ast_t *val = expect(ctx, start, &pos, parse_term_no_suffix, "I expected an expression for this '@'"); + + for (;;) { + ast_t *new_term; + if ((new_term=parse_index_suffix(ctx, val)) + || (new_term=parse_fncall_suffix(ctx, val)) + || (new_term=parse_field_suffix(ctx, val))) { + val = new_term; + } else break; + } + pos = val->end; + ast_t *ast = NewAST(ctx->file, start, pos, HeapAllocate, .value=val); ast_t *optional = parse_optional_suffix(ctx, ast); - return optional ? optional : ast; + if (optional) ast = optional; + return ast; } PARSER(parse_stack_reference) { const char *start = pos; if (!match(&pos, "&")) return NULL; spaces(&pos); - ast_t *val = expect(ctx, start, &pos, parse_term, "I expected an expression for this '&'"); + ast_t *val = expect(ctx, start, &pos, parse_term_no_suffix, "I expected an expression for this '&'"); + + for (;;) { + ast_t *new_term; + if ((new_term=parse_index_suffix(ctx, val)) + || (new_term=parse_fncall_suffix(ctx, val)) + || (new_term=parse_field_suffix(ctx, val))) { + val = new_term; + } else break; + } + pos = val->end; + ast_t *ast = NewAST(ctx->file, start, pos, StackReference, .value=val); ast_t *optional = parse_optional_suffix(ctx, ast); - return optional ? optional : ast; + if (optional) ast = optional; + return ast; } PARSER(parse_not) { @@ -1425,6 +1463,7 @@ PARSER(parse_lambda) { spaces(&pos); expect_closing(ctx, &pos, ")", "I was expecting a ')' to finish this anonymous function's arguments"); ast_t *body = optional(ctx, &pos, parse_block); + if (!body) body = NewAST(ctx->file, pos, pos, Block, .statements=NULL); return NewAST(ctx->file, start, pos, Lambda, .id=ctx->next_lambda_id++, .args=args, .body=body); } @@ -1488,6 +1527,7 @@ PARSER(parse_term) { || (new_term=parse_field_suffix(ctx, term)) || (new_term=parse_method_call_suffix(ctx, term)) || (new_term=parse_fncall_suffix(ctx, term)) + || (new_term=parse_optional_suffix(ctx, term)) ); if (progress) term = new_term; } @@ -1630,6 +1670,7 @@ static ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightn || (new_term=parse_field_suffix(ctx, key)) || (new_term=parse_method_call_suffix(ctx, key)) || (new_term=parse_fncall_suffix(ctx, key)) + || (new_term=parse_optional_suffix(ctx, key)) ); if (progress) key = new_term; } @@ -139,7 +139,7 @@ const TypeInfo *type_to_type_info(type_t *t) if (ptr->is_readonly) sigil = CORD_cat(sigil, "%"); const TypeInfo *pointed_info = type_to_type_info(ptr->pointed); const TypeInfo pointer_info = {.size=sizeof(void*), .align=__alignof__(void*), - .tag=PointerInfo, .PointerInfo={.sigil=sigil, .pointed=pointed_info, .is_optional=ptr->is_optional}}; + .tag=PointerInfo, .PointerInfo={.sigil=sigil, .pointed=pointed_info}}; return memcpy(GC_MALLOC(sizeof(TypeInfo)), &pointer_info, sizeof(TypeInfo)); } default: errx(1, "Unsupported type: %T", t); @@ -494,8 +494,6 @@ void eval(env_t *env, ast_t *ast, void *dest) } case PointerType: { auto ptr = Match(indexed_t, PointerType); - if (ptr->is_optional) - repl_err(ast, "You can't dereference an optional pointer because it might be null"); size_t pointed_size = type_size(ptr->pointed); void *pointer; eval(env, index->indexed, &pointer); diff --git a/test/arrays.tm b/test/arrays.tm index 16f411a2..5856e0a4 100644 --- a/test/arrays.tm +++ b/test/arrays.tm @@ -168,8 +168,8 @@ func main(): >> [10, 20, 30]:find(999) = 0 - >> [10, 20]:first(func(i:&Int): i:is_prime()) - = !Int - >> [4, 5, 6]:first(func(i:&Int): i:is_prime()) - = @%5? + # >> [10, 20]:first(func(i:&Int): i:is_prime()) + # = !Int + # >> [4, 5, 6]:first(func(i:&Int): i:is_prime()) + # = @%5? diff --git a/test/optionals.tm b/test/optionals.tm index 5f6fb78a..d8cbf741 100644 --- a/test/optionals.tm +++ b/test/optionals.tm @@ -1,15 +1,123 @@ +func maybe_int(should_i:Bool)->Int?: + if should_i: + return 123 + else: + return !Int -func main(): - >> opt := @5? - when opt is @nonnull: - >> nonnull[] - = 5 +func maybe_array(should_i:Bool)->[Int]?: + if should_i: + return [10, 20, 30] + else: + return ![Int] + +func maybe_bool(should_i:Bool)->Bool?: + if should_i: + return no + else: + return !Bool + +func maybe_text(should_i:Bool)->Text?: + if should_i: + return "Hello" + else: + return !Text + +func maybe_num(should_i:Bool)->Num?: + if should_i: + return 12.3 else: - fail("Oops") + return !Num - >> opt = !@Int - when opt is @nonnull: - fail("Oops") +func maybe_lambda(should_i:Bool)-> func()?: + if should_i: + return func(): say("hi!") else: - >> opt + return !func() + +func main(): + >> 5? + = 5? : Int? + + >> if no: + !Int + else: + 5 + = 5? : Int? + + do: + !! Ints: + >> yep := maybe_int(yes) + = 123? + >> nope := maybe_int(no) = !Int + >> if yep: >> yep + else: fail("Falsey: $yep") + >> if nope: + fail("Truthy: $nope") + else: !! Falsey: $nope + + do: + !! ... + !! Arrays: + >> yep := maybe_array(yes) + = [10, 20, 30]? + >> nope := maybe_array(no) + = ![Int] + >> if yep: >> yep + else: fail("Falsey: $yep") + >> if nope: + fail("Truthy: $nope") + else: !! Falsey: $nope + + do: + !! ... + !! Bools: + >> yep := maybe_bool(yes) + = no? + >> nope := maybe_bool(no) + = !Bool + >> if yep: >> yep + else: fail("Falsey: $yep") + >> if nope: + fail("Truthy: $nope") + else: !! Falsey: $nope + + do: + !! ... + !! Text: + >> yep := maybe_text(yes) + = "Hello"? + >> nope := maybe_text(no) + = !Text + >> if yep: >> yep + else: fail("Falsey: $yep") + >> if nope: + fail("Truthy: $nope") + else: !! Falsey: $nope + + do: + !! ... + !! Nums: + >> yep := maybe_num(yes) + = 12.3? + >> nope := maybe_num(no) + = !Num + >> if yep: >> yep + else: fail("Falsey: $yep") + >> if nope: + fail("Truthy: $nope") + else: !! Falsey: $nope + + do: + !! ... + !! Lambdas: + >> yep := maybe_lambda(yes) + = func(): ...? + >> nope := maybe_lambda(no) + = !func() + >> if yep: >> yep + else: fail("Falsey: $yep") + >> if nope: + fail("Truthy: $nope") + else: !! Falsey: $nope + diff --git a/test/tables.tm b/test/tables.tm index 7b3595b6..e59283ed 100644 --- a/test/tables.tm +++ b/test/tables.tm @@ -38,7 +38,7 @@ func main(): >> t2.length = 1 >> t2.fallback - = @%{"one":1, "two":2}? + = {"one":1, "two":2}? t2_str := "" for k,v in t2: diff --git a/typecheck.c b/typecheck.c index 5d3ffb48..d82ddcbf 100644 --- a/typecheck.c +++ b/typecheck.c @@ -43,7 +43,7 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) type_t *pointed_t = parse_type_ast(env, ptr->pointed); if (pointed_t->tag == VoidType) code_err(ast, "Void pointers are not supported. You probably meant 'Memory' instead of 'Void'"); - return Type(PointerType, .is_optional=ptr->is_optional, .pointed=pointed_t, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly); + return Type(PointerType, .pointed=pointed_t, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly); } case ArrayTypeAST: { type_ast_t *item_type = Match(ast, ArrayTypeAST)->item; @@ -109,6 +109,13 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) REVERSE_LIST(type_args); return Type(ClosureType, Type(FunctionType, .args=type_args, .ret=ret_t)); } + case OptionalTypeAST: { + auto opt = Match(ast, OptionalTypeAST); + type_t *t = parse_type_ast(env, opt->type); + if (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType) + code_err(ast, "Optional %T types are not supported.", t); + return Type(OptionalType, .type=t); + } case UnknownTypeAST: code_err(ast, "I don't know how to get this type"); } errx(1, "Unreachable"); @@ -438,60 +445,43 @@ type_t *get_method_type(env_t *env, ast_t *self, const char *name) type_t *get_clause_type(env_t *env, type_t *subject_t, when_clause_t *clause) { - if (subject_t->tag == PointerType) { - if (!Match(subject_t, PointerType)->is_optional) - code_err(clause->body, "This %T pointer type is not optional, so this 'when' statement is tautological", subject_t); - - const char *tag_name = Match(clause->tag_name, Var)->name; - if (!streq(tag_name, "@")) - code_err(clause->tag_name, "'when' clauses on optional pointers only support @var, not tags like '%s'", tag_name); - - assert(clause->args); - env_t *scope = fresh_scope(env); - auto ptr = Match(subject_t, PointerType); - set_binding(scope, Match(clause->args->ast, Var)->name, - new(binding_t, .type=Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly))); - - return get_type(scope, clause->body); - } else { - assert(subject_t->tag == EnumType); - tag_t * const tags = Match(subject_t, EnumType)->tags; - - const char *tag_name = Match(clause->tag_name, Var)->name; - type_t *tag_type = NULL; - for (tag_t *tag = tags; tag; tag = tag->next) { - if (streq(tag->name, tag_name)) { - tag_type = tag->type; - break; - } + assert(subject_t->tag == EnumType); + tag_t * const tags = Match(subject_t, EnumType)->tags; + + const char *tag_name = Match(clause->tag_name, Var)->name; + type_t *tag_type = NULL; + for (tag_t *tag = tags; tag; tag = tag->next) { + if (streq(tag->name, tag_name)) { + tag_type = tag->type; + break; } + } - if (!tag_type) - code_err(clause->tag_name, "There is no tag '%s' for the type %T", tag_name, subject_t); + if (!tag_type) + code_err(clause->tag_name, "There is no tag '%s' for the type %T", tag_name, subject_t); - // Don't return early so we validate the tags - if (!clause->args) - return get_type(env, clause->body); + // Don't return early so we validate the tags + if (!clause->args) + return get_type(env, clause->body); - env_t *scope = fresh_scope(env); - auto tag_struct = Match(tag_type, StructType); - if (!clause->args->next && tag_struct->fields && tag_struct->fields->next) { - set_binding(scope, Match(clause->args->ast, Var)->name, new(binding_t, .type=tag_type)); - } else { - ast_list_t *var = clause->args; - arg_t *field = tag_struct->fields; - while (var || field) { - if (!var) - code_err(clause->tag_name, "The field %T.%s.%s wasn't accounted for", subject_t, tag_name, field->name); - if (!field) - code_err(var->ast, "This is one more field than %T has", subject_t); - set_binding(scope, Match(var->ast, Var)->name, new(binding_t, .type=field->type)); - var = var->next; - field = field->next; - } - } - return get_type(scope, clause->body); - } + env_t *scope = fresh_scope(env); + auto tag_struct = Match(tag_type, StructType); + if (!clause->args->next && tag_struct->fields && tag_struct->fields->next) { + set_binding(scope, Match(clause->args->ast, Var)->name, new(binding_t, .type=tag_type)); + } else { + ast_list_t *var = clause->args; + arg_t *field = tag_struct->fields; + while (var || field) { + if (!var) + code_err(clause->tag_name, "The field %T.%s.%s wasn't accounted for", subject_t, tag_name, field->name); + if (!field) + code_err(var->ast, "This is one more field than %T has", subject_t); + set_binding(scope, Match(var->ast, Var)->name, new(binding_t, .type=field->type)); + var = var->next; + field = field->next; + } + } + return get_type(scope, clause->body); } type_t *get_type(env_t *env, ast_t *ast) @@ -500,10 +490,7 @@ type_t *get_type(env_t *env, ast_t *ast) switch (ast->tag) { case Nil: { type_t *t = parse_type_ast(env, Match(ast, Nil)->type); - if (t->tag != PointerType) - code_err(ast, "This type is not a pointer type, so it doesn't work with a '!' nil expression"); - auto ptr = Match(t, PointerType); - return Type(PointerType, .is_optional=true, .pointed=ptr->pointed, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly); + return Type(OptionalType, .type=t); } case Bool: { return Type(BoolType); @@ -533,7 +520,7 @@ type_t *get_type(env_t *env, ast_t *ast) type_t *pointed = get_type(env, Match(ast, HeapAllocate)->value); if (has_stack_memory(pointed)) code_err(ast, "Stack references cannot be moved to the heap because they may outlive the stack frame they were created in."); - return Type(PointerType, .is_optional=false, .pointed=pointed); + return Type(PointerType, .pointed=pointed); } case StackReference: { // Supported: @@ -558,10 +545,10 @@ type_t *get_type(env_t *env, ast_t *ast) type_t *ref_type = get_type(env, value); type_t *base_type = get_type(env, base); - if (base_type->tag == PointerType) { + if (base_type->tag == OptionalType) { + code_err(base, "This value might be null, so it can't be safely dereferenced"); + } else if (base_type->tag == PointerType) { auto ptr = Match(base_type, PointerType); - if (ptr->is_optional) - code_err(base, "This value might be null, so it can't be safely dereferenced"); return Type(PointerType, .pointed=ref_type, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly); } else if (base->tag == Var) { return Type(PointerType, .pointed=ref_type, .is_stack=true); @@ -573,12 +560,9 @@ type_t *get_type(env_t *env, ast_t *ast) case Optional: { ast_t *value = Match(ast, Optional)->value; type_t *t = get_type(env, value); - if (t->tag != PointerType) - code_err(ast, "This value is not a pointer, it has type %T, so it can't be optional", t); - auto ptr = Match(t, PointerType); - if (ptr->is_optional) + if (t->tag == OptionalType) code_err(ast, "This value is already optional, it can't be converted to optional"); - return Type(PointerType, .pointed=ptr->pointed, .is_optional=true, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly); + return Type(OptionalType, .type=t); } case TextLiteral: return TEXT_TYPE; case TextJoin: { @@ -740,12 +724,11 @@ type_t *get_type(env_t *env, ast_t *ast) case Index: { auto indexing = Match(ast, Index); type_t *indexed_t = get_type(env, indexing->indexed); - if (indexed_t->tag == PointerType && !indexing->index) { - auto ptr = Match(indexed_t, PointerType); - if (ptr->is_optional) - code_err(ast, "You're attempting to dereference a pointer whose type indicates it could be nil"); - return ptr->pointed; - } + if (indexed_t->tag == OptionalType && !indexing->index) + code_err(ast, "You're attempting to dereference a value whose type indicates it could be nil"); + + if (indexed_t->tag == PointerType && !indexing->index) + return Match(indexed_t, PointerType)->pointed; type_t *value_t = value_type(indexed_t); if (value_t->tag == ArrayType) { @@ -791,7 +774,7 @@ type_t *get_type(env_t *env, ast_t *ast) else if (streq(call->name, "clear")) return Type(VoidType); else if (streq(call->name, "counts")) return Type(TableType, .key_type=item_type, .value_type=INT_TYPE); else if (streq(call->name, "find")) return INT_TYPE; - else if (streq(call->name, "first")) return Type(PointerType, .pointed=item_type, .is_optional=true, .is_readonly=true); + else if (streq(call->name, "first")) return Type(OptionalType, .type=Type(PointerType, .pointed=item_type, .is_readonly=true)); else if (streq(call->name, "from")) return self_value_t; else if (streq(call->name, "has")) return Type(BoolType); else if (streq(call->name, "heap_pop")) return item_type; @@ -845,8 +828,7 @@ type_t *get_type(env_t *env, ast_t *ast) code_err(ast, "The table method :get_or_null() is only supported for tables whose value type is a pointer, not %T", table->value_type); auto ptr = Match(table->value_type, PointerType); - return Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack, - .is_readonly=ptr->is_readonly, .is_optional=true); + return Type(OptionalType, .type=Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly)); } else if (streq(call->name, "has")) return Type(BoolType); else if (streq(call->name, "remove")) return Type(VoidType); else if (streq(call->name, "set")) return Type(VoidType); @@ -939,7 +921,7 @@ type_t *get_type(env_t *env, ast_t *ast) type_t *t = get_type(env, Match(ast, Not)->value); if (t->tag == IntType || t->tag == NumType || t->tag == BoolType) return t; - if (t->tag == PointerType && Match(t, PointerType)->is_optional) + if (t->tag == OptionalType) return Type(BoolType); ast_t *value = Match(ast, Not)->value; @@ -1005,12 +987,14 @@ type_t *get_type(env_t *env, ast_t *ast) return lhs_t; } else if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType) { return lhs_t; + } else if (rhs_t->tag == OptionalType) { + if (can_promote(lhs_t, rhs_t)) + return rhs_t; } else if (lhs_t->tag == PointerType && rhs_t->tag == PointerType) { auto lhs_ptr = Match(lhs_t, PointerType); auto rhs_ptr = Match(rhs_t, PointerType); if (type_eq(lhs_ptr->pointed, rhs_ptr->pointed)) - return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=lhs_ptr->is_optional || rhs_ptr->is_optional, - .is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly); + return Type(PointerType, .pointed=lhs_ptr->pointed, .is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly); } else if (is_int_type(lhs_t) && is_int_type(rhs_t)) { return get_math_type(env, ast, lhs_t, rhs_t); } @@ -1024,15 +1008,17 @@ type_t *get_type(env_t *env, ast_t *ast) return lhs_t; } else if (is_int_type(lhs_t) && is_int_type(rhs_t)) { return get_math_type(env, ast, lhs_t, rhs_t); + } else if (lhs_t->tag == OptionalType) { + if (can_promote(rhs_t, lhs_t)) + return rhs_t; } else if (lhs_t->tag == PointerType) { auto lhs_ptr = Match(lhs_t, PointerType); if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType) { - return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=false, .is_readonly=lhs_ptr->is_readonly); + return Type(PointerType, .pointed=lhs_ptr->pointed, .is_readonly=lhs_ptr->is_readonly); } else if (rhs_t->tag == PointerType) { auto rhs_ptr = Match(rhs_t, PointerType); if (type_eq(rhs_ptr->pointed, lhs_ptr->pointed)) - return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=lhs_ptr->is_optional && rhs_ptr->is_optional, - .is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly); + return Type(PointerType, .pointed=lhs_ptr->pointed, .is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly); } } code_err(ast, "I can't figure out the type of this `or` expression because the left side is a %T, but the right side is a %T", @@ -1157,7 +1143,6 @@ type_t *get_type(env_t *env, ast_t *ast) REVERSE_LIST(args); type_t *ret = get_type(scope, lambda->body); - assert(ret); if (ret->tag == ReturnType) ret = Match(ret, ReturnType)->ret; if (ret->tag == AbortType) @@ -1192,104 +1177,77 @@ type_t *get_type(env_t *env, ast_t *ast) auto when = Match(ast, When); type_t *subject_t = get_type(env, when->subject); type_t *overall_t = NULL; - if (subject_t->tag == PointerType) { - if (!Match(subject_t, PointerType)->is_optional) - code_err(when->subject, "This %T pointer type is not optional, so this 'when' statement is tautological", subject_t); - - bool handled_at = false; - for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { - const char *tag_name = Match(clause->tag_name, Var)->name; - if (!streq(tag_name, "@")) - code_err(clause->tag_name, "'when' clauses on optional pointers only support @var, not tags like '%s'", tag_name); - if (handled_at) - code_err(clause->tag_name, "This 'when' statement has already handled the case of non-null pointers!"); - handled_at = true; - - assert(clause->args); - type_t *clause_type = get_clause_type(env, subject_t, clause); - type_t *merged = type_or_type(overall_t, clause_type); - if (!merged) - code_err(clause->body, "The type of this branch is %T, which conflicts with the earlier branch type of %T", - clause_type, overall_t); - overall_t = merged; - } - if (!handled_at) - code_err(ast, "This 'when' statement doesn't handle non-null pointers"); - if (!when->else_body) - code_err(ast, "This 'when' statement doesn't handle null pointers"); - return overall_t; - } else if (subject_t->tag == EnumType) { - tag_t * const tags = Match(subject_t, EnumType)->tags; - - typedef struct match_s { - tag_t *tag; - bool handled; - struct match_s *next; - } match_t; - match_t *matches = NULL; - for (tag_t *tag = tags; tag; tag = tag->next) - matches = new(match_t, .tag=tag, .handled=false, .next=matches); - - for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { - const char *tag_name = Match(clause->tag_name, Var)->name; - CORD valid_tags = CORD_EMPTY; - for (match_t *m = matches; m; m = m->next) { - if (streq(m->tag->name, tag_name)) { - if (m->handled) - code_err(clause->tag_name, "This tag was already handled earlier"); - m->handled = true; - goto found_matching_tag; - } - if (valid_tags) valid_tags = CORD_cat(valid_tags, ", "); - valid_tags = CORD_cat(valid_tags, m->tag->name); - } + if (subject_t->tag != EnumType) + code_err(when->subject, "'when' statements are only for enum types and optional pointers, not %T", subject_t); - code_err(clause->tag_name, "There is no tag '%s' for the type %T (valid tags: %s)", - tag_name, subject_t, CORD_to_char_star(valid_tags)); - found_matching_tag:; - } + tag_t * const tags = Match(subject_t, EnumType)->tags; - for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { - type_t *clause_type = get_clause_type(env, subject_t, clause); - type_t *merged = type_or_type(overall_t, clause_type); - if (!merged) - code_err(clause->body, "The type of this branch is %T, which conflicts with the earlier branch type of %T", - clause_type, overall_t); - overall_t = merged; + typedef struct match_s { + tag_t *tag; + bool handled; + struct match_s *next; + } match_t; + match_t *matches = NULL; + for (tag_t *tag = tags; tag; tag = tag->next) + matches = new(match_t, .tag=tag, .handled=false, .next=matches); + + for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { + const char *tag_name = Match(clause->tag_name, Var)->name; + CORD valid_tags = CORD_EMPTY; + for (match_t *m = matches; m; m = m->next) { + if (streq(m->tag->name, tag_name)) { + if (m->handled) + code_err(clause->tag_name, "This tag was already handled earlier"); + m->handled = true; + goto found_matching_tag; + } + if (valid_tags) valid_tags = CORD_cat(valid_tags, ", "); + valid_tags = CORD_cat(valid_tags, m->tag->name); } - if (when->else_body) { - bool any_unhandled = false; - for (match_t *m = matches; m; m = m->next) { - if (!m->handled) { - any_unhandled = true; - break; - } - } - // HACK: `while when ...` is handled by the parser adding an implicit - // `else: stop`, which has an empty source code span. - if (!any_unhandled && when->else_body->end > when->else_body->start) - code_err(when->else_body, "This 'else' block will never run because every tag is handled"); + code_err(clause->tag_name, "There is no tag '%s' for the type %T (valid tags: %s)", + tag_name, subject_t, CORD_to_char_star(valid_tags)); + found_matching_tag:; + } - type_t *else_t = get_type(env, when->else_body); - type_t *merged = type_or_type(overall_t, else_t); - if (!merged) - code_err(when->else_body, - "I was expecting this block to have a %T value (based on earlier clauses), but it actually has a %T value.", - overall_t, else_t); - return merged; - } else { - CORD unhandled = CORD_EMPTY; - for (match_t *m = matches; m; m = m->next) { - if (!m->handled) - unhandled = unhandled ? CORD_all(unhandled, ", ", m->tag->name) : m->tag->name; + for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { + type_t *clause_type = get_clause_type(env, subject_t, clause); + type_t *merged = type_or_type(overall_t, clause_type); + if (!merged) + code_err(clause->body, "The type of this branch is %T, which conflicts with the earlier branch type of %T", + clause_type, overall_t); + overall_t = merged; + } + + if (when->else_body) { + bool any_unhandled = false; + for (match_t *m = matches; m; m = m->next) { + if (!m->handled) { + any_unhandled = true; + break; } - if (unhandled) - code_err(ast, "This 'when' statement doesn't handle the tags: %s", CORD_to_const_char_star(unhandled)); - return overall_t; } + // HACK: `while when ...` is handled by the parser adding an implicit + // `else: stop`, which has an empty source code span. + if (!any_unhandled && when->else_body->end > when->else_body->start) + code_err(when->else_body, "This 'else' block will never run because every tag is handled"); + + type_t *else_t = get_type(env, when->else_body); + type_t *merged = type_or_type(overall_t, else_t); + if (!merged) + code_err(when->else_body, + "I was expecting this block to have a %T value (based on earlier clauses), but it actually has a %T value.", + overall_t, else_t); + return merged; } else { - code_err(when->subject, "'when' statements are only for enum types and optional pointers, not %T", subject_t); + CORD unhandled = CORD_EMPTY; + for (match_t *m = matches; m; m = m->next) { + if (!m->handled) + unhandled = unhandled ? CORD_all(unhandled, ", ", m->tag->name) : m->tag->name; + } + if (unhandled) + code_err(ast, "This 'when' statement doesn't handle the tags: %s", CORD_to_const_char_star(unhandled)); + return overall_t; } } @@ -65,12 +65,15 @@ CORD type_to_cord(type_t *t) { auto ptr = Match(t, PointerType); CORD sigil = ptr->is_stack ? "&" : "@"; if (ptr->is_readonly) sigil = CORD_cat(sigil, "%"); - return CORD_all(sigil, type_to_cord(ptr->pointed), ptr->is_optional ? "?" : CORD_EMPTY); + return CORD_all(sigil, type_to_cord(ptr->pointed)); } case EnumType: { auto tagged = Match(t, EnumType); return tagged->name; } + case OptionalType: { + return CORD_all(type_to_cord(Match(t, OptionalType)->type), "?"); + } case TypeInfoType: { return CORD_all("TypeInfo(", Match(t, TypeInfoType)->name, ")"); } @@ -111,21 +114,20 @@ bool type_eq(type_t *a, type_t *b) bool type_is_a(type_t *t, type_t *req) { if (type_eq(t, req)) return true; + if (req->tag == OptionalType) + return type_is_a(t, Match(req, OptionalType)->type); if (t->tag == PointerType && req->tag == PointerType) { auto t_ptr = Match(t, PointerType); auto req_ptr = Match(req, PointerType); if (type_eq(t_ptr->pointed, req_ptr->pointed)) - return (!t_ptr->is_stack && !t_ptr->is_optional && req_ptr->is_stack) - || (!t_ptr->is_stack && req_ptr->is_optional); + return (!t_ptr->is_stack && req_ptr->is_stack) || (!t_ptr->is_stack); } return false; } static type_t *non_optional(type_t *t) { - if (t->tag != PointerType) return t; - auto ptr = Match(t, PointerType); - return ptr->is_optional ? Type(PointerType, .is_optional=false, .pointed=ptr->pointed) : t; + return t->tag == OptionalType ? Match(t, OptionalType)->type : t; } PUREFUNC type_t *value_type(type_t *t) @@ -217,6 +219,7 @@ PUREFUNC bool has_heap_memory(type_t *t) case TableType: return true; case SetType: return true; case PointerType: return true; + case OptionalType: return has_heap_memory(Match(t, OptionalType)->type); case BigIntType: return true; case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { @@ -243,6 +246,7 @@ PUREFUNC bool can_send_over_channel(type_t *t) case ChannelType: return true; case TableType: return true; case PointerType: return false; + case OptionalType: return can_send_over_channel(Match(t, OptionalType)->type); case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { if (!can_send_over_channel(field->type)) @@ -265,6 +269,7 @@ PUREFUNC bool has_stack_memory(type_t *t) { switch (t->tag) { case PointerType: return Match(t, PointerType)->is_stack; + case OptionalType: return has_stack_memory(Match(t, OptionalType)->type); default: return false; } } @@ -294,11 +299,13 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed) return true; // Automatic dereferencing: - if (actual->tag == PointerType && !Match(actual, PointerType)->is_optional - && can_promote(Match(actual, PointerType)->pointed, needed)) + if (actual->tag == PointerType && can_promote(Match(actual, PointerType)->pointed, needed)) return true; // Optional promotion: + if (needed->tag == OptionalType && can_promote(actual, Match(needed, OptionalType)->type)) + return true; + if (needed->tag == PointerType && actual->tag == PointerType) { auto needed_ptr = Match(needed, PointerType); auto actual_ptr = Match(actual, PointerType); @@ -309,9 +316,6 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed) else if (actual_ptr->is_stack && !needed_ptr->is_stack) // Can't use &x for a function that wants a @Foo or ?Foo return false; - else if (actual_ptr->is_optional && !needed_ptr->is_optional) - // Can't use !Foo for a function that wants @Foo - return false; else if (actual_ptr->is_readonly && !needed_ptr->is_readonly) // Can't use pointer to readonly data when we need a pointer that can write to the data return false; @@ -354,66 +358,6 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed) return false; } -PUREFUNC bool can_leave_uninitialized(type_t *t) -{ - switch (t->tag) { - case PointerType: return Match(t, PointerType)->is_optional; - case ArrayType: case IntType: case NumType: case BoolType: - return true; - case ChannelType: return false; - case StructType: { - for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { - if (!can_leave_uninitialized(field->type)) - return false; - } - return true; - } - case EnumType: { - for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && !can_leave_uninitialized(tag->type)) - return false; - } - return true; - } - default: return false; - } -} - -PUREFUNC static bool _can_have_cycles(type_t *t, Table_t *seen) -{ - switch (t->tag) { - case ArrayType: return _can_have_cycles(Match(t, ArrayType)->item_type, seen); - case ChannelType: return _can_have_cycles(Match(t, ChannelType)->item_type, seen); - case TableType: { - auto table = Match(t, TableType); - return _can_have_cycles(table->key_type, seen) || _can_have_cycles(table->value_type, seen); - } - case SetType: return _can_have_cycles(Match(t, SetType)->item_type, seen); - case StructType: { - for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { - if (_can_have_cycles(field->type, seen)) - return true; - } - return false; - } - case PointerType: return _can_have_cycles(Match(t, PointerType)->pointed, seen); - case EnumType: { - for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) { - if (tag->type && _can_have_cycles(tag->type, seen)) - return true; - } - return false; - } - default: return false; - } -} - -PUREFUNC bool can_have_cycles(type_t *t) -{ - Table_t seen = {0}; - return _can_have_cycles(t, &seen); -} - PUREFUNC bool is_int_type(type_t *t) { return t->tag == IntType || t->tag == BigIntType; @@ -424,48 +368,15 @@ PUREFUNC bool is_numeric_type(type_t *t) return t->tag == IntType || t->tag == BigIntType || t->tag == NumType; } -type_t *replace_type(type_t *t, type_t *target, type_t *replacement) +PUREFUNC bool supports_optionals(type_t *t) { - if (type_eq(t, target)) - return replacement; - -#define COPY(t) memcpy(GC_MALLOC(sizeof(type_t)), (t), sizeof(type_t)) -#define REPLACED_MEMBER(t, tag, member) ({ t = memcpy(GC_MALLOC(sizeof(type_t)), (t), sizeof(type_t)); Match((struct type_s*)(t), tag)->member = replace_type(Match((t), tag)->member, target, replacement); t; }) switch (t->tag) { - case ArrayType: return REPLACED_MEMBER(t, ArrayType, item_type); - case SetType: return REPLACED_MEMBER(t, SetType, item_type); - case ChannelType: return REPLACED_MEMBER(t, ChannelType, item_type); - case TableType: { - t = REPLACED_MEMBER(t, TableType, key_type); - t = REPLACED_MEMBER(t, TableType, value_type); - return t; - } - case FunctionType: { - auto fn = Match(t, FunctionType); - t = REPLACED_MEMBER(t, FunctionType, ret); - arg_t *args = LIST_MAP(fn->args, old_arg, .type=replace_type(old_arg->type, target, replacement)); - Match((struct type_s*)t, FunctionType)->args = args; - return t; - } - case StructType: { - auto struct_ = Match(t, StructType); - arg_t *fields = LIST_MAP(struct_->fields, field, .type=replace_type(field->type, target, replacement)); - t = COPY(t); - Match((struct type_s*)t, StructType)->fields = fields; - return t; - } - case PointerType: return REPLACED_MEMBER(t, PointerType, pointed); - case EnumType: { - auto tagged = Match(t, EnumType); - tag_t *tags = LIST_MAP(tagged->tags, tag, .type=replace_type(tag->type, target, replacement)); - t = COPY(t); - Match((struct type_s*)t, EnumType)->tags = tags; - return t; - } - default: return t; + case BoolType: case CStringType: case BigIntType: case NumType: case TextType: + case ArrayType: case SetType: case TableType: case FunctionType: case ClosureType: + case PointerType: + return true; + default: return false; } -#undef COPY -#undef REPLACED_MEMBER } PUREFUNC size_t type_size(type_t *t) @@ -495,6 +406,20 @@ PUREFUNC size_t type_size(type_t *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(t, IntType)->bits) { + case TYPE_IBITS64: return sizeof(struct { int64_t x; bool is_null; }); + case TYPE_IBITS32: return sizeof(int64_t); + case TYPE_IBITS16: return sizeof(int32_t); + case TYPE_IBITS8: return sizeof(int16_t); + default: errx(1, "Invalid integer bit size"); + } + default: return type_size(nonnull); + } + } case StructType: { arg_t *fields = Match(t, StructType)->fields; size_t size = t->tag == StructType ? 0 : sizeof(void*); @@ -558,6 +483,20 @@ PUREFUNC size_t type_align(type_t *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(t, IntType)->bits) { + case TYPE_IBITS64: return __alignof__(struct { int64_t x; bool is_null; }); + case TYPE_IBITS32: return __alignof__(int64_t); + case TYPE_IBITS16: return __alignof__(int32_t); + case TYPE_IBITS8: return __alignof__(int16_t); + default: errx(1, "Invalid integer bit size"); + } + default: return type_align(nonnull); + } + } case StructType: { arg_t *fields = Match(t, StructType)->fields; size_t align = t->tag == StructType ? 0 : sizeof(void*); @@ -633,9 +572,9 @@ type_t *get_field_type(type_t *t, const char *field_name) else if (streq(field_name, "values")) return Type(ArrayType, Match(t, TableType)->value_type); else if (streq(field_name, "default")) - return Type(PointerType, .pointed=Match(t, TableType)->value_type, .is_readonly=true, .is_optional=true); + return Type(OptionalType, Match(t, TableType)->value_type); else if (streq(field_name, "fallback")) - return Type(PointerType, .pointed=t, .is_readonly=true, .is_optional=true); + return Type(OptionalType, .type=t); return NULL; } case ArrayType: { @@ -57,6 +57,7 @@ struct type_s { PointerType, StructType, EnumType, + OptionalType, TypeInfoType, ModuleType, } tag; @@ -97,7 +98,7 @@ struct type_s { } ClosureType; struct { type_t *pointed; - bool is_optional:1, is_stack:1, is_readonly:1; + bool is_stack:1, is_readonly:1; } PointerType; struct { const char *name; @@ -112,6 +113,9 @@ struct type_s { struct env_s *env; } EnumType; struct { + type_t *type; + } OptionalType; + struct { const char *name; type_t *type; struct env_s *env; @@ -139,11 +143,9 @@ PUREFUNC bool has_heap_memory(type_t *t); PUREFUNC bool has_stack_memory(type_t *t); PUREFUNC bool can_send_over_channel(type_t *t); PUREFUNC bool can_promote(type_t *actual, type_t *needed); -PUREFUNC bool can_leave_uninitialized(type_t *t); -PUREFUNC bool can_have_cycles(type_t *t); PUREFUNC bool is_int_type(type_t *t); PUREFUNC bool is_numeric_type(type_t *t); -type_t *replace_type(type_t *t, type_t *target, type_t *replacement); +PUREFUNC bool supports_optionals(type_t *t); PUREFUNC size_t type_size(type_t *t); PUREFUNC size_t type_align(type_t *t); PUREFUNC size_t padded_type_size(type_t *t); |
