commit 565ce1c45b783b7a97003c867d3528a055268c37
parent 2a2f1ced2e2e40f7efaa499a5d619bcfb33e2954
Author: Drew DeVault <sir@cmpwn.com>
Date: Fri, 25 Dec 2020 11:02:41 -0500
Implement type assignability rules
Diffstat:
8 files changed, 162 insertions(+), 29 deletions(-)
diff --git a/include/type_store.h b/include/type_store.h
@@ -14,6 +14,9 @@ struct type_store {
struct type_bucket *buckets[TYPE_STORE_BUCKETS];
};
+bool type_is_assignable(struct type_store *store,
+ const struct type *to, const struct type *from);
+
unsigned long atype_hash(struct type_store *store, const struct ast_type *type);
unsigned long type_hash(struct type_store *store, const struct type *type);
diff --git a/include/types.h b/include/types.h
@@ -11,17 +11,18 @@ enum type_storage {
TYPE_STORAGE_ENUM,
TYPE_STORAGE_F32,
TYPE_STORAGE_F64,
- TYPE_STORAGE_I8,
TYPE_STORAGE_I16,
TYPE_STORAGE_I32,
TYPE_STORAGE_I64,
+ TYPE_STORAGE_I8,
TYPE_STORAGE_INT,
+ TYPE_STORAGE_NULL,
TYPE_STORAGE_RUNE,
TYPE_STORAGE_SIZE,
- TYPE_STORAGE_U8,
TYPE_STORAGE_U16,
TYPE_STORAGE_U32,
TYPE_STORAGE_U64,
+ TYPE_STORAGE_U8,
TYPE_STORAGE_UINT,
TYPE_STORAGE_UINTPTR,
TYPE_STORAGE_VOID,
@@ -87,12 +88,13 @@ struct type {
};
};
+const struct type *type_dereference(const struct type *type);
+
const char *type_storage_unparse(enum type_storage storage);
bool type_is_signed(const struct type *type);
bool type_is_integer(const struct type *type);
bool type_is_numeric(const struct type *type);
-
-const struct type *type_dereference(const struct type *type);
+bool type_is_float(const struct type *type);
// Built-in type singletons
extern const struct type
@@ -112,6 +114,7 @@ extern const struct type
builtin_type_u64,
builtin_type_uint,
builtin_type_uintptr,
+ builtin_type_null,
builtin_type_rune,
builtin_type_size,
builtin_type_void,
diff --git a/src/check.c b/src/check.c
@@ -13,6 +13,7 @@
struct context {
struct type_store store;
+ const struct type *current_fntype;
struct scope *unit;
struct scope *scope;
};
@@ -77,14 +78,16 @@ check_expr_assign(struct context *ctx,
"Cannot dereference non-pointer type for assignment");
expect(!(object->result->pointer.flags & PTR_NULLABLE),
"Cannot dereference nullable pointer type");
- // TODO: Test assignability rules
- assert(object->result->pointer.referent->storage == value->result->storage);
+ expect(type_is_assignable(&ctx->store,
+ object->result->pointer.referent,
+ value->result),
+ "Value type is not assignable to pointer type");
} else {
assert(object->type == EXPR_ACCESS); // Invariant
const struct scope_object *obj = object->access.object;
expect(!(obj->type->flags & TYPE_CONST), "Cannot assign to const object");
- // TODO: Test assignability rules:
- assert(obj->type->storage == value->result->storage);
+ expect(type_is_assignable(&ctx->store, obj->type, value->result),
+ "rvalue type is not assignable to lvalue");
}
}
@@ -170,8 +173,8 @@ check_expr_binding(struct context *ctx,
}
expect(type->size != 0 && type->size != SIZE_UNDEFINED,
"Cannot create binding for type of zero or undefined size");
-
- // TODO: Check assignability of initializer
+ expect(type_is_assignable(&ctx->store, type, initializer->result),
+ "Initializer is not assignable to binding type");
const struct scope_object *obj = scope_insert(ctx->scope,
O_BIND, &ident, type);
@@ -216,9 +219,9 @@ check_expr_call(struct context *ctx,
arg->value = xcalloc(1, sizeof(struct expression));
check_expression(ctx, aarg->value, arg->value);
- // TODO: Test for assignability
- expect(arg->value->result->storage == param->type->storage,
- "Invalid type for parameter");
+ expect(type_is_assignable(&ctx->store,
+ param->type, arg->value->result),
+ "Argument is not assignable to parameter type");
aarg = aarg->next;
param = param->next;
@@ -264,11 +267,12 @@ check_expr_constant(struct context *ctx,
case TYPE_STORAGE_F32:
case TYPE_STORAGE_F64:
case TYPE_STORAGE_STRING:
+ case TYPE_STORAGE_NULL:
+ case TYPE_STORAGE_VOID:
assert(0); // TODO
case TYPE_STORAGE_CHAR:
case TYPE_STORAGE_ENUM:
case TYPE_STORAGE_UINTPTR:
- case TYPE_STORAGE_VOID:
case TYPE_STORAGE_ALIAS:
case TYPE_STORAGE_ARRAY:
case TYPE_STORAGE_FUNCTION:
@@ -308,6 +312,7 @@ check_expr_list(struct context *ctx,
next = &list->next;
} else {
expr->result = lexpr->result;
+ expr->terminates = lexpr->terminates;
}
}
@@ -357,7 +362,9 @@ check_expr_return(struct context *ctx,
struct expression *rval = xcalloc(1, sizeof(struct expression));
check_expression(ctx, aexpr->_return.value, rval);
expr->_return.value = rval;
- // TODO: Test assignability with function's return type
+ expect(type_is_assignable(&ctx->store,
+ ctx->current_fntype->func.result, rval->result),
+ "Return value is not assignable to function result type");
}
trleave(TR_CHECK, NULL);
@@ -495,6 +502,7 @@ check_function(struct context *ctx,
const struct type *fntype = type_store_lookup_atype(
&ctx->store, &fn_atype);
assert(fntype); // Invariant
+ ctx->current_fntype = fntype;
struct declaration *decl = xcalloc(1, sizeof(struct declaration));
decl->type = DECL_FUNC;
@@ -519,7 +527,8 @@ check_function(struct context *ctx,
check_expression(ctx, afndecl->body, body);
decl->func.body = body;
- // TODO: Check assignability of expression result to function type
+ expect(body->terminates || type_is_assignable(&ctx->store, fntype->func.result, body->result),
+ "Result value is not assignable to function result type");
// TODO: Add function name to errors
if ((decl->func.flags & FN_INIT)
@@ -534,6 +543,7 @@ check_function(struct context *ctx,
scope_insert(ctx->unit, O_DECL, &decl->ident, decl->func.type);
scope_pop(&ctx->scope, TR_CHECK);
+ ctx->current_fntype = NULL;
trleave(TR_CHECK, NULL);
return decl;
}
diff --git a/src/lex.c b/src/lex.c
@@ -1025,6 +1025,7 @@ token_str(const struct token *tok)
case TYPE_STORAGE_ENUM:
case TYPE_STORAGE_FUNCTION:
case TYPE_STORAGE_POINTER:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_SLICE:
case TYPE_STORAGE_STRUCT:
case TYPE_STORAGE_TAGGED_UNION:
diff --git a/src/qtype.c b/src/qtype.c
@@ -27,6 +27,7 @@ qstype_for_type(const struct type *type)
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_UINTPTR: // XXX: Architecture dependent
case TYPE_STORAGE_POINTER: // XXX: Architecture dependent
+ case TYPE_STORAGE_NULL: // XXX: Architecture dependent
return Q_LONG;
case TYPE_STORAGE_F32:
return Q_SINGLE;
@@ -71,6 +72,7 @@ qxtype_for_type(const struct type *type)
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_UINTPTR: // XXX: Architecture dependent
case TYPE_STORAGE_POINTER: // XXX: Architecture dependent
+ case TYPE_STORAGE_NULL: // XXX: Architecture dependent
case TYPE_STORAGE_F32:
case TYPE_STORAGE_F64:
case TYPE_STORAGE_VOID:
@@ -113,6 +115,7 @@ qtype_for_type(struct gen_context *ctx, const struct type *type, bool extended)
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_UINTPTR:
case TYPE_STORAGE_POINTER:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_F32:
case TYPE_STORAGE_F64:
case TYPE_STORAGE_VOID:
@@ -147,6 +150,7 @@ type_is_aggregate(const struct type *type)
case TYPE_STORAGE_I8:
case TYPE_STORAGE_INT:
case TYPE_STORAGE_POINTER:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_RUNE:
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_U16:
diff --git a/src/type_store.c b/src/type_store.c
@@ -2,6 +2,93 @@
#include <stdlib.h>
#include "type_store.h"
#include "util.h"
+#include <stdio.h>
+
+bool
+type_is_assignable(struct type_store *store,
+ const struct type *to,
+ const struct type *from)
+{
+ // const and non-const types are mutually assignable
+ if (to->flags & TYPE_CONST) {
+ to = type_store_lookup_with_flags(store,
+ to, to->flags & ~TYPE_CONST);
+ }
+ if (from->flags & TYPE_CONST) {
+ from = type_store_lookup_with_flags(store,
+ from, from->flags & ~TYPE_CONST);
+ }
+
+ if (to == from) {
+ return true;
+ }
+
+ switch (to->storage) {
+ case TYPE_STORAGE_I8:
+ case TYPE_STORAGE_I16:
+ case TYPE_STORAGE_I32:
+ case TYPE_STORAGE_I64:
+ case TYPE_STORAGE_INT:
+ return type_is_integer(from)
+ && type_is_signed(from)
+ && to->size >= from->size;
+ case TYPE_STORAGE_SIZE:
+ case TYPE_STORAGE_U8:
+ case TYPE_STORAGE_U16:
+ case TYPE_STORAGE_U32:
+ case TYPE_STORAGE_U64:
+ case TYPE_STORAGE_UINT:
+ return type_is_integer(from)
+ && !type_is_signed(from)
+ && to->size >= from->size;
+ case TYPE_STORAGE_UINTPTR:
+ return (type_is_integer(from)
+ && !type_is_signed(from)
+ && to->size >= from->size)
+ || from->storage == TYPE_STORAGE_POINTER;
+ case TYPE_STORAGE_F32:
+ case TYPE_STORAGE_F64:
+ return type_is_float(from);
+ case TYPE_STORAGE_POINTER:
+ switch (from->storage) {
+ case TYPE_STORAGE_UINTPTR:
+ return true;
+ case TYPE_STORAGE_NULL:
+ return to->pointer.flags & PTR_NULLABLE;
+ case TYPE_STORAGE_POINTER:
+ if (to->pointer.referent != from->pointer.referent) {
+ return false;
+ }
+ if (to->pointer.flags & PTR_NULLABLE) {
+ return from->pointer.flags & PTR_NULLABLE;
+ }
+ return true;
+ default:
+ return false;
+ }
+ assert(0); // Unreachable
+ case TYPE_STORAGE_ALIAS:
+ case TYPE_STORAGE_ENUM:
+ case TYPE_STORAGE_TAGGED_UNION:
+ assert(0); // TODO
+ // The following types are only assignable from themselves, and are
+ // handled above:
+ case TYPE_STORAGE_ARRAY:
+ case TYPE_STORAGE_BOOL:
+ case TYPE_STORAGE_CHAR:
+ case TYPE_STORAGE_FUNCTION:
+ case TYPE_STORAGE_NULL:
+ case TYPE_STORAGE_RUNE:
+ case TYPE_STORAGE_SLICE:
+ case TYPE_STORAGE_STRING:
+ case TYPE_STORAGE_STRUCT:
+ case TYPE_STORAGE_UNION:
+ case TYPE_STORAGE_VOID:
+ return false;
+ }
+
+ assert(0); // Unreachable
+}
const struct type *
builtin_type_for_storage(enum type_storage storage, bool is_const)
@@ -42,8 +129,9 @@ builtin_type_for_storage(enum type_storage storage, bool is_const)
case TYPE_STORAGE_UINTPTR:
return is_const ? &builtin_type_const_uintptr : &builtin_type_uintptr;
case TYPE_STORAGE_VOID:
- // const void and void are the same type
- return is_const ? &builtin_type_void : &builtin_type_void;
+ return &builtin_type_void; // const void and void are the same type
+ case TYPE_STORAGE_NULL:
+ return &builtin_type_null; // const null and null are the same type
case TYPE_STORAGE_ALIAS:
case TYPE_STORAGE_ARRAY:
case TYPE_STORAGE_FUNCTION:
@@ -76,6 +164,7 @@ atype_hash(struct type_store *store, const struct ast_type *type)
case TYPE_STORAGE_I32:
case TYPE_STORAGE_I64:
case TYPE_STORAGE_INT:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_RUNE:
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_U8:
@@ -129,6 +218,7 @@ type_hash(struct type_store *store, const struct type *type)
case TYPE_STORAGE_I32:
case TYPE_STORAGE_I64:
case TYPE_STORAGE_INT:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_RUNE:
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_U8:
@@ -199,6 +289,7 @@ type_eq_atype(struct type_store *store,
case TYPE_STORAGE_I32:
case TYPE_STORAGE_I64:
case TYPE_STORAGE_INT:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_RUNE:
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_U8:
@@ -264,6 +355,7 @@ type_eq_type(struct type_store *store,
case TYPE_STORAGE_I32:
case TYPE_STORAGE_I64:
case TYPE_STORAGE_INT:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_RUNE:
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_U8:
@@ -327,6 +419,7 @@ type_init_from_atype(struct type_store *store,
case TYPE_STORAGE_I32:
case TYPE_STORAGE_I64:
case TYPE_STORAGE_INT:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_RUNE:
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_U8:
@@ -387,17 +480,18 @@ type_init_from_type(struct type_store *store,
case TYPE_STORAGE_CHAR:
case TYPE_STORAGE_F32:
case TYPE_STORAGE_F64:
- case TYPE_STORAGE_I8:
case TYPE_STORAGE_I16:
case TYPE_STORAGE_I32:
case TYPE_STORAGE_I64:
+ case TYPE_STORAGE_I8:
case TYPE_STORAGE_INT:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_RUNE:
case TYPE_STORAGE_SIZE:
- case TYPE_STORAGE_U8:
case TYPE_STORAGE_U16:
case TYPE_STORAGE_U32:
case TYPE_STORAGE_U64:
+ case TYPE_STORAGE_U8:
case TYPE_STORAGE_UINT:
case TYPE_STORAGE_UINTPTR:
case TYPE_STORAGE_VOID:
diff --git a/src/types.c b/src/types.c
@@ -2,6 +2,15 @@
#include <stdbool.h>
#include "types.h"
+const struct type *
+type_dereference(const struct type *type)
+{
+ if (type->storage != TYPE_STORAGE_POINTER) {
+ return type;
+ }
+ return type_dereference(type->pointer.referent);
+}
+
const char *
type_storage_unparse(enum type_storage storage)
{
@@ -34,6 +43,8 @@ type_storage_unparse(enum type_storage storage)
return "int";
case TYPE_STORAGE_POINTER:
return "pointer";
+ case TYPE_STORAGE_NULL:
+ return "rune";
case TYPE_STORAGE_RUNE:
return "rune";
case TYPE_STORAGE_SIZE:
@@ -82,6 +93,7 @@ type_is_integer(const struct type *type)
case TYPE_STORAGE_UNION:
case TYPE_STORAGE_BOOL:
case TYPE_STORAGE_CHAR:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_RUNE:
case TYPE_STORAGE_ENUM:
case TYPE_STORAGE_F32:
@@ -121,6 +133,7 @@ type_is_numeric(const struct type *type)
case TYPE_STORAGE_BOOL:
case TYPE_STORAGE_CHAR:
case TYPE_STORAGE_RUNE:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_ENUM:
return false;
case TYPE_STORAGE_I8:
@@ -143,6 +156,12 @@ type_is_numeric(const struct type *type)
}
bool
+type_is_float(const struct type *type)
+{
+ return type->storage == TYPE_STORAGE_F32 || type->storage == TYPE_STORAGE_F64;
+}
+
+bool
type_is_signed(const struct type *type)
{
switch (type->storage) {
@@ -159,6 +178,7 @@ type_is_signed(const struct type *type)
case TYPE_STORAGE_BOOL:
case TYPE_STORAGE_CHAR:
case TYPE_STORAGE_RUNE:
+ case TYPE_STORAGE_NULL:
case TYPE_STORAGE_SIZE:
case TYPE_STORAGE_U8:
case TYPE_STORAGE_U16:
@@ -181,15 +201,6 @@ type_is_signed(const struct type *type)
assert(0); // Unreachable
}
-const struct type *
-type_dereference(const struct type *type)
-{
- if (type->storage != TYPE_STORAGE_POINTER) {
- return type;
- }
- return type_dereference(type->pointer.referent);
-}
-
// Built-in type singletons
const struct type builtin_type_bool = {
.storage = TYPE_STORAGE_BOOL,
@@ -266,6 +277,11 @@ builtin_type_uintptr = {
.size = 8, // XXX: ARCH
.align = 8,
},
+builtin_type_null = {
+ .storage = TYPE_STORAGE_NULL,
+ .size = 8, // XXX: ARCH
+ .align = 8,
+},
builtin_type_rune = {
.storage = TYPE_STORAGE_RUNE,
.size = 4,
diff --git a/todo.txt b/todo.txt
@@ -1,5 +1,7 @@
highest priorities, unordered except where there are obvious dependencies:
+- double check integers <32 bits in gen
+- named constants (true, false, null, void)
- aggregate types
- in parameters
- in variables