https://gcc.gnu.org/g:d90bc2bc3af79d28bc62767dc8aa7696000a94f0
commit r17-5435-gd90bc2bc3af79d28bc62767dc8aa7696000a94f0 Author: Utkarsh Bahuguna <[email protected]> Date: Mon Oct 5 13:12:29 2026 +0530 gccrs: Implement match arm guards 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]> Diff: --- gcc/rust/backend/rust-compile-expr.cc | 23 ++++++ gcc/rust/checks/errors/rust-const-checker.cc | 6 +- .../checks/errors/rust-hir-pattern-analysis.cc | 6 +- gcc/rust/checks/errors/rust-unsafe-checker.cc | 6 +- gcc/rust/typecheck/rust-hir-type-check-expr.cc | 16 ++++ .../rust/compile/match-guard-exhaustive.rs | 18 +++++ gcc/testsuite/rust/compile/match-guard-type.rs | 9 +++ gcc/testsuite/rust/compile/match-guard-unsafe.rs | 13 ++++ gcc/testsuite/rust/execute/torture/match-guard.rs | 91 ++++++++++++++++++++++ 9 files changed, 185 insertions(+), 3 deletions(-) diff --git a/gcc/rust/backend/rust-compile-expr.cc b/gcc/rust/backend/rust-compile-expr.cc index 54ea68f3b35f..8c8268bc31e2 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 1147c4d5568c..a73941231c9e 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 33461d918141..456b3afb9b1f 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 d7b24765bd6d..b673ea5be50c 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 b1fc902e4096..f420eb6f0a02 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 000000000000..0538828bd29a --- /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 000000000000..6bbe1fc69eca --- /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 000000000000..54ab7beb235f --- /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 000000000000..3dce362a197f --- /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 +}
