commit f590e0b1291a705ba99e451f66cdabf101987f02
parent 3990b7f01d35e6e6cf88a353f0fe2cfdd3e94d04
Author: Bor Grošelj Simić <bor.groseljsimic@telemach.net>
Date: Mon, 28 Mar 2022 22:52:47 +0200
allow casting tagged unions to their subsets
Allow casts like (a | b | c): (a | b). Difficulties can arise with casts
like this because the two unions may have different alignment. We
already have the gen code for this because we support doing such casts
implicitly in match, so this commit mostly just adds syntactic sugar over
let x: (a | b | c) = ...;
match (x) {
case let x: (a | b) =>
...
case c =>
abort();
};
Signed-off-by: Bor Grošelj Simić <bgs@turminal.net>
Diffstat:
M | src/check.c | | | 14 | +++++--------- |
M | src/gen.c | | | 96 | ++++++++++++++++++++++++++++++++++++++++++++++++------------------------------- |
M | src/types.c | | | 30 | +++++++++++++++++------------- |
M | tests/13-tagged.ha | | | 52 | ++++++++++++++++++++++++++++++++++++++++++++++++++-- |
4 files changed, 131 insertions(+), 61 deletions(-)
diff --git a/src/check.c b/src/check.c
@@ -1234,15 +1234,11 @@ check_expr_cast(struct context *ctx,
"a nullable pointer");
return;
}
- bool found = false;
- for (const struct type_tagged_union *t = &primary->tagged;
- t; t = t->next) {
- if (t->type->id == secondary->id) {
- found = true;
- break;
- }
- }
- if (!found) {
+ // secondary type must be a strict subset or a
+ // member of the primary type
+ if (!((tagged_subset_compat(primary, secondary)
+ || tagged_select_subtype(primary, secondary))
+ && !tagged_subset_compat(secondary, primary))) {
error(ctx, aexpr->cast.type->loc, expr,
"Type is not a valid member of "
"the tagged union type");
diff --git a/src/gen.c b/src/gen.c
@@ -1107,26 +1107,39 @@ gen_expr_call(struct gen_context *ctx, const struct expression *expr)
static struct gen_value gen_expr_cast(struct gen_context *ctx,
const struct expression *expr);
+static struct gen_value gen_subset_match_tests(struct gen_context *ctx,
+ struct qbe_value bmatch, struct qbe_value bnext,
+ struct qbe_value tag, const struct type *type);
+
static struct gen_value
gen_expr_type_test(struct gen_context *ctx, const struct expression *expr)
{
const struct type *secondary = expr->cast.secondary,
*from = expr->cast.value->result;
assert(type_dealias(from)->storage == STORAGE_TAGGED);
- const struct type *subtype = tagged_select_subtype(from, secondary);
- assert(subtype && subtype == secondary); // Lowered by check
-
struct gen_value val = gen_expr(ctx, expr->cast.value);
struct qbe_value qval = mkqval(ctx, &val);
+
struct qbe_value tag = mkqtmp(ctx,
- qtype_lookup(ctx, &builtin_type_uint, false),
- ".%d");
+ qtype_lookup(ctx, &builtin_type_uint, false), ".%d");
enum qbe_instr load = load_for_type(ctx, &builtin_type_uint);
+
+ struct gen_value result = {0};
pushi(ctx->current, &tag, load, &qval, NULL);
- struct qbe_value expected = constl(secondary->id);
- struct gen_value result = mktemp(ctx, &builtin_type_bool, ".%d");
- struct qbe_value qr = mkqval(ctx, &result);
- pushi(ctx->current, &qr, Q_CEQW, &tag, &expected, NULL);
+ if (tagged_select_subtype(from, secondary) != NULL) {
+ result = mktemp(ctx, &builtin_type_bool, ".%d");
+ struct qbe_value qr = mkqval(ctx, &result);
+ struct qbe_value expected = constl(secondary->id);
+ pushi(ctx->current, &qr, Q_CEQW, &tag, &expected, NULL);
+ } else if (tagged_subset_compat(from, secondary)) {
+ struct qbe_statement endl;
+ struct qbe_value bend = mklabel(ctx, &endl, ".%d");
+ result = gen_subset_match_tests(ctx, bend, bend, tag,
+ type_dealias(secondary));
+ push(&ctx->current->body, &endl);
+ } else {
+ abort();
+ }
return result;
}
@@ -1137,21 +1150,27 @@ gen_type_assertion(struct gen_context *ctx,
{
const struct type *want = expr->result;
struct qbe_value tag = mkqtmp(ctx,
- qtype_lookup(ctx, &builtin_type_uint, false),
- ".%d");
+ qtype_lookup(ctx, &builtin_type_uint, false), ".%d");
enum qbe_instr load = load_for_type(ctx, &builtin_type_uint);
pushi(ctx->current, &tag, load, &base, NULL);
- struct qbe_value expected = constl(want->id);
- struct gen_value result = mktemp(ctx, &builtin_type_bool, ".%d");
- struct qbe_value qr = mkqval(ctx, &result);
- pushi(ctx->current, &qr, Q_CEQW, &tag, &expected, NULL);
struct qbe_statement failedl, passedl;
struct qbe_value bfailed = mklabel(ctx, &failedl, "failed.%d");
struct qbe_value bpassed = mklabel(ctx, &passedl, "passed.%d");
- pushi(ctx->current, NULL, Q_JNZ, &qr, &bpassed, &bfailed, NULL);
- push(&ctx->current->body, &failedl);
+ if (tagged_select_subtype(expr->cast.value->result, want)) {
+ struct gen_value result = mktemp(ctx, &builtin_type_bool, ".%d");
+ struct qbe_value expected = constl(want->id);
+ struct qbe_value qr = mkqval(ctx, &result);
+ pushi(ctx->current, &qr, Q_CEQW, &tag, &expected, NULL);
+ pushi(ctx->current, NULL, Q_JNZ, &qr, &bpassed, &bfailed, NULL);
+ } else if (tagged_subset_compat(expr->cast.value->result, want)) {
+ gen_subset_match_tests(ctx, bpassed, bfailed, tag,
+ type_dealias(want));
+ } else {
+ abort();
+ }
+ push(&ctx->current->body, &failedl);
gen_fixed_abort(ctx, expr->loc, ABORT_TYPE_ASSERTION);
push(&ctx->current->body, &passedl);
@@ -1203,17 +1222,9 @@ static void
gen_expr_cast_tagged_at(struct gen_context *ctx,
const struct expression *expr, struct gen_value out)
{
+ assert(expr->type == EXPR_CAST);
const struct type *to = expr->result, *from = expr->cast.value->result;
const struct type *subtype = tagged_select_subtype(to, from);
- if ((int)expr->cast.value->type != EXPR_GEN_VALUE) {
- // EXPR_GEN_VALUE is a special case used by match for
- // COMPAT_MISALIGNED. Normally, casts are only allowed between
- // tagged unions if 'to' is a superset of 'from'. However, in
- // the EXPR_GEN_VALUE case, 'to' is a subset of 'from', but the
- // match code has already determined that the selected tag is a
- // member of 'to'.
- assert(subtype || tagged_subset_compat(to, from));
- }
if (!subtype && tagged_align_compat(from, to)) {
// Case 1: from is a union whose members are a subset of to, and
@@ -1221,12 +1232,20 @@ gen_expr_cast_tagged_at(struct gen_context *ctx,
// type 'from' as if it were of type 'to'
struct gen_value out2 = out;
out2.type = from;
- gen_expr_at(ctx, expr->cast.value, out2);
+ struct gen_value val = gen_expr(ctx, expr->cast.value);
+ gen_store(ctx, out2, val);
+ struct qbe_value qout = mkqval(ctx, &out2);
+ if (expr->cast.kind == C_ASSERTION) {
+ gen_type_assertion(ctx, expr, qout);
+ }
} else if (!subtype) {
// Case 2: like case 1, but with an alignment mismatch; more
// work is required.
struct gen_value value = gen_expr(ctx, expr->cast.value);
struct qbe_value qval = mkqval(ctx, &value);
+ if (expr->cast.kind == C_ASSERTION) {
+ gen_type_assertion(ctx, expr, qval);
+ }
struct qbe_value qout = mkqval(ctx, &out);
struct qbe_value tag = mkqtmp(ctx,
qtype_lookup(ctx, &builtin_type_uint, false), "tag.%d");
@@ -1254,6 +1273,7 @@ gen_expr_cast_tagged_at(struct gen_context *ctx,
gen_copy_aligned(ctx, iout, ival);
} else {
// Case 3: from is a member of to
+ assert(expr->cast.kind == C_CAST);
assert(subtype == from); // Lowered by check
struct qbe_value qout = mkqval(ctx, &out);
struct qbe_value id = constw(subtype->id);
@@ -1393,7 +1413,8 @@ gen_expr_cast(struct gen_context *ctx, const struct expression *expr)
if (expr->cast.kind != C_CAST) {
bool is_valid_tagged, is_valid_pointer;
is_valid_tagged = type_dealias(from)->storage == STORAGE_TAGGED
- && tagged_select_subtype(from, to);
+ && (tagged_select_subtype(from, to)
+ || tagged_subset_compat(from, to));
is_valid_pointer = type_dealias(from)->storage == STORAGE_POINTER
&& (type_dealias(to)->storage == STORAGE_POINTER
|| type_dealias(to)->storage == STORAGE_NULL);
@@ -2204,10 +2225,10 @@ gen_nested_match_tests(struct gen_context *ctx, struct gen_value object,
pushi(ctx->current, NULL, Q_JMP, &bmatch, NULL);
}
-static void
+static struct gen_value
gen_subset_match_tests(struct gen_context *ctx,
struct qbe_value bmatch, struct qbe_value bnext,
- struct qbe_value tag, const struct match_case *_case)
+ struct qbe_value tag, const struct type *type)
{
// In this case, we're testing a case which is itself a tagged union,
// and is a subset of the match object.
@@ -2222,18 +2243,18 @@ gen_subset_match_tests(struct gen_context *ctx,
//
// In this situation, we test the match object's tag against each type
// ID of the case type.
- const struct type *casetype = type_dealias(_case->type);
- for (const struct type_tagged_union *tu = &casetype->tagged;
- tu; tu = tu->next) {
+ struct gen_value match = mktemp(ctx, &builtin_type_bool, ".%d");
+ for (const struct type_tagged_union *tu = &type->tagged; tu; tu = tu->next) {
struct qbe_statement lnexttag;
struct qbe_value bnexttag = mklabel(ctx, &lnexttag, ".%d");
struct qbe_value id = constl(tu->type->id);
- struct qbe_value match = mkqtmp(ctx, &qbe_word, ".%d");
- pushi(ctx->current, &match, Q_CEQW, &tag, &id, NULL);
- pushi(ctx->current, NULL, Q_JNZ, &match, &bmatch, &bnexttag, NULL);
+ struct qbe_value qmatch = mkqval(ctx, &match);
+ pushi(ctx->current, &qmatch, Q_CEQW, &tag, &id, NULL);
+ pushi(ctx->current, NULL, Q_JNZ, &qmatch, &bmatch, &bnexttag, NULL);
push(&ctx->current->body, &lnexttag);
}
pushi(ctx->current, NULL, Q_JMP, &bnext, NULL);
+ return match;
}
static struct gen_value
@@ -2283,7 +2304,8 @@ gen_match_with_tagged(struct gen_context *ctx,
} else {
compat = COMPAT_MISALIGNED;
}
- gen_subset_match_tests(ctx, bmatch, bnext, tag, _case);
+ const struct type *casetype = type_dealias(_case->type);
+ gen_subset_match_tests(ctx, bmatch, bnext, tag, casetype);
}
push(&ctx->current->body, &lmatch);
diff --git a/src/types.c b/src/types.c
@@ -869,13 +869,21 @@ type_is_assignable(const struct type *to, const struct type *from)
}
static bool
-castable_from_tagged(const struct type *to, const struct type *from)
+is_castable_with_tagged(const struct type *to, const struct type *from)
{
- // TODO: This may need to be expanded upon
- from = type_dealias(from);
- for (const struct type_tagged_union *tu = &from->tagged;
- tu; tu = tu->next) {
- if (tu->type->id == to->id) {
+ if (type_dealias(from)->storage == STORAGE_TAGGED
+ && type_dealias(to)->storage == STORAGE_TAGGED) {
+ if (tagged_subset_compat(to, from) || tagged_subset_compat(from, to)) {
+ return true;
+ }
+ }
+ if (type_dealias(to)->storage == STORAGE_TAGGED) {
+ if (tagged_select_subtype(to, from) != NULL) {
+ return true;
+ }
+ }
+ if (type_dealias(from)->storage == STORAGE_TAGGED) {
+ if (tagged_select_subtype(from, to) != NULL) {
return true;
}
}
@@ -889,13 +897,9 @@ type_is_castable(const struct type *to, const struct type *from)
return true;
}
- if (type_dealias(to)->storage == STORAGE_TAGGED) {
- return tagged_select_subtype(to, from) != NULL
- || tagged_subset_compat(to, from);
- }
-
- if (type_dealias(from)->storage == STORAGE_TAGGED) {
- return castable_from_tagged(to, from);
+ if (type_dealias(from)->storage == STORAGE_TAGGED
+ || type_dealias(to)->storage == STORAGE_TAGGED) {
+ return is_castable_with_tagged(to, from);
}
to = type_dealias(to), from = type_dealias(from);
diff --git a/tests/13-tagged.ha b/tests/13-tagged.ha
@@ -129,6 +129,7 @@ fn membercast() void = {
fn subsetcast() void = {
// Equal alignment
+ // subset -> superset
let x: (size | void) = 1337z;
let y: (size | int | void) = x;
let p = &y: *struct {
@@ -140,8 +141,14 @@ fn subsetcast() void = {
};
assert(p.tag == 1737287038);
assert(p.data.z == 1337z);
+ // superset -> subset
+ let x: (size | void | int) = 2z;
+ assert(x: (size | void) as size == 2);
+ assert(x as (size | void) as size == 2);
+ assert(x is (size | void) && (x is size) && !(x is void));
// Disjoint alignment
+ // subset -> superset
let x: (int | void) = 1337;
let y: (size | int | void) = x;
let p = &y: *struct {
@@ -153,12 +160,18 @@ fn subsetcast() void = {
};
assert(p.tag == 1099590421);
assert(p.data.i == 1337);
+ // superset -> subset
+ let x: (size | int | void) = 2i;
+ assert(x: (int | void) as int == 2);
+ assert(x as (int | void) as int == 2);
+ assert(x is (int | void) && (x is int) && !(x is void));
};
fn castout() void = {
let x: (int | void) = 1337;
- let y = x: int;
- assert(y == 1337);
+ assert(x: int == 1337);
+ assert(x as int == 1337);
+ assert(x is int);
// XXX: We can probably expand this
};
@@ -168,6 +181,40 @@ fn assertions() void = {
assert(a as u16 == 42u16);
};
+fn reject() void = {
+ // cannot type assert into a disjoint tagged type
+ assert(rt::compile(
+ "fn test() void = {
+ let a: (u8 | u16) = 42u8;
+ let b = a as (str | void);
+ };"
+ ) != 0);
+
+ // cannot type assert into non-member type
+ assert(rt::compile(
+ "fn test() void = {
+ let a: (u8 | u16) = 42u8;
+ let b = a as *str;
+ };"
+ ) != 0);
+
+ // cannot type assert into superset
+ assert(rt::compile(
+ "fn test() void = {
+ let a: (u8 | u16) = 42u8;
+ let b = a as (u8 | u16 | void);
+ };"
+ ) != 0);
+
+ // cannot type assert into the same type
+ assert(rt::compile(
+ "fn test() void = {
+ let a: (u8 | u16) = 42u8;
+ let b = a as (u8 | u16);
+ };"
+ ) != 0);
+};
+
export fn main() void = {
measurements();
storage();
@@ -178,4 +225,5 @@ export fn main() void = {
subsetcast();
castout();
assertions();
+ reject();
};