From: Utkarsh Bahuguna <[email protected]>

Guards were never type checked or compiled, so an arm with a guard was
taken whenever its pattern matched. Check the guard as a bool, evaluate
it after the arm's bindings, and fall through to the next arm when it is
false. The unsafe, const and pattern checkers now visit guards too.

Fixes Rust-GCC/gccrs#4957

gcc/rust/ChangeLog:

        * typecheck/rust-hir-type-check-expr.cc
        (TypeCheckExpr::visit (MatchExpr)): Type check match arm guards as
        bool.
        * backend/rust-compile-expr.cc (CompileExpr::visit (MatchExpr)): Only
        run the arm body when its guard holds.
        * checks/errors/rust-unsafe-checker.cc
        (UnsafeChecker::visit (MatchExpr)): Visit match arm guards.
        * checks/errors/rust-const-checker.cc
        (ConstChecker::visit (MatchExpr)): Likewise.
        * checks/errors/rust-hir-pattern-analysis.cc
        (PatternChecker::visit (MatchExpr)): Likewise.

gcc/testsuite/ChangeLog:

        * rust/compile/match-guard-exhaustive.rs: New test.
        * rust/compile/match-guard-type.rs: New test.
        * rust/compile/match-guard-unsafe.rs: New test.
        * rust/execute/torture/match-guard.rs: New test.

Signed-off-by: Utkarsh Bahuguna <[email protected]>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.


Commit on github: 
https://github.com/Rust-GCC/gccrs/commit/7cadcacdd2821e2357b0866a4c1720baeba1d9f3

The commit has been mentioned in the following issue(s):
 - Rust-GCC/gccrs#4957: https://github.com/Rust-GCC/gccrs/issues/4957

The commit has been mentioned in the following pull-request(s):
 - https://github.com/Rust-GCC/gccrs/pull/4959

 gcc/rust/backend/rust-compile-expr.cc         | 23 +++++
 gcc/rust/checks/errors/rust-const-checker.cc  |  6 +-
 .../errors/rust-hir-pattern-analysis.cc       |  6 +-
 gcc/rust/checks/errors/rust-unsafe-checker.cc |  6 +-
 .../typecheck/rust-hir-type-check-expr.cc     | 16 ++++
 .../rust/compile/match-guard-exhaustive.rs    | 18 ++++
 .../rust/compile/match-guard-type.rs          |  9 ++
 .../rust/compile/match-guard-unsafe.rs        | 13 +++
 .../rust/execute/torture/match-guard.rs       | 91 +++++++++++++++++++
 9 files changed, 185 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/rust/compile/match-guard-exhaustive.rs
 create mode 100644 gcc/testsuite/rust/compile/match-guard-type.rs
 create mode 100644 gcc/testsuite/rust/compile/match-guard-unsafe.rs
 create mode 100644 gcc/testsuite/rust/execute/torture/match-guard.rs

diff --git a/gcc/rust/backend/rust-compile-expr.cc 
b/gcc/rust/backend/rust-compile-expr.cc
index 54ea68f3b..8c8268bc3 100644
--- a/gcc/rust/backend/rust-compile-expr.cc
+++ b/gcc/rust/backend/rust-compile-expr.cc
@@ -1530,6 +1530,19 @@ CompileExpr::visit (HIR::MatchExpr &expr)
       CompilePatternBindings::Compile (*kase_pattern, match_scrutinee_expr,
                                       ctx);
 
+      // The guard is evaluated once the bindings are set up. The arm body
+      // goes into its own block that only runs when the guard holds, and
+      // when it does not the arm falls through to the next one.
+      tree guard_expr = NULL_TREE;
+      tree guarded_block = NULL_TREE;
+      if (kase_arm.has_match_arm_guard ())
+       {
+         guard_expr = CompileExpr::Compile (kase_arm.get_guard_expr (), ctx);
+         guarded_block = Backend::block (fndecl, ctx->peek_enclosing_scope (),
+                                         {}, start_location, end_location);
+         ctx->push_block (guarded_block);
+       }
+
       // compile the expr and setup the assignment if required when tmp !=
       // NULL
       location_t arm_locus = kase_arm.get_locus ();
@@ -1556,6 +1569,16 @@ CompileExpr::visit (HIR::MatchExpr &expr)
        = build1_loc (arm_locus, GOTO_EXPR, void_type_node, end_label);
       ctx->add_statement (goto_end_label);
 
+      if (guarded_block != NULL_TREE)
+       {
+         ctx->pop_block ();
+         tree guard_stmt
+           = Backend::if_statement (NULL_TREE, guard_expr, guarded_block,
+                                    NULL_TREE,
+                                    kase_arm.get_guard_expr ().get_locus ());
+         ctx->add_statement (guard_stmt);
+       }
+
       ctx->pop_block ();
 
       tree check_expr
diff --git a/gcc/rust/checks/errors/rust-const-checker.cc 
b/gcc/rust/checks/errors/rust-const-checker.cc
index 1147c4d55..a73941231 100644
--- a/gcc/rust/checks/errors/rust-const-checker.cc
+++ b/gcc/rust/checks/errors/rust-const-checker.cc
@@ -525,7 +525,11 @@ ConstChecker::visit (MatchExpr &expr)
   expr.get_scrutinee_expr ().accept_vis (*this);
 
   for (auto &match_arm : expr.get_match_cases ())
-    match_arm.get_expr ().accept_vis (*this);
+    {
+      if (match_arm.get_arm ().has_match_arm_guard ())
+       match_arm.get_arm ().get_guard_expr ().accept_vis (*this);
+      match_arm.get_expr ().accept_vis (*this);
+    }
 }
 
 void
diff --git a/gcc/rust/checks/errors/rust-hir-pattern-analysis.cc 
b/gcc/rust/checks/errors/rust-hir-pattern-analysis.cc
index 33461d918..456b3afb9 100644
--- a/gcc/rust/checks/errors/rust-hir-pattern-analysis.cc
+++ b/gcc/rust/checks/errors/rust-hir-pattern-analysis.cc
@@ -396,7 +396,11 @@ PatternChecker::visit (MatchExpr &expr)
   expr.get_scrutinee_expr ().accept_vis (*this);
 
   for (auto &match_arm : expr.get_match_cases ())
-    match_arm.get_expr ().accept_vis (*this);
+    {
+      if (match_arm.get_arm ().has_match_arm_guard ())
+       match_arm.get_arm ().get_guard_expr ().accept_vis (*this);
+      match_arm.get_expr ().accept_vis (*this);
+    }
 
   // match expressions are only an entrypoint
   TyTy::BaseType *scrutinee_ty;
diff --git a/gcc/rust/checks/errors/rust-unsafe-checker.cc 
b/gcc/rust/checks/errors/rust-unsafe-checker.cc
index d7b24765b..b673ea5be 100644
--- a/gcc/rust/checks/errors/rust-unsafe-checker.cc
+++ b/gcc/rust/checks/errors/rust-unsafe-checker.cc
@@ -688,7 +688,11 @@ UnsafeChecker::visit (MatchExpr &expr)
   expr.get_scrutinee_expr ().accept_vis (*this);
 
   for (auto &match_arm : expr.get_match_cases ())
-    match_arm.get_expr ().accept_vis (*this);
+    {
+      if (match_arm.get_arm ().has_match_arm_guard ())
+       match_arm.get_arm ().get_guard_expr ().accept_vis (*this);
+      match_arm.get_expr ().accept_vis (*this);
+    }
 }
 
 void
diff --git a/gcc/rust/typecheck/rust-hir-type-check-expr.cc 
b/gcc/rust/typecheck/rust-hir-type-check-expr.cc
index b1fc902e4..f420eb6f0 100644
--- a/gcc/rust/typecheck/rust-hir-type-check-expr.cc
+++ b/gcc/rust/typecheck/rust-hir-type-check-expr.cc
@@ -1868,6 +1868,22 @@ TypeCheckExpr::visit (HIR::MatchExpr &expr)
          continue;
        }
 
+      // the guard is a bool condition evaluated with the bindings of the
+      // pattern in scope
+      if (kase_arm.has_match_arm_guard ())
+       {
+         TyTy::BaseType *bool_ty = nullptr;
+         bool ok = context->lookup_builtin ("bool", &bool_ty);
+         rust_assert (ok);
+
+         HIR::Expr &guard = kase_arm.get_guard_expr ();
+         TyTy::BaseType *guard_ty = TypeCheckExpr::Resolve (guard);
+         unify_site (guard.get_mappings ().get_hirid (),
+                     TyTy::TyWithLocation (bool_ty),
+                     TyTy::TyWithLocation (guard_ty, guard.get_locus ()),
+                     guard.get_locus ());
+       }
+
       // check the kase type
       TyTy::BaseType *kase_block_ty = TypeCheckExpr::Resolve (kase.get_expr 
());
       kase_block_tys.push_back (kase_block_ty);
diff --git a/gcc/testsuite/rust/compile/match-guard-exhaustive.rs 
b/gcc/testsuite/rust/compile/match-guard-exhaustive.rs
new file mode 100644
index 000000000..0538828bd
--- /dev/null
+++ b/gcc/testsuite/rust/compile/match-guard-exhaustive.rs
@@ -0,0 +1,18 @@
+#![feature(no_core)]
+#![no_core]
+
+enum T {
+    A(),
+    B(),
+}
+
+fn main() -> i32 {
+    let t = T::A();
+    match 0 {
+        0 if match t {
+            // { dg-error "non-exhaustive patterns: 'T::B..' not covered" "" { 
target *-*-* } .-1 }
+            T::A() => true,
+        } => 1,
+        _ => 0,
+    }
+}
diff --git a/gcc/testsuite/rust/compile/match-guard-type.rs 
b/gcc/testsuite/rust/compile/match-guard-type.rs
new file mode 100644
index 000000000..6bbe1fc69
--- /dev/null
+++ b/gcc/testsuite/rust/compile/match-guard-type.rs
@@ -0,0 +1,9 @@
+#![feature(no_core)]
+#![no_core]
+
+fn main() -> i32 {
+    match 0 {
+        0 if 5 => 10, // { dg-error "mismatched types" }
+        _ => 20,
+    }
+}
diff --git a/gcc/testsuite/rust/compile/match-guard-unsafe.rs 
b/gcc/testsuite/rust/compile/match-guard-unsafe.rs
new file mode 100644
index 000000000..54ab7beb2
--- /dev/null
+++ b/gcc/testsuite/rust/compile/match-guard-unsafe.rs
@@ -0,0 +1,13 @@
+#![feature(no_core)]
+#![no_core]
+
+unsafe fn check() -> bool {
+    true
+}
+
+fn main() -> i32 {
+    match 0 {
+        0 if check() => 1, // { dg-error "call to unsafe function requires 
unsafe function or block" }
+        _ => 0,
+    }
+}
diff --git a/gcc/testsuite/rust/execute/torture/match-guard.rs 
b/gcc/testsuite/rust/execute/torture/match-guard.rs
new file mode 100644
index 000000000..3dce362a1
--- /dev/null
+++ b/gcc/testsuite/rust/execute/torture/match-guard.rs
@@ -0,0 +1,91 @@
+// Match guards are evaluated after the bindings of the arm, and a false
+// guard falls through to the next arm.
+#![feature(no_core)]
+#![no_core]
+
+static mut CALLS: i32 = 0;
+
+fn even(x: i32) -> bool {
+    unsafe { CALLS += 1 };
+    x % 2 == 0
+}
+
+fn never() -> bool {
+    false
+}
+
+fn pick(n: i32) -> i32 {
+    match n {
+        x if even(x) => 1,
+        x if x > 100 => 2,
+        _ => 3,
+    }
+}
+
+enum E {
+    A(i32),
+    B(i32, i32),
+    C,
+}
+
+fn variant(e: E) -> i32 {
+    match e {
+        E::A(v) if v < 0 => 10,
+        E::A(v) => v,
+        E::B(a, b) if a == b => 20,
+        E::B(_, _) => 21,
+        E::C => 30,
+    }
+}
+
+fn tuple(t: (i32, bool)) -> i32 {
+    match t {
+        (n, true) if n > 5 => 1,
+        (_, true) => 2,
+        (0, false) | (1, false) if never() => 3,
+        _ => 4,
+    }
+}
+
+fn unit_arm(n: i32) -> i32 {
+    let mut r = 0;
+    match n {
+        1 if r == 0 => r = 5,
+        _ => r = 6,
+    }
+    r
+}
+
+fn main() -> i32 {
+    match 0 {
+        0 if never() => return 1,
+        _ => {}
+    }
+
+    if pick(7) != 3 || pick(8) != 1 || pick(101) != 2 {
+        return 2;
+    }
+    // even() runs for every arm it is reached in: 7, 8 and 101
+    if unsafe { CALLS } != 3 {
+        return 3;
+    }
+
+    if variant(E::A(-1)) != 10 || variant(E::A(4)) != 4 {
+        return 4;
+    }
+    if variant(E::B(2, 2)) != 20 || variant(E::B(2, 3)) != 21
+        || variant(E::C) != 30
+    {
+        return 5;
+    }
+
+    if tuple((9, true)) != 1 || tuple((1, true)) != 2 || tuple((0, false)) != 
4 {
+        return 6;
+    }
+
+    if unit_arm(1) != 5 || unit_arm(2) != 6 {
+        return 7;
+    }
+
+    0
+}

base-commit: 36b5a1512938d9f2d6b23c29404ac6baca9dab3c
-- 
2.55.0

Reply via email to