harec

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 6270d3a795320fa6f51e874c68a7ba100cac76ad
parent fcccb4783b405a27d6073534e181cb77efd4d477
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun,  3 Jan 2021 11:10:08 -0500

check: lower implicit casts

Diffstat:
Msrc/check.c | 54++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/gen.c | 9+++++++--
Msrc/type_store.c | 4++++
Atests/05-implicit-casts.ha | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/configure | 3++-
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