commit 6270d3a795320fa6f51e874c68a7ba100cac76ad
parent fcccb4783b405a27d6073534e181cb77efd4d477
Author: Drew DeVault <sir@cmpwn.com>
Date: Sun, 3 Jan 2021 11:10:08 -0500
check: lower implicit casts
Diffstat:
5 files changed, 156 insertions(+), 11 deletions(-)
diff --git a/src/check.c b/src/check.c
@@ -38,6 +38,21 @@ expect(const struct location *loc, bool constraint, char *fmt, ...)
}
}
+static struct expression *
+lower_implicit_cast(const struct type *to, struct expression *expr)
+{
+ if (to == expr->result) {
+ return expr;
+ }
+ struct expression *cast = xcalloc(1, sizeof(struct expression));
+ cast->type = EXPR_CAST;
+ cast->result = to;
+ cast->terminates = expr->terminates;
+ cast->cast.kind = C_CAST;
+ cast->cast.value = expr;
+ return cast;
+}
+
void check_expression(struct context *ctx,
const struct ast_expression *aexpr, struct expression *expr);
@@ -145,8 +160,6 @@ check_expr_assign(struct context *ctx,
check_expression(ctx, aexpr->assign.value, value);
expr->assign.op = aexpr->assign.op;
- expr->assign.object = object;
- expr->assign.value = value;
if (aexpr->assign.indirect) {
expect(&aexpr->loc,
@@ -160,6 +173,7 @@ check_expr_assign(struct context *ctx,
object->result->pointer.referent,
value->result),
"Value type is not assignable to pointer type");
+ value = lower_implicit_cast(object->result->pointer.referent, value);
} else {
assert(object->type == EXPR_ACCESS); // Invariant
expect(&aexpr->loc, !(object->result->flags & TYPE_CONST),
@@ -167,7 +181,11 @@ check_expr_assign(struct context *ctx,
expect(&aexpr->loc,
type_is_assignable(&ctx->store, object->result, value->result),
"rvalue type is not assignable to lvalue");
+ value = lower_implicit_cast(object->result, value);
}
+
+ expr->assign.object = object;
+ expr->assign.value = value;
}
static void
@@ -260,7 +278,8 @@ check_expr_binding(struct context *ctx,
const struct scope_object *obj = scope_insert(
ctx->scope, O_BIND, &ident, &ident, type, NULL);
binding->object = obj;
- binding->initializer = initializer;
+ binding->initializer =
+ lower_implicit_cast(type, initializer);
if (abinding->next) {
binding = *next =
@@ -305,6 +324,9 @@ check_expr_call(struct context *ctx,
type_is_assignable(&ctx->store,
param->type, arg->value->result),
"Argument is not assignable to parameter type");
+ if (param->type != arg->value->result) {
+ arg->value = lower_implicit_cast(param->type, arg->value);
+ }
aarg = aarg->next;
param = param->next;
@@ -496,11 +518,20 @@ check_expr_if(struct context *ctx,
false_branch = xcalloc(1, sizeof(struct expression));
check_expression(ctx, aexpr->_if.false_branch, false_branch);
- // TODO: Tagged unions:
- assert(true_branch->result == false_branch->result);
- expr->result = true_branch->result;
+ if (true_branch->terminates && false_branch->terminates) {
+ expr->result = &builtin_type_void;
+ } else if (true_branch->terminates) {
+ expr->result = false_branch->result;
+ } else if (false_branch->terminates) {
+ expr->result = true_branch->result;
+ } else {
+ // TODO: Tagged unions
+ assert(true_branch->result == false_branch->result);
+ expr->result = true_branch->result;
+ }
} else {
expr->result = &builtin_type_void;
+ expr->terminates = true_branch->terminates;
}
expect(&aexpr->_if.cond->loc,
@@ -595,10 +626,14 @@ check_expr_return(struct context *ctx,
if (aexpr->_return.value) {
struct expression *rval = xcalloc(1, sizeof(struct expression));
check_expression(ctx, aexpr->_return.value, rval);
- expr->_return.value = rval;
expect(&aexpr->_return.value->loc,
type_is_assignable(&ctx->store, ctx->current_fntype->func.result, rval->result),
"Return value is not assignable to function result type");
+ if (ctx->current_fntype->func.result != rval->result) {
+ rval = lower_implicit_cast(
+ ctx->current_fntype->func.result, rval);
+ }
+ expr->_return.value = rval;
}
trleave(TR_CHECK, NULL);
@@ -775,11 +810,14 @@ check_function(struct context *ctx,
struct expression *body = xcalloc(1, sizeof(struct expression));
check_expression(ctx, afndecl->body, body);
- decl->func.body = body;
expect(&afndecl->body->loc,
body->terminates || type_is_assignable(&ctx->store, fntype->func.result, body->result),
"Result value is not assignable to function result type");
+ if (!body->terminates && fntype->func.result != body->result) {
+ body = lower_implicit_cast(fntype->func.result, body);
+ }
+ decl->func.body = body;
// TODO: Add function name to errors
if ((decl->func.flags & FN_INIT)
diff --git a/src/gen.c b/src/gen.c
@@ -359,7 +359,6 @@ gen_expr_assert(struct gen_context *ctx,
const struct expression *expr,
const struct qbe_value *out)
{
- assert(!out); // Invariant
assert(expr->assert.message); // Invariant
struct qbe_statement failedl = {0}, passedl = {0};
@@ -587,6 +586,11 @@ gen_expr_cast(struct gen_context *ctx,
{
const struct type *to = expr->result,
*from = expr->cast.value->result;
+ if (to->storage == from->storage) {
+ gen_expression(ctx, expr->cast.value, out);
+ return;
+ }
+
bool is_signed = type_is_signed(from);
struct qbe_value in = {0}, result = {0};
@@ -669,8 +673,9 @@ gen_expr_cast(struct gen_context *ctx,
case TYPE_STORAGE_STRING:
case TYPE_STORAGE_STRUCT:
case TYPE_STORAGE_UNION:
- case TYPE_STORAGE_VOID:
assert(0); // Invariant
+ case TYPE_STORAGE_VOID:
+ return; // no-op
}
gen_store(ctx, out, &result);
diff --git a/src/type_store.c b/src/type_store.c
@@ -85,6 +85,10 @@ type_is_assignable(struct type_store *store,
return to->pointer.flags & PTR_NULLABLE;
}
return true;
+ case TYPE_STORAGE_STRING:
+ return to->pointer.referent->storage == TYPE_STORAGE_CHAR
+ // TODO: const transitivity
+ && to->pointer.referent->flags & TYPE_CONST;
default:
return false;
}
diff --git a/tests/05-implicit-casts.ha b/tests/05-implicit-casts.ha
@@ -0,0 +1,97 @@
+fn rt::compile(src: str) int;
+
+fn rules() void = {
+ // Fixed precision ints
+ let _i64: i64 = 0i64;
+ _i64 = 42i8;
+ _i64 = 42i16;
+ _i64 = 42i32;
+ _i64 = 42i;
+ let _i32: i32 = 0i32;
+ _i32 = 42i8;
+ _i32 = 42i16;
+ let _i16: i16 = 0i16;
+ _i16 = 42i8;
+ let _u64: u64 = 0u64;
+ _u64 = 42u8;
+ _u64 = 42u16;
+ _u64 = 42u32;
+ _u64 = 42u;
+ let _u32: u32 = 0u32;
+ _u32 = 42u8;
+ _u32 = 42u16;
+ let _u16: u16 = 0u16;
+ _u16 = 42u8;
+
+ // Implementation-defined precision
+ if (size(int) == 8z) {
+ assert(rt::compile("fn test() void = { let i: int = 42i64; };") == 0);
+ };
+ let i: int = 42i;
+ i = 42i32;
+ i = 42i16;
+ i = 42i8;
+
+ if (size(uint) == 8z) {
+ assert(rt::compile("fn test() void = { let u: uint = 42u64; };") == 0);
+ };
+ let u: uint = 42u;
+ u = 42u32;
+ u = 42u16;
+ u = 42u8;
+
+ // Precision loss (should fail)
+ assert(rt::compile("fn test() void = { let _i8: i8 = 42i16; };") != 0);
+ assert(rt::compile("fn test() void = { let _i8: i8 = 42i32; };") != 0);
+ assert(rt::compile("fn test() void = { let _i8: i8 = 42i64; };") != 0);
+ assert(rt::compile("fn test() void = { let _i8: i8 = 42i; };") != 0);
+ assert(rt::compile("fn test() void = { let _i16: i16 = 42i32; };") != 0);
+ assert(rt::compile("fn test() void = { let _i16: i16 = 42i64; };") != 0);
+ assert(rt::compile("fn test() void = { let _i32: i32 = 42i64; };") != 0);
+ assert(rt::compile("fn test() void = { let _u8: u8 = 42u16; };") != 0);
+ assert(rt::compile("fn test() void = { let _u8: u8 = 42u32; };") != 0);
+ assert(rt::compile("fn test() void = { let _u8: u8 = 42u64; };") != 0);
+ assert(rt::compile("fn test() void = { let _u8: u8 = 42u; };") != 0);
+ assert(rt::compile("fn test() void = { let _u16: u16 = 42u32; };") != 0);
+ assert(rt::compile("fn test() void = { let _u16: u16 = 42u64; };") != 0);
+ assert(rt::compile("fn test() void = { let _u32: u32 = 42u64; };") != 0);
+
+ // Pointer conversions
+ let cchr: *const char = "hello world";
+ let nptr: nullable *int = null;
+ nptr = &i;
+ let vptr: nullable *void = nptr;
+
+ // Invalid pointer conversions
+ assert(rt::compile(
+ "fn test() void = { let x: nullable *int = null; let y: *int = x; };"
+ ) != 0);
+ assert(rt::compile(
+ "fn test() void = { let x: int = 10; let y: *int = &x; let y: *uint = x; };"
+ ) != 0);
+
+ // Non-const from const (copy)
+ const j = 10;
+ let k = j;
+};
+
+fn rvalue() i64 = {
+ return 1234;
+};
+
+fn callme(in: i64) void = {
+ assert(in == 1234i64);
+};
+
+fn calls() void = {
+ callme(1234);
+};
+
+export fn main() void = {
+ rules();
+ assert(rvalue() == 1234i64);
+ calls();
+ // TODO: Expand this:
+ // - Floats
+ // - Arrays <-> slices
+};
diff --git a/tests/configure b/tests/configure
@@ -7,7 +7,8 @@ tests() {
01-arrays \
02-integers \
03-pointers \
- 04-strings
+ 04-strings \
+ 05-implicit-casts
do
cat <<EOF
tests/$t: libhart.a tests/$t.ha