about summary refs log tree commit diff
diff options
context:
space:
mode:
authordianne <diannes.gm@gmail.com>2025-05-07 06:59:30 -0700
committerdianne <diannes.gm@gmail.com>2025-05-18 04:21:57 -0700
commitf0b8ec1d71f055cbdb741565eaddabc93bf1ae75 (patch)
treeb5a4b808c917d313c1533cd92f72a2f9ac589e7d
parent30a0ac66dbb15ee8dd3951499b34df48e1d758a0 (diff)
downloadrust-f0b8ec1d71f055cbdb741565eaddabc93bf1ae75.tar.gz
rust-f0b8ec1d71f055cbdb741565eaddabc93bf1ae75.zip
name resolution for guard patterns
-rw-r--r--compiler/rustc_ast/src/ast.rs2
-rw-r--r--compiler/rustc_resolve/src/late.rs36
-rw-r--r--tests/ui/feature-gates/feature-gate-guard-patterns.rs2
-rw-r--r--tests/ui/feature-gates/feature-gate-guard-patterns.stderr31
-rw-r--r--tests/ui/pattern/rfc-3637-guard-patterns/name-resolution.rs81
-rw-r--r--tests/ui/pattern/rfc-3637-guard-patterns/name-resolution.stderr133
6 files changed, 255 insertions, 30 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 114b9835b98..ab48a2899a7 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -611,7 +611,7 @@ impl Pat {
     /// Walk top-down and call `it` in each place where a pattern occurs
     /// starting with the root pattern `walk` is called on. If `it` returns
     /// false then we will descend no further but siblings will be processed.
-    pub fn walk(&self, it: &mut impl FnMut(&Pat) -> bool) {
+    pub fn walk<'ast>(&'ast self, it: &mut impl FnMut(&'ast Pat) -> bool) {
         if !it(self) {
             return;
         }
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index aa211a8f3c2..1b682d0cf8a 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -799,7 +799,14 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
     fn visit_pat(&mut self, p: &'ast Pat) {
         let prev = self.diag_metadata.current_pat;
         self.diag_metadata.current_pat = Some(p);
-        visit::walk_pat(self, p);
+
+        if let PatKind::Guard(subpat, _) = &p.kind {
+            // We walk the guard expression in `resolve_pattern_inner`. Don't resolve it twice.
+            self.visit_pat(subpat);
+        } else {
+            visit::walk_pat(self, p);
+        }
+
         self.diag_metadata.current_pat = prev;
     }
     fn visit_local(&mut self, local: &'ast Local) {
@@ -3922,7 +3929,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
     #[tracing::instrument(skip(self, bindings), level = "debug")]
     fn resolve_pattern_inner(
         &mut self,
-        pat: &Pat,
+        pat: &'ast Pat,
         pat_src: PatternSource,
         bindings: &mut PatternBindings,
     ) {
@@ -3982,6 +3989,31 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
                     // Prevent visiting `ps` as we've already done so above.
                     return false;
                 }
+                PatKind::Guard(ref subpat, ref guard) => {
+                    // Add a new set of bindings to the stack to collect bindings in `subpat`.
+                    bindings.push((PatBoundCtx::Product, Default::default()));
+                    // Resolving `subpat` adds bindings onto the newly-pushed context. After, the
+                    // total number of contexts on the stack should be the same as before.
+                    let binding_ctx_stack_len = bindings.len();
+                    self.resolve_pattern_inner(subpat, pat_src, bindings);
+                    assert_eq!(bindings.len(), binding_ctx_stack_len);
+                    // These bindings, but none from the surrounding pattern, are visible in the
+                    // guard; put them in scope and resolve `guard`.
+                    let subpat_bindings = bindings.pop().unwrap().1;
+                    self.with_rib(ValueNS, RibKind::Normal, |this| {
+                        *this.innermost_rib_bindings(ValueNS) = subpat_bindings.clone();
+                        this.resolve_expr(guard, None);
+                    });
+                    // Propagate the subpattern's bindings upwards.
+                    // FIXME(guard_patterns): For `if let` guards, we'll also need to get the
+                    // bindings introduced by the guard from its rib and propagate them upwards.
+                    // This will require checking the identifiers for overlaps with `bindings`, like
+                    // what `fresh_binding` does (ideally sharing its logic). To keep them separate
+                    // from `subpat_bindings`, we can introduce a fresh rib for the guard.
+                    bindings.last_mut().unwrap().1.extend(subpat_bindings);
+                    // Prevent visiting `subpat` as we've already done so above.
+                    return false;
+                }
                 _ => {}
             }
             true
diff --git a/tests/ui/feature-gates/feature-gate-guard-patterns.rs b/tests/ui/feature-gates/feature-gate-guard-patterns.rs
index 74fb5817081..095f66eeb90 100644
--- a/tests/ui/feature-gates/feature-gate-guard-patterns.rs
+++ b/tests/ui/feature-gates/feature-gate-guard-patterns.rs
@@ -22,7 +22,6 @@ fn other_guards_dont() {
 
     let ((x if guard(x)) | x) = 0;
     //~^ ERROR: guard patterns are experimental
-    //~| ERROR: cannot find value `x`
 
     if let (x if guard(x)) = 0 {}
     //~^ ERROR: guard patterns are experimental
@@ -37,7 +36,6 @@ fn other_guards_dont() {
 
 fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
 //~^ ERROR: guard patterns are experimental
-//~| ERROR: cannot find value `x`
 
 fn guard<T>(x: T) -> bool {
     unimplemented!()
diff --git a/tests/ui/feature-gates/feature-gate-guard-patterns.stderr b/tests/ui/feature-gates/feature-gate-guard-patterns.stderr
index 8b85b663889..b0bf302f3cb 100644
--- a/tests/ui/feature-gates/feature-gate-guard-patterns.stderr
+++ b/tests/ui/feature-gates/feature-gate-guard-patterns.stderr
@@ -10,24 +10,6 @@ LL -         (0 if guard(0)) => {},
 LL +         0 if guard(0) => {},
    |
 
-error[E0425]: cannot find value `x` in this scope
-  --> $DIR/feature-gate-guard-patterns.rs:23:22
-   |
-LL |     let ((x if guard(x)) | x) = 0;
-   |                      ^ not found in this scope
-
-error[E0425]: cannot find value `x` in this scope
-  --> $DIR/feature-gate-guard-patterns.rs:38:45
-   |
-LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
-   |                                             ^
-   |
-help: the binding `x` is available in a different scope in the same function
-  --> $DIR/feature-gate-guard-patterns.rs:23:11
-   |
-LL |     let ((x if guard(x)) | x) = 0;
-   |           ^
-
 error[E0658]: guard patterns are experimental
   --> $DIR/feature-gate-guard-patterns.rs:18:15
    |
@@ -51,7 +33,7 @@ LL |     let ((x if guard(x)) | x) = 0;
    = help: consider using match arm guards
 
 error[E0658]: guard patterns are experimental
-  --> $DIR/feature-gate-guard-patterns.rs:27:18
+  --> $DIR/feature-gate-guard-patterns.rs:26:18
    |
 LL |     if let (x if guard(x)) = 0 {}
    |                  ^^^^^^^^
@@ -62,7 +44,7 @@ LL |     if let (x if guard(x)) = 0 {}
    = help: consider using match arm guards
 
 error[E0658]: guard patterns are experimental
-  --> $DIR/feature-gate-guard-patterns.rs:30:21
+  --> $DIR/feature-gate-guard-patterns.rs:29:21
    |
 LL |     while let (x if guard(x)) = 0 {}
    |                     ^^^^^^^^
@@ -73,7 +55,7 @@ LL |     while let (x if guard(x)) = 0 {}
    = help: consider using match arm guards
 
 error[E0658]: guard patterns are experimental
-  --> $DIR/feature-gate-guard-patterns.rs:34:21
+  --> $DIR/feature-gate-guard-patterns.rs:33:21
    |
 LL |     while let (x if guard(x)) = 0 {}
    |                     ^^^^^^^^
@@ -84,7 +66,7 @@ LL |     while let (x if guard(x)) = 0 {}
    = help: consider using match arm guards
 
 error[E0658]: guard patterns are experimental
-  --> $DIR/feature-gate-guard-patterns.rs:38:39
+  --> $DIR/feature-gate-guard-patterns.rs:37:39
    |
 LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
    |                                       ^^^^^^^^
@@ -94,7 +76,6 @@ LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {
    = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
    = help: consider using match arm guards
 
-error: aborting due to 9 previous errors
+error: aborting due to 7 previous errors
 
-Some errors have detailed explanations: E0425, E0658.
-For more information about an error, try `rustc --explain E0425`.
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/pattern/rfc-3637-guard-patterns/name-resolution.rs b/tests/ui/pattern/rfc-3637-guard-patterns/name-resolution.rs
new file mode 100644
index 00000000000..83ad8c76bb1
--- /dev/null
+++ b/tests/ui/pattern/rfc-3637-guard-patterns/name-resolution.rs
@@ -0,0 +1,81 @@
+//! Test that guard patterns can see bindings already in scope and bindings introduced in their
+//! subpattern, but no other bindings from the containing pattern. Also make sure bindings
+//! introduced in guard patterns are visible in fn/arm/loop/etc bodies.
+
+#![feature(guard_patterns)]
+#![expect(incomplete_features)]
+
+fn good_fn_item(((x if x) | x): bool) -> bool { x }
+
+fn bad_fn_item_1(x: bool, ((y if x) | y): bool) {}
+//~^ ERROR cannot find value `x` in this scope
+fn bad_fn_item_2(((x if y) | x): bool, y: bool) {}
+//~^ ERROR cannot find value `y` in this scope
+
+fn main() {
+    let ((local if local) if local) = false;
+
+    match (true, true) {
+        (x if local, y if good_fn_item(y)) => x && y,
+        (x, y if x) => x && y,
+        //~^ ERROR cannot find value `x` in this scope
+        (x if y, y) => x && y,
+        //~^ ERROR cannot find value `y` in this scope
+    };
+
+    match (true,) {
+        (x @ y if x && y,) => x && y,
+        (x @ (y if y),) => x && y,
+        (x @ (y if x),) => x && y,
+        //~^ ERROR cannot find value `x` in this scope
+    };
+
+    match (Ok(true),) {
+        ((Ok(x) | Err(x)) if good_fn_item(x),) => x,
+        ((Ok(x) if local) | (Err(x) if good_fn_item(x)),) => x,
+        ((Ok(x if x) if x) | (Err(x if x) if x) if x,) if x => x,
+        ((Ok(x) if y) | (Err(y) if x),) => x && y,
+        //~^ ERROR variable `x` is not bound in all patterns
+        //~| ERROR variable `y` is not bound in all patterns
+        //~| ERROR cannot find value `x` in this scope
+        //~| ERROR cannot find value `y` in this scope
+    };
+
+    let (_ if nonexistent) = true;
+    //~^ ERROR cannot find value `nonexistent` in this scope
+    if let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
+    //~^ ERROR cannot find value `x` in this scope
+    //~| ERROR cannot find value `y` in this scope
+    while let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
+    //~^ ERROR cannot find value `x` in this scope
+    //~| ERROR cannot find value `y` in this scope
+    for ((x, y if x) | (x if y, y)) in [(true, true)] { x && y; }
+    //~^ ERROR cannot find value `x` in this scope
+    //~| ERROR cannot find value `y` in this scope
+
+    (|(x if x), (y if y)| x && y)(true, true);
+    (|(x if y), (y if x)| x && y)(true, true);
+    //~^ ERROR cannot find value `x` in this scope
+    //~| ERROR cannot find value `y` in this scope
+
+    // FIXME(guard_patterns): mismatched bindings are not yet allowed
+    match Some(0) {
+        Some(x if x > 0) | None => {}
+        //~^ ERROR variable `x` is not bound in all patterns
+    }
+}
+
+/// Make sure shadowing is handled properly. In particular, if a pattern shadows an identifier,
+/// a guard pattern's guard should still see the original binding if the shadowing binding isn't in
+/// its subpattern.
+fn test_shadowing(local: bool) -> u8 {
+    match (0, 0) {
+        // The `local` binding here shadows the `bool` definition, so we get a type error.
+        //~v ERROR mismatched types
+        local if local => 0,
+        // The guards here should see the `bool` definition of `local`, not the new `u8` binding.
+        // The body should see the new binding.
+        (local, _ if local) => local,
+        (_ if local, local) => local,
+    }
+}
diff --git a/tests/ui/pattern/rfc-3637-guard-patterns/name-resolution.stderr b/tests/ui/pattern/rfc-3637-guard-patterns/name-resolution.stderr
new file mode 100644
index 00000000000..d76e60478a1
--- /dev/null
+++ b/tests/ui/pattern/rfc-3637-guard-patterns/name-resolution.stderr
@@ -0,0 +1,133 @@
+error[E0408]: variable `y` is not bound in all patterns
+  --> $DIR/name-resolution.rs:37:10
+   |
+LL |         ((Ok(x) if y) | (Err(y) if x),) => x && y,
+   |          ^^^^^^^^^^^^        - variable not in all patterns
+   |          |
+   |          pattern doesn't bind `y`
+
+error[E0408]: variable `x` is not bound in all patterns
+  --> $DIR/name-resolution.rs:37:25
+   |
+LL |         ((Ok(x) if y) | (Err(y) if x),) => x && y,
+   |              -          ^^^^^^^^^^^^^ pattern doesn't bind `x`
+   |              |
+   |              variable not in all patterns
+
+error[E0408]: variable `x` is not bound in all patterns
+  --> $DIR/name-resolution.rs:63:28
+   |
+LL |         Some(x if x > 0) | None => {}
+   |              -             ^^^^ pattern doesn't bind `x`
+   |              |
+   |              variable not in all patterns
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/name-resolution.rs:10:34
+   |
+LL | fn bad_fn_item_1(x: bool, ((y if x) | y): bool) {}
+   |                                  ^ help: a local variable with a similar name exists: `y`
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/name-resolution.rs:12:25
+   |
+LL | fn bad_fn_item_2(((x if y) | x): bool, y: bool) {}
+   |                         ^ help: a local variable with a similar name exists: `x`
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/name-resolution.rs:20:18
+   |
+LL |         (x, y if x) => x && y,
+   |                  ^ help: a local variable with a similar name exists: `y`
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/name-resolution.rs:22:15
+   |
+LL |         (x if y, y) => x && y,
+   |               ^ help: a local variable with a similar name exists: `x`
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/name-resolution.rs:29:20
+   |
+LL |         (x @ (y if x),) => x && y,
+   |                    ^ help: a local variable with a similar name exists: `y`
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/name-resolution.rs:37:20
+   |
+LL |         ((Ok(x) if y) | (Err(y) if x),) => x && y,
+   |                    ^ help: a local variable with a similar name exists: `x`
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/name-resolution.rs:37:36
+   |
+LL |         ((Ok(x) if y) | (Err(y) if x),) => x && y,
+   |                                    ^ help: a local variable with a similar name exists: `y`
+
+error[E0425]: cannot find value `nonexistent` in this scope
+  --> $DIR/name-resolution.rs:44:15
+   |
+LL |     let (_ if nonexistent) = true;
+   |               ^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/name-resolution.rs:46:22
+   |
+LL |     if let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
+   |                      ^ help: a local variable with a similar name exists: `y`
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/name-resolution.rs:46:33
+   |
+LL |     if let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
+   |                                 ^ help: a local variable with a similar name exists: `x`
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/name-resolution.rs:49:25
+   |
+LL |     while let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
+   |                         ^ help: a local variable with a similar name exists: `y`
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/name-resolution.rs:49:36
+   |
+LL |     while let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
+   |                                    ^ help: a local variable with a similar name exists: `x`
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/name-resolution.rs:52:19
+   |
+LL |     for ((x, y if x) | (x if y, y)) in [(true, true)] { x && y; }
+   |                   ^ help: a local variable with a similar name exists: `y`
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/name-resolution.rs:52:30
+   |
+LL |     for ((x, y if x) | (x if y, y)) in [(true, true)] { x && y; }
+   |                              ^ help: a local variable with a similar name exists: `x`
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/name-resolution.rs:57:13
+   |
+LL |     (|(x if y), (y if x)| x && y)(true, true);
+   |             ^ help: a local variable with a similar name exists: `x`
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/name-resolution.rs:57:23
+   |
+LL |     (|(x if y), (y if x)| x && y)(true, true);
+   |                       ^ help: a local variable with a similar name exists: `y`
+
+error[E0308]: mismatched types
+  --> $DIR/name-resolution.rs:75:18
+   |
+LL |         local if local => 0,
+   |                  ^^^^^ expected `bool`, found `({integer}, {integer})`
+   |
+   = note: expected type `bool`
+             found tuple `({integer}, {integer})`
+
+error: aborting due to 20 previous errors
+
+Some errors have detailed explanations: E0308, E0408, E0425.
+For more information about an error, try `rustc --explain E0308`.