aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--ast.c5
-rw-r--r--ast.h6
-rw-r--r--builtins/functions.c11
-rw-r--r--builtins/optionals.c49
-rw-r--r--builtins/optionals.h25
-rw-r--r--builtins/pointer.c9
-rw-r--r--builtins/tomo.h1
-rw-r--r--builtins/types.c1
-rw-r--r--builtins/types.h13
-rw-r--r--compile.c131
-rw-r--r--environment.c2
-rw-r--r--parse.c63
-rw-r--r--repl.c4
-rw-r--r--test/arrays.tm8
-rw-r--r--test/optionals.tm128
-rw-r--r--test/tables.tm2
-rw-r--r--typecheck.c298
-rw-r--r--types.c163
-rw-r--r--types.h10
20 files changed, 557 insertions, 375 deletions
diff --git a/Makefile b/Makefile
index 21f65d7e..89eaa619 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/ast.c b/ast.c
index 0dfddb9a..869168f9 100644
--- a/ast.c
+++ b/ast.c
@@ -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;
}
diff --git a/ast.h b/ast.h
index 0306cef3..116f6ea7 100644
--- a/ast.h
+++ b/ast.h
@@ -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;
diff --git a/compile.c b/compile.c
index 99f94237..5ccfafe3 100644
--- a/compile.c
+++ b/compile.c
@@ -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"},
diff --git a/parse.c b/parse.c
index b094f6c9..95b05cbc 100644
--- a/parse.c
+++ b/parse.c
@@ -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;
}
diff --git a/repl.c b/repl.c
index 81962c38..372fcba4 100644
--- a/repl.c
+++ b/repl.c
@@ -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;
}
}
diff --git a/types.c b/types.c
index f7c3ba2e..c91e6eb2 100644
--- a/types.c
+++ b/types.c
@@ -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: {
diff --git a/types.h b/types.h
index 95b4d4c0..82efab37 100644
--- a/types.h
+++ b/types.h
@@ -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);