Skip to content

Commit 002e6e8

Browse files
committed
name resolution for guard patterns
1 parent 30a0ac6 commit 002e6e8

File tree

6 files changed

+251
-30
lines changed

6 files changed

+251
-30
lines changed

compiler/rustc_ast/src/ast.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ impl Pat {
611611
/// Walk top-down and call `it` in each place where a pattern occurs
612612
/// starting with the root pattern `walk` is called on. If `it` returns
613613
/// false then we will descend no further but siblings will be processed.
614-
pub fn walk(&self, it: &mut impl FnMut(&Pat) -> bool) {
614+
pub fn walk<'ast>(&'ast self, it: &mut impl FnMut(&'ast Pat) -> bool) {
615615
if !it(self) {
616616
return;
617617
}

compiler/rustc_resolve/src/late.rs

+30-2
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,14 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
799799
fn visit_pat(&mut self, p: &'ast Pat) {
800800
let prev = self.diag_metadata.current_pat;
801801
self.diag_metadata.current_pat = Some(p);
802-
visit::walk_pat(self, p);
802+
803+
if let PatKind::Guard(subpat, _) = &p.kind {
804+
// We walk the guard expression in `resolve_pattern_inner`. Don't resolve it twice.
805+
self.visit_pat(subpat);
806+
} else {
807+
visit::walk_pat(self, p);
808+
}
809+
803810
self.diag_metadata.current_pat = prev;
804811
}
805812
fn visit_local(&mut self, local: &'ast Local) {
@@ -3922,7 +3929,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
39223929
#[tracing::instrument(skip(self, bindings), level = "debug")]
39233930
fn resolve_pattern_inner(
39243931
&mut self,
3925-
pat: &Pat,
3932+
pat: &'ast Pat,
39263933
pat_src: PatternSource,
39273934
bindings: &mut PatternBindings,
39283935
) {
@@ -3982,6 +3989,27 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
39823989
// Prevent visiting `ps` as we've already done so above.
39833990
return false;
39843991
}
3992+
PatKind::Guard(ref subpat, ref guard) => {
3993+
// Add a new set of bindings to the stack to collect bindings in `subpat`.
3994+
bindings.push((PatBoundCtx::Product, Default::default()));
3995+
self.resolve_pattern_inner(subpat, pat_src, bindings);
3996+
// These bindings, but none from the surrounding pattern, are visible in the
3997+
// guard; put them in scope and resolve `guard`.
3998+
let subpat_bindings = bindings.pop().unwrap().1;
3999+
self.with_rib(ValueNS, RibKind::Normal, |this| {
4000+
*this.innermost_rib_bindings(ValueNS) = subpat_bindings.clone();
4001+
this.resolve_expr(guard, None);
4002+
});
4003+
// Propagate the subpattern's bindings upwards.
4004+
// FIXME(guard_patterns): For `if let` guards, we'll also need to get the
4005+
// bindings introduced by the guard from its rib and propagate them upwards.
4006+
// This will require checking the identifiers for overlaps with `bindings`, like
4007+
// what `fresh_binding` does (ideally sharing its logic). To keep them separate
4008+
// from `subpat_bindings`, we can introduce a fresh rib for the guard.
4009+
bindings.last_mut().unwrap().1.extend(subpat_bindings);
4010+
// Prevent visiting `subpat` as we've already done so above.
4011+
return false;
4012+
}
39854013
_ => {}
39864014
}
39874015
true

tests/ui/feature-gates/feature-gate-guard-patterns.rs

-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ fn other_guards_dont() {
2222

2323
let ((x if guard(x)) | x) = 0;
2424
//~^ ERROR: guard patterns are experimental
25-
//~| ERROR: cannot find value `x`
2625

2726
if let (x if guard(x)) = 0 {}
2827
//~^ ERROR: guard patterns are experimental
@@ -37,7 +36,6 @@ fn other_guards_dont() {
3736

3837
fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
3938
//~^ ERROR: guard patterns are experimental
40-
//~| ERROR: cannot find value `x`
4139

4240
fn guard<T>(x: T) -> bool {
4341
unimplemented!()

tests/ui/feature-gates/feature-gate-guard-patterns.stderr

+6-25
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,6 @@ LL - (0 if guard(0)) => {},
1010
LL + 0 if guard(0) => {},
1111
|
1212

13-
error[E0425]: cannot find value `x` in this scope
14-
--> $DIR/feature-gate-guard-patterns.rs:23:22
15-
|
16-
LL | let ((x if guard(x)) | x) = 0;
17-
| ^ not found in this scope
18-
19-
error[E0425]: cannot find value `x` in this scope
20-
--> $DIR/feature-gate-guard-patterns.rs:38:45
21-
|
22-
LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
23-
| ^
24-
|
25-
help: the binding `x` is available in a different scope in the same function
26-
--> $DIR/feature-gate-guard-patterns.rs:23:11
27-
|
28-
LL | let ((x if guard(x)) | x) = 0;
29-
| ^
30-
3113
error[E0658]: guard patterns are experimental
3214
--> $DIR/feature-gate-guard-patterns.rs:18:15
3315
|
@@ -51,7 +33,7 @@ LL | let ((x if guard(x)) | x) = 0;
5133
= help: consider using match arm guards
5234

5335
error[E0658]: guard patterns are experimental
54-
--> $DIR/feature-gate-guard-patterns.rs:27:18
36+
--> $DIR/feature-gate-guard-patterns.rs:26:18
5537
|
5638
LL | if let (x if guard(x)) = 0 {}
5739
| ^^^^^^^^
@@ -62,7 +44,7 @@ LL | if let (x if guard(x)) = 0 {}
6244
= help: consider using match arm guards
6345

6446
error[E0658]: guard patterns are experimental
65-
--> $DIR/feature-gate-guard-patterns.rs:30:21
47+
--> $DIR/feature-gate-guard-patterns.rs:29:21
6648
|
6749
LL | while let (x if guard(x)) = 0 {}
6850
| ^^^^^^^^
@@ -73,7 +55,7 @@ LL | while let (x if guard(x)) = 0 {}
7355
= help: consider using match arm guards
7456

7557
error[E0658]: guard patterns are experimental
76-
--> $DIR/feature-gate-guard-patterns.rs:34:21
58+
--> $DIR/feature-gate-guard-patterns.rs:33:21
7759
|
7860
LL | while let (x if guard(x)) = 0 {}
7961
| ^^^^^^^^
@@ -84,7 +66,7 @@ LL | while let (x if guard(x)) = 0 {}
8466
= help: consider using match arm guards
8567

8668
error[E0658]: guard patterns are experimental
87-
--> $DIR/feature-gate-guard-patterns.rs:38:39
69+
--> $DIR/feature-gate-guard-patterns.rs:37:39
8870
|
8971
LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {}
9072
| ^^^^^^^^
@@ -94,7 +76,6 @@ LL | fn even_as_function_parameters(((x if guard(x), _) | (_, x)): (i32, i32)) {
9476
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
9577
= help: consider using match arm guards
9678

97-
error: aborting due to 9 previous errors
79+
error: aborting due to 7 previous errors
9880

99-
Some errors have detailed explanations: E0425, E0658.
100-
For more information about an error, try `rustc --explain E0425`.
81+
For more information about this error, try `rustc --explain E0658`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! Test that guard patterns can see bindings already in scope and bindings introduced in their
2+
//! subpattern, but no other bindings from the containing pattern. Also make sure bindings
3+
//! introduced in guard patterns are visible in fn/arm/loop/etc bodies.
4+
5+
#![feature(guard_patterns)]
6+
#![expect(incomplete_features)]
7+
8+
fn good_fn_item(((x if x) | x): bool) -> bool { x }
9+
10+
fn bad_fn_item_1(x: bool, ((y if x) | y): bool) {}
11+
//~^ ERROR cannot find value `x` in this scope
12+
fn bad_fn_item_2(((x if y) | x): bool, y: bool) {}
13+
//~^ ERROR cannot find value `y` in this scope
14+
15+
fn main() {
16+
let ((local if local) if local) = false;
17+
18+
match (true, true) {
19+
(x if local, y if good_fn_item(y)) => x && y,
20+
(x, y if x) => x && y,
21+
//~^ ERROR cannot find value `x` in this scope
22+
(x if y, y) => x && y,
23+
//~^ ERROR cannot find value `y` in this scope
24+
};
25+
26+
match (true,) {
27+
(x @ y if x && y,) => x && y,
28+
(x @ (y if y),) => x && y,
29+
(x @ (y if x),) => x && y,
30+
//~^ ERROR cannot find value `x` in this scope
31+
};
32+
33+
match (Ok(true),) {
34+
((Ok(x) | Err(x)) if good_fn_item(x),) => x,
35+
((Ok(x) if local) | (Err(x) if good_fn_item(x)),) => x,
36+
((Ok(x if x) if x) | (Err(x if x) if x) if x,) if x => x,
37+
((Ok(x) if y) | (Err(y) if x),) => x && y,
38+
//~^ ERROR variable `x` is not bound in all patterns
39+
//~| ERROR variable `y` is not bound in all patterns
40+
//~| ERROR cannot find value `x` in this scope
41+
//~| ERROR cannot find value `y` in this scope
42+
};
43+
44+
let (_ if nonexistent) = true;
45+
//~^ ERROR cannot find value `nonexistent` in this scope
46+
if let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
47+
//~^ ERROR cannot find value `x` in this scope
48+
//~| ERROR cannot find value `y` in this scope
49+
while let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
50+
//~^ ERROR cannot find value `x` in this scope
51+
//~| ERROR cannot find value `y` in this scope
52+
for ((x, y if x) | (x if y, y)) in [(true, true)] { x && y; }
53+
//~^ ERROR cannot find value `x` in this scope
54+
//~| ERROR cannot find value `y` in this scope
55+
56+
(|(x if x), (y if y)| x && y)(true, true);
57+
(|(x if y), (y if x)| x && y)(true, true);
58+
//~^ ERROR cannot find value `x` in this scope
59+
//~| ERROR cannot find value `y` in this scope
60+
61+
// FIXME(guard_patterns): mismatched bindings are not yet allowed
62+
match Some(0) {
63+
Some(x if x > 0) | None => {}
64+
//~^ ERROR variable `x` is not bound in all patterns
65+
}
66+
}
67+
68+
/// Make sure shadowing is handled properly. In particular, if a pattern shadows an identifier,
69+
/// a guard pattern's guard should still see the original binding if the shadowing binding isn't in
70+
/// its subpattern.
71+
fn test_shadowing(local: bool) -> u8 {
72+
match (0, 0) {
73+
// The `local` binding here shadows the `bool` definition, so we get a type error.
74+
//~v ERROR mismatched types
75+
local if local => 0,
76+
// The guards here should see the `bool` definition of `local`, not the new `u8` binding.
77+
// The body should see the new binding.
78+
(local, _ if local) => local,
79+
(_ if local, local) => local,
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
error[E0408]: variable `y` is not bound in all patterns
2+
--> $DIR/name-resolution.rs:37:10
3+
|
4+
LL | ((Ok(x) if y) | (Err(y) if x),) => x && y,
5+
| ^^^^^^^^^^^^ - variable not in all patterns
6+
| |
7+
| pattern doesn't bind `y`
8+
9+
error[E0408]: variable `x` is not bound in all patterns
10+
--> $DIR/name-resolution.rs:37:25
11+
|
12+
LL | ((Ok(x) if y) | (Err(y) if x),) => x && y,
13+
| - ^^^^^^^^^^^^^ pattern doesn't bind `x`
14+
| |
15+
| variable not in all patterns
16+
17+
error[E0408]: variable `x` is not bound in all patterns
18+
--> $DIR/name-resolution.rs:63:28
19+
|
20+
LL | Some(x if x > 0) | None => {}
21+
| - ^^^^ pattern doesn't bind `x`
22+
| |
23+
| variable not in all patterns
24+
25+
error[E0425]: cannot find value `x` in this scope
26+
--> $DIR/name-resolution.rs:10:34
27+
|
28+
LL | fn bad_fn_item_1(x: bool, ((y if x) | y): bool) {}
29+
| ^ help: a local variable with a similar name exists: `y`
30+
31+
error[E0425]: cannot find value `y` in this scope
32+
--> $DIR/name-resolution.rs:12:25
33+
|
34+
LL | fn bad_fn_item_2(((x if y) | x): bool, y: bool) {}
35+
| ^ help: a local variable with a similar name exists: `x`
36+
37+
error[E0425]: cannot find value `x` in this scope
38+
--> $DIR/name-resolution.rs:20:18
39+
|
40+
LL | (x, y if x) => x && y,
41+
| ^ help: a local variable with a similar name exists: `y`
42+
43+
error[E0425]: cannot find value `y` in this scope
44+
--> $DIR/name-resolution.rs:22:15
45+
|
46+
LL | (x if y, y) => x && y,
47+
| ^ help: a local variable with a similar name exists: `x`
48+
49+
error[E0425]: cannot find value `x` in this scope
50+
--> $DIR/name-resolution.rs:29:20
51+
|
52+
LL | (x @ (y if x),) => x && y,
53+
| ^ help: a local variable with a similar name exists: `y`
54+
55+
error[E0425]: cannot find value `y` in this scope
56+
--> $DIR/name-resolution.rs:37:20
57+
|
58+
LL | ((Ok(x) if y) | (Err(y) if x),) => x && y,
59+
| ^ help: a local variable with a similar name exists: `x`
60+
61+
error[E0425]: cannot find value `x` in this scope
62+
--> $DIR/name-resolution.rs:37:36
63+
|
64+
LL | ((Ok(x) if y) | (Err(y) if x),) => x && y,
65+
| ^ help: a local variable with a similar name exists: `y`
66+
67+
error[E0425]: cannot find value `nonexistent` in this scope
68+
--> $DIR/name-resolution.rs:44:15
69+
|
70+
LL | let (_ if nonexistent) = true;
71+
| ^^^^^^^^^^^ not found in this scope
72+
73+
error[E0425]: cannot find value `x` in this scope
74+
--> $DIR/name-resolution.rs:46:22
75+
|
76+
LL | if let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
77+
| ^ help: a local variable with a similar name exists: `y`
78+
79+
error[E0425]: cannot find value `y` in this scope
80+
--> $DIR/name-resolution.rs:46:33
81+
|
82+
LL | if let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
83+
| ^ help: a local variable with a similar name exists: `x`
84+
85+
error[E0425]: cannot find value `x` in this scope
86+
--> $DIR/name-resolution.rs:49:25
87+
|
88+
LL | while let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
89+
| ^ help: a local variable with a similar name exists: `y`
90+
91+
error[E0425]: cannot find value `y` in this scope
92+
--> $DIR/name-resolution.rs:49:36
93+
|
94+
LL | while let ((x, y if x) | (x if y, y)) = (true, true) { x && y; }
95+
| ^ help: a local variable with a similar name exists: `x`
96+
97+
error[E0425]: cannot find value `x` in this scope
98+
--> $DIR/name-resolution.rs:52:19
99+
|
100+
LL | for ((x, y if x) | (x if y, y)) in [(true, true)] { x && y; }
101+
| ^ help: a local variable with a similar name exists: `y`
102+
103+
error[E0425]: cannot find value `y` in this scope
104+
--> $DIR/name-resolution.rs:52:30
105+
|
106+
LL | for ((x, y if x) | (x if y, y)) in [(true, true)] { x && y; }
107+
| ^ help: a local variable with a similar name exists: `x`
108+
109+
error[E0425]: cannot find value `y` in this scope
110+
--> $DIR/name-resolution.rs:57:13
111+
|
112+
LL | (|(x if y), (y if x)| x && y)(true, true);
113+
| ^ help: a local variable with a similar name exists: `x`
114+
115+
error[E0425]: cannot find value `x` in this scope
116+
--> $DIR/name-resolution.rs:57:23
117+
|
118+
LL | (|(x if y), (y if x)| x && y)(true, true);
119+
| ^ help: a local variable with a similar name exists: `y`
120+
121+
error[E0308]: mismatched types
122+
--> $DIR/name-resolution.rs:75:18
123+
|
124+
LL | local if local => 0,
125+
| ^^^^^ expected `bool`, found `({integer}, {integer})`
126+
|
127+
= note: expected type `bool`
128+
found tuple `({integer}, {integer})`
129+
130+
error: aborting due to 20 previous errors
131+
132+
Some errors have detailed explanations: E0308, E0408, E0425.
133+
For more information about an error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)