commit a78994ee7377ec20b4de56432bde3113d205d684
parent edd4452053a3803ad9d4c304c1284f5ebba11487
Author: Drew DeVault <sir@cmpwn.com>
Date: Fri, 15 Jan 2021 19:05:47 -0500
Implement `is` operator
Diffstat:
5 files changed, 97 insertions(+), 5 deletions(-)
diff --git a/include/expr.h b/include/expr.h
@@ -104,6 +104,7 @@ enum cast_kind {
struct expression_cast {
enum cast_kind kind;
+ const struct type *secondary;
struct expression *value;
};
diff --git a/src/check.c b/src/check.c
@@ -442,12 +442,41 @@ check_expr_cast(struct context *ctx,
trace(TR_CHECK, "cast");
expr->type = EXPR_CAST;
expr->cast.kind = aexpr->cast.kind;
- expr->cast.value = xcalloc(1, sizeof(struct expression));
- check_expression(ctx, aexpr->cast.value, expr->cast.value);
- expr->result = type_store_lookup_atype(&ctx->store, aexpr->cast.type);
+ struct expression *value = expr->cast.value =
+ xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->cast.value, value);
+ const struct type *secondary = expr->cast.secondary =
+ type_store_lookup_atype(&ctx->store, aexpr->cast.type);
expect(&aexpr->cast.type->loc,
- type_is_castable(expr->result, expr->cast.value->result),
+ type_is_castable(secondary, value->result),
"Invalid cast");
+
+ if (aexpr->cast.kind == C_ASSERTION || aexpr->cast.kind == C_TEST) {
+ expect(&aexpr->cast.value->loc,
+ type_dealias(expr->cast.value->result)->storage
+ == TYPE_STORAGE_TAGGED_UNION,
+ "Expected a tagged union type");
+ bool found = false;
+ for (const struct type_tagged_union *t = &value->result->tagged;
+ t; t = t->next) {
+ if (t->type->id == secondary->id) {
+ found = true;
+ break;
+ }
+ }
+ expect(&aexpr->cast.type->loc, found,
+ "Type is not a valid member of the tagged union type");
+ }
+
+ switch (aexpr->cast.kind) {
+ case C_CAST:
+ case C_ASSERTION:
+ expr->result = secondary;
+ break;
+ case C_TEST:
+ expr->result = &builtin_type_bool;
+ break;
+ }
}
static void
diff --git a/src/gen.c b/src/gen.c
@@ -739,10 +739,38 @@ gen_cast_from_tagged(struct gen_context *ctx,
}
static void
+gen_expr_type_test(struct gen_context *ctx,
+ const struct expression *expr,
+ const struct qbe_value *out)
+{
+ // XXX: ARCH
+ const struct type *want = type_dealias(expr->cast.secondary),
+ *tagged = type_dealias(expr->cast.value->result);
+ struct qbe_value tag = {0}, in = {0}, id = {0};
+ gen_temp(ctx, &tag, &qbe_long, "tag.%d");
+ gen_temp(ctx, &in, qtype_for_type(ctx, tagged, false), "cast.in.%d");
+ qval_address(&in);
+ gen_expression(ctx, expr->cast.value, &in);
+ pushi(ctx->current, &tag, Q_LOADL, &in, NULL);
+ constl(&id, want->id);
+ pushi(ctx->current, out, Q_CEQL, &tag, &id, NULL);
+}
+
+static void
gen_expr_cast(struct gen_context *ctx,
const struct expression *expr,
const struct qbe_value *out)
{
+ switch (expr->cast.kind) {
+ case C_CAST:
+ break; // Handled below
+ case C_ASSERTION:
+ assert(0); // TODO
+ case C_TEST:
+ gen_expr_type_test(ctx, expr, out);
+ return;
+ }
+
const struct type *to = type_dealias(expr->result),
*from = type_dealias(expr->cast.value->result);
if (to->storage == from->storage) {
diff --git a/src/type_store.c b/src/type_store.c
@@ -167,6 +167,28 @@ type_is_assignable(struct type_store *store,
assert(0); // Unreachable
}
+static bool
+tagged_castable(const struct type *to, const struct type *from)
+{
+ if (to->storage == TYPE_STORAGE_TAGGED_UNION) {
+ assert(0); // TODO
+ }
+
+ // TODO: Update spec to make this consistent
+ size_t ncastable = 0;
+ for (const struct type_tagged_union *tu = &from->tagged;
+ tu; tu = tu->next) {
+ if (tu->type->id == to->id) {
+ return true;
+ }
+ if (type_is_castable(tu->type, to)) {
+ ++ncastable;
+ }
+ }
+
+ return ncastable == 1;
+}
+
bool
type_is_castable(const struct type *to, const struct type *from)
{
@@ -219,7 +241,7 @@ type_is_castable(const struct type *to, const struct type *from)
return to->storage == TYPE_STORAGE_SLICE
|| to->storage == TYPE_STORAGE_ARRAY;
case TYPE_STORAGE_TAGGED_UNION:
- assert(0); // TODO
+ return tagged_castable(to, from);
case TYPE_STORAGE_STRING:
return to->pointer.referent->storage == TYPE_STORAGE_CHAR
&& to->pointer.referent->flags & TYPE_CONST;
diff --git a/tests/13-tagged.ha b/tests/13-tagged.ha
@@ -29,8 +29,20 @@ fn storage() void = {
assert(y._u64 == 0xCAFEBABEDEADBEEFu64);
};
+fn operators() void = {
+ let x: (u8 | u16 | u32 | u64) = 42u8;
+ assert(x is u8);
+ x = 1337u16;
+ assert(x is u16);
+ x = 0xCAFEBABEu32;
+ assert(x is u32);
+ x = 0xCAFEBABEDEADBEEFu64;
+ assert(x is u64);
+};
+
export fn main() void = {
measurements();
storage();
+ operators();
// TODO: Expand this as other tagged union features become available
};