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