From 86680279fa81e4ba5ea972895fbad52d236975ab Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 10 Feb 2025 16:20:33 +0100 Subject: [PATCH] Improve inference on `??` #1943. --- releasenotes.md | 1 + src/compiler/sema_expr.c | 33 +++++---- test/test_suite/cast/top_down_casts.c3t | 9 ++- test/test_suite/errors/else_checks.c3t | 38 +++++----- test/test_suite/errors/or_err_infer.c3t | 96 +++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 test/test_suite/errors/or_err_infer.c3t diff --git a/releasenotes.md b/releasenotes.md index e5083a2c2..83e0a4191 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -21,6 +21,7 @@ - Distinct inline void causes unexpected error if used in slice #1946. - Allow `fn int test() => @pool() { return 1; }` short function syntax usage #1906. - Test runner will also check for leaks. +- Improve inference on `??` #1943. ### Fixes - Fix issue requiring prefix on a generic interface declaration. diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 52eecfb31..0e647b273 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -55,14 +55,14 @@ static inline bool sema_expr_analyse_access(SemaContext *context, Expr *expr, bo bool lvalue); static inline bool sema_expr_analyse_compound_literal(SemaContext *context, Expr *expr); static inline bool sema_expr_analyse_builtin(SemaContext *context, Expr *expr, bool throw_error); -static inline bool sema_expr_analyse_binary(SemaContext *context, Expr *expr, bool *failed_ref); +static inline bool sema_expr_analyse_binary(SemaContext *context, Type *infer_type, Expr *expr, bool *failed_ref); static inline bool sema_expr_resolve_ct_eval(SemaContext *context, Expr *expr); static inline bool sema_expr_analyse_identifier(SemaContext *context, Type *to, Expr *expr); static inline bool sema_expr_analyse_ct_identifier(SemaContext *context, Expr *expr); static inline bool sema_expr_analyse_ternary(SemaContext *context, Type *infer_type, Expr *expr); static inline bool sema_expr_analyse_cast(SemaContext *context, Expr *expr, bool *invalid_cast_ref); -static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, Expr *left, Expr *right); +static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *infer_type); static inline bool sema_expr_analyse_unary(SemaContext *context, Expr *expr, bool *failed_ref, CheckType check); static inline bool sema_expr_analyse_embed(SemaContext *context, Expr *expr, bool allow_fail); @@ -6007,7 +6007,7 @@ static bool sema_binary_analyse_ct_op_assign(SemaContext *context, Expr *expr, E } expr->binary_expr.operator = binaryop_assign_base_op(expr->binary_expr.operator); - if (!sema_expr_analyse_binary(context, expr, NULL)) return false; + if (!sema_expr_analyse_binary(context, NULL, expr, NULL)) return false; expr->resolve_status = RESOLVE_DONE; if (!sema_cast_const(expr)) @@ -6041,7 +6041,7 @@ static bool sema_binary_analyse_ct_subscript_op_assign(SemaContext *context, Exp BinaryOp op = binaryop_assign_base_op(expr->binary_expr.operator); expr->binary_expr = (ExprBinary) { .left = exprid(value), .right = expr->binary_expr.right, .operator = op }; - if (!sema_expr_analyse_binary(context, expr, NULL)) return false; + if (!sema_expr_analyse_binary(context, NULL, expr, NULL)) return false; if (!sema_expr_analyse_ct_subscript_set_value(context, left, left_var, expr)) return false; return true; @@ -7862,13 +7862,12 @@ INLINE bool expr_is_ungrouped_ternary(Expr *expr) return expr->expr_kind == EXPR_TERNARY && !expr->ternary_expr.grouped; } -static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, Expr *left, Expr *right) +static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *infer_type) { bool lhs_is_embed = left->expr_kind == EXPR_EMBED; if (expr_is_ungrouped_ternary(left) || expr_is_ungrouped_ternary(right)) { - SEMA_ERROR(expr, "Unclear precedence using ternary with ??, please use () to remove ambiguity."); - return false; + RETURN_SEMA_ERROR(expr, "Unclear precedence using ternary with ??, please use () to remove ambiguity."); } if (lhs_is_embed) { @@ -7876,7 +7875,7 @@ static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, } else { - if (!sema_analyse_expr(context, left)) return false; + if (!sema_analyse_inferred_expr(context, infer_type, left)) return false; } Type *type = left->type; @@ -7888,13 +7887,12 @@ static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, return true; } RETURN_SEMA_ERROR(left, "No optional to use '\?\?' with, please remove the '\?\?'."); - return false; } bool active_scope_jump = context->active_scope.jump_end; // First we analyse the "else" and try to implictly cast. - if (!sema_analyse_expr(context, right)) return false; + if (!sema_analyse_inferred_expr(context, infer_type, right)) return false; if (left->expr_kind == EXPR_OPTIONAL) { @@ -7916,9 +7914,7 @@ static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr, Type *common = type_find_max_type(type, else_type); if (!common) { - SEMA_ERROR(right, "Cannot find a common type for %s and %s.", type_quoted_error_string(type), - type_quoted_error_string(else_type)); - return false; + RETURN_SEMA_ERROR(right, "Cannot find a common type for %s and %s.", type_quoted_error_string(type), type_quoted_error_string(else_type)); } if (!cast_implicit(context, left, common, false)) return false; if (!cast_implicit(context, right, common, false)) return false; @@ -7943,7 +7939,7 @@ static inline bool sema_expr_analyse_ct_and_or(SemaContext *context, Expr *expr, return true; } -static inline bool sema_expr_analyse_binary(SemaContext *context, Expr *expr, bool *failed_ref) +static inline bool sema_expr_analyse_binary(SemaContext *context, Type *infer_type, Expr *expr, bool *failed_ref) { ASSERT_SPAN(expr, expr->resolve_status == RESOLVE_RUNNING); Expr *left = exprptr(expr->binary_expr.left); @@ -7958,7 +7954,7 @@ static inline bool sema_expr_analyse_binary(SemaContext *context, Expr *expr, bo switch (operator) { case BINARYOP_ELSE: - return sema_expr_analyse_or_error(context, expr, left, right); + return sema_expr_analyse_or_error(context, expr, left, right, infer_type); case BINARYOP_CT_CONCAT: return sema_expr_analyse_ct_concat(context, expr, left, right); case BINARYOP_CT_OR: @@ -9340,7 +9336,7 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr UNREACHABLE case EXPR_BINARY: main_expr->resolve_status = RESOLVE_RUNNING; - if (!sema_expr_analyse_binary(context, main_expr, &failed)) + if (!sema_expr_analyse_binary(context, NULL, main_expr, &failed)) { if (!failed) goto FAIL; success = false; @@ -9942,7 +9938,7 @@ static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr, if (!sema_expr_resolve_ct_eval(context, expr)) return false; return sema_analyse_expr_dispatch(context, expr, check); case EXPR_BINARY: - return sema_expr_analyse_binary(context, expr, NULL); + return sema_expr_analyse_binary(context, NULL, expr, NULL); case EXPR_TERNARY: return sema_expr_analyse_ternary(context, NULL, expr); case EXPR_UNARY: @@ -10591,6 +10587,9 @@ RETRY: case EXPR_LAMBDA: if (!sema_expr_analyse_lambda(context, to, expr)) return expr_poison(expr); break; + case EXPR_BINARY: + if (!sema_expr_analyse_binary(context, to, expr, NULL)) return expr_poison(expr); + break; case EXPR_TERNARY: if (!sema_expr_analyse_ternary(context, to, expr)) return expr_poison(expr); break; diff --git a/test/test_suite/cast/top_down_casts.c3t b/test/test_suite/cast/top_down_casts.c3t index 3790c1d67..80bbf9ce4 100644 --- a/test/test_suite/cast/top_down_casts.c3t +++ b/test/test_suite/cast/top_down_casts.c3t @@ -82,14 +82,17 @@ entry: %not_err = icmp eq i64 %optval, 0 %18 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) br i1 %18, label %after_check, label %else_block + after_check: ; preds = %entry %19 = load i32, ptr %w, align 4 + %sext13 = sext i32 %19 to i64 br label %phi_block + else_block: ; preds = %entry br label %phi_block + phi_block: ; preds = %else_block, %after_check - %val = phi i32 [ %19, %after_check ], [ 1, %else_block ] - %sext13 = sext i32 %val to i64 - store i64 %sext13, ptr %z, align 8 + %val = phi i64 [ %sext13, %after_check ], [ 1, %else_block ] + store i64 %val, ptr %z, align 8 ret void } \ No newline at end of file diff --git a/test/test_suite/errors/else_checks.c3t b/test/test_suite/errors/else_checks.c3t index 9e51a873f..26a4e405d 100644 --- a/test/test_suite/errors/else_checks.c3t +++ b/test/test_suite/errors/else_checks.c3t @@ -57,45 +57,45 @@ phi_block: ; preds = %else_block, %after_ %6 = call i64 @testError(ptr %retparam5) %not_err6 = icmp eq i64 %6, 0 %7 = call i1 @llvm.expect.i1(i1 %not_err6, i1 true) - br i1 %7, label %after_check7, label %else_block8 + br i1 %7, label %after_check7, label %else_block9 after_check7: ; preds = %phi_block %8 = load i32, ptr %retparam5, align 4 %shl = shl i32 1, %8 %9 = freeze i32 %shl - br label %phi_block9 + %sifp8 = sitofp i32 %9 to double + br label %phi_block10 -else_block8: ; preds = %phi_block - br label %phi_block9 +else_block9: ; preds = %phi_block + br label %phi_block10 -phi_block9: ; preds = %else_block8, %after_check7 - %val10 = phi i32 [ %9, %after_check7 ], [ 100, %else_block8 ] - %sifp11 = sitofp i32 %val10 to double - store double %sifp11, ptr %y, align 8 +phi_block10: ; preds = %else_block9, %after_check7 + %val11 = phi double [ %sifp8, %after_check7 ], [ 1.000000e+02, %else_block9 ] + store double %val11, ptr %y, align 8 %10 = call i64 @testError(ptr %retparam12) %not_err13 = icmp eq i64 %10, 0 %11 = call i1 @llvm.expect.i1(i1 %not_err13, i1 true) - br i1 %11, label %after_check14, label %else_block15 + br i1 %11, label %after_check14, label %else_block16 -after_check14: ; preds = %phi_block9 +after_check14: ; preds = %phi_block10 %12 = load i32, ptr %retparam12, align 4 %ashr = ashr i32 %12, 1 %13 = freeze i32 %ashr - br label %phi_block16 + %sifp15 = sitofp i32 %13 to double + br label %phi_block17 -else_block15: ; preds = %phi_block9 - br label %phi_block16 +else_block16: ; preds = %phi_block10 + br label %phi_block17 -phi_block16: ; preds = %else_block15, %after_check14 - %val17 = phi i32 [ %13, %after_check14 ], [ 100, %else_block15 ] - %sifp18 = sitofp i32 %val17 to double - store double %sifp18, ptr %z, align 8 +phi_block17: ; preds = %else_block16, %after_check14 + %val18 = phi double [ %sifp15, %after_check14 ], [ 1.000000e+02, %else_block16 ] + store double %val18, ptr %z, align 8 %14 = call i64 @testError(ptr %retparam19) %not_err20 = icmp eq i64 %14, 0 %15 = call i1 @llvm.expect.i1(i1 %not_err20, i1 true) br i1 %15, label %after_check21, label %else_block27 -after_check21: ; preds = %phi_block16 +after_check21: ; preds = %phi_block17 %16 = load i32, ptr %retparam19, align 4 %sifp22 = sitofp i32 %16 to double %17 = call i64 @testError(ptr %retparam23) @@ -109,7 +109,7 @@ after_check25: ; preds = %after_check21 %fmul = fmul double %sifp22, %sifp26 br label %phi_block28 -else_block27: ; preds = %after_check21, %phi_block16 +else_block27: ; preds = %after_check21, %phi_block17 br label %phi_block28 phi_block28: ; preds = %else_block27, %after_check25 diff --git a/test/test_suite/errors/or_err_infer.c3t b/test/test_suite/errors/or_err_infer.c3t new file mode 100644 index 000000000..cda307534 --- /dev/null +++ b/test/test_suite/errors/or_err_infer.c3t @@ -0,0 +1,96 @@ +// #target: macos-x64 +enum Foo +{ + ABC, + DEF +} + +fn int main() +{ + int a; + Foo! x = ABC; + Foo f = x ?? DEF; + return 0; +} + +/* expect: test.ll + +define void @top_down_casts.test() #0 { +entry: + %x = alloca i32, align 4 + %y = alloca i32, align 4 + %z = alloca i64, align 8 + %w = alloca i32, align 4 + %w.f = alloca i64, align 8 + store i32 0, ptr %x, align 4 + store i32 0, ptr %y, align 4 + %0 = load i32, ptr %x, align 4 + %sext = sext i32 %0 to i64 + %1 = load i32, ptr %y, align 4 + %sext1 = sext i32 %1 to i64 + %mul = mul i64 %sext, %sext1 + store i64 %mul, ptr %z, align 8 + %2 = load i32, ptr %x, align 4 + %3 = load i32, ptr %y, align 4 + %sdiv = sdiv i32 %2, %3 + %sext2 = sext i32 %sdiv to i64 + store i64 %sext2, ptr %z, align 8 + %4 = load i32, ptr %x, align 4 + %sext3 = sext i32 %4 to i64 + %5 = load i32, ptr %y, align 4 + %sext4 = sext i32 %5 to i64 + %add = add i64 %sext3, %sext4 + store i64 %add, ptr %z, align 8 + %6 = load i32, ptr %x, align 4 + %sext5 = sext i32 %6 to i64 + %7 = load i32, ptr %y, align 4 + %sext6 = sext i32 %7 to i64 + %sub = sub i64 %sext5, %sext6 + store i64 %sub, ptr %z, align 8 + %8 = load i32, ptr %x, align 4 + %9 = load i32, ptr %y, align 4 + %smod = srem i32 %8, %9 + %sext7 = sext i32 %smod to i64 + store i64 %sext7, ptr %z, align 8 + %10 = load i32, ptr %x, align 4 + %sext8 = sext i32 %10 to i64 + %11 = load i32, ptr %y, align 4 + %zext = zext i32 %11 to i64 + %shl = shl i64 %sext8, %zext + %12 = freeze i64 %shl + store i64 %12, ptr %z, align 8 + %13 = load i32, ptr %x, align 4 + %sext9 = sext i32 %13 to i64 + %14 = load i32, ptr %y, align 4 + %zext10 = zext i32 %14 to i64 + %ashr = ashr i64 %sext9, %zext10 + %15 = freeze i64 %ashr + store i64 %15, ptr %z, align 8 + %16 = load i32, ptr %x, align 4 + %bnot = xor i32 %16, -1 + %sext11 = sext i32 %bnot to i64 + store i64 %sext11, ptr %z, align 8 + %17 = load i32, ptr %x, align 4 + %neg = sub i32 0, %17 + %sext12 = sext i32 %neg to i64 + store i64 %sext12, ptr %z, align 8 + store i64 0, ptr %w.f, align 8 + store i32 0, ptr %w, align 4 + %optval = load i64, ptr %w.f, align 8 + %not_err = icmp eq i64 %optval, 0 + %18 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %18, label %after_check, label %else_block + +after_check: ; preds = %entry + %19 = load i32, ptr %w, align 4 + %sext13 = sext i32 %19 to i64 + br label %phi_block + +else_block: ; preds = %entry + br label %phi_block + +phi_block: ; preds = %else_block, %after_check + %val = phi i64 [ %sext13, %after_check ], [ 1, %else_block ] + store i64 %val, ptr %z, align 8 + ret void +} \ No newline at end of file