Skip to content

Commit f04862f

Browse files
committed
Auto merge of #140746 - dianne:guard-pat-res, r=<try>
name resolution for guard patterns This PR provides an initial implementation of name resolution for guard patterns [(RFC 3637)](https://github.com/rust-lang/rfcs/blob/master/text/3637-guard-patterns.md). This does not change the requirement that the bindings on either side of an or-pattern must be the same [(proposal here)](https://github.com/rust-lang/rfcs/blob/master/text/3637-guard-patterns.md#allowing-mismatching-bindings-when-possible); the code that handles that is separate from what this PR touches, so I'm saving it for a follow-up. On a technical level, this separates "collecting the bindings in a pattern" (which was already done for or-patterns) from "introducing those bindings into scope". I believe the approach used here can be extended straightforwardly in the future to work with `if let` guard patterns, but I haven't tried it myself since we don't allow those yet. Tracking issue for guard patterns: #129967 cc `@Nadrieril`
2 parents 7e552b4 + d7ac436 commit f04862f

File tree

6 files changed

+292
-62
lines changed

6 files changed

+292
-62
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

+95-34
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ enum PatBoundCtx {
111111
Or,
112112
}
113113

114+
/// Tracks bindings resolved within a pattern. This serves two purposes:
115+
///
116+
/// - This tracks when identifiers are bound multiple times within a pattern. In a product context,
117+
/// this is an error. In an or-pattern, this lets us reuse the same resolution for each instance.
118+
/// See `fresh_binding` and `resolve_pattern_inner` for more information.
119+
///
120+
/// - The guard expression of a guard pattern may use bindings from within the guard pattern, but
121+
/// not from elsewhere in the pattern containing it. This allows us to isolate the bindings in the
122+
/// subpattern to construct the scope for the guard.
123+
///
124+
/// Each identifier must map to at most one distinct [`Res`].
125+
type PatternBindings = SmallVec<[(PatBoundCtx, FxIndexMap<Ident, Res>); 1]>;
126+
114127
/// Does this the item (from the item rib scope) allow generic parameters?
115128
#[derive(Copy, Clone, Debug)]
116129
pub(crate) enum HasGenericParams {
@@ -786,7 +799,14 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
786799
fn visit_pat(&mut self, p: &'ast Pat) {
787800
let prev = self.diag_metadata.current_pat;
788801
self.diag_metadata.current_pat = Some(p);
789-
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+
790810
self.diag_metadata.current_pat = prev;
791811
}
792812
fn visit_local(&mut self, local: &'ast Local) {
@@ -2297,7 +2317,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
22972317
fn resolve_fn_params(
22982318
&mut self,
22992319
has_self: bool,
2300-
inputs: impl Iterator<Item = (Option<&'ast Pat>, &'ast Ty)>,
2320+
inputs: impl Iterator<Item = (Option<&'ast Pat>, &'ast Ty)> + Clone,
23012321
) -> Result<LifetimeRes, (Vec<MissingLifetime>, Vec<ElisionFnParameter>)> {
23022322
enum Elision {
23032323
/// We have not found any candidate.
@@ -2319,15 +2339,20 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
23192339
let mut parameter_info = Vec::new();
23202340
let mut all_candidates = Vec::new();
23212341

2342+
// Resolve and apply bindings first so diagnostics can see if they're used in types.
23222343
let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())];
2323-
for (index, (pat, ty)) in inputs.enumerate() {
2324-
debug!(?pat, ?ty);
2344+
for (pat, _) in inputs.clone() {
2345+
debug!("resolving bindings in pat = {pat:?}");
23252346
self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| {
23262347
if let Some(pat) = pat {
23272348
this.resolve_pattern(pat, PatternSource::FnParam, &mut bindings);
23282349
}
23292350
});
2351+
}
2352+
self.apply_pattern_bindings(bindings);
23302353

2354+
for (index, (pat, ty)) in inputs.enumerate() {
2355+
debug!("resolving type for pat = {pat:?}, ty = {ty:?}");
23312356
// Record elision candidates only for this parameter.
23322357
debug_assert_matches!(self.lifetime_elision_candidates, None);
23332358
self.lifetime_elision_candidates = Some(Default::default());
@@ -3615,16 +3640,10 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
36153640
self.visit_path(&delegation.path, delegation.id);
36163641
let Some(body) = &delegation.body else { return };
36173642
self.with_rib(ValueNS, RibKind::FnOrCoroutine, |this| {
3618-
// `PatBoundCtx` is not necessary in this context
3619-
let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())];
3620-
36213643
let span = delegation.path.segments.last().unwrap().ident.span;
3622-
this.fresh_binding(
3623-
Ident::new(kw::SelfLower, span),
3624-
delegation.id,
3625-
PatternSource::FnParam,
3626-
&mut bindings,
3627-
);
3644+
let ident = Ident::new(kw::SelfLower, span.normalize_to_macro_rules());
3645+
let res = Res::Local(delegation.id);
3646+
this.innermost_rib_bindings(ValueNS).insert(ident, res);
36283647
this.visit_block(body);
36293648
});
36303649
}
@@ -3635,6 +3654,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
36353654
for Param { pat, .. } in params {
36363655
this.resolve_pattern(pat, PatternSource::FnParam, &mut bindings);
36373656
}
3657+
this.apply_pattern_bindings(bindings);
36383658
});
36393659
for Param { ty, .. } in params {
36403660
self.visit_ty(ty);
@@ -3851,13 +3871,32 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
38513871
fn resolve_pattern_top(&mut self, pat: &'ast Pat, pat_src: PatternSource) {
38523872
let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())];
38533873
self.resolve_pattern(pat, pat_src, &mut bindings);
3874+
self.apply_pattern_bindings(bindings);
3875+
}
3876+
3877+
/// Apply the bindings from a pattern to the innermost rib of the current scope.
3878+
fn apply_pattern_bindings(&mut self, mut pat_bindings: PatternBindings) {
3879+
let rib_bindings = self.innermost_rib_bindings(ValueNS);
3880+
let Some((_, pat_bindings)) = pat_bindings.pop() else {
3881+
bug!("tried applying nonexistent bindings from pattern");
3882+
};
3883+
3884+
if rib_bindings.is_empty() {
3885+
// Often, such as for match arms, the bindings are introduced into a new rib.
3886+
// In this case, we can move the bindings over directly.
3887+
*rib_bindings = pat_bindings;
3888+
} else {
3889+
rib_bindings.extend(pat_bindings);
3890+
}
38543891
}
38553892

3893+
/// Resolve bindings in a pattern. `apply_pattern_bindings` must be called after to introduce
3894+
/// the bindings into scope.
38563895
fn resolve_pattern(
38573896
&mut self,
38583897
pat: &'ast Pat,
38593898
pat_src: PatternSource,
3860-
bindings: &mut SmallVec<[(PatBoundCtx, FxHashSet<Ident>); 1]>,
3899+
bindings: &mut PatternBindings,
38613900
) {
38623901
// We walk the pattern before declaring the pattern's inner bindings,
38633902
// so that we avoid resolving a literal expression to a binding defined
@@ -3890,9 +3929,9 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
38903929
#[tracing::instrument(skip(self, bindings), level = "debug")]
38913930
fn resolve_pattern_inner(
38923931
&mut self,
3893-
pat: &Pat,
3932+
pat: &'ast Pat,
38943933
pat_src: PatternSource,
3895-
bindings: &mut SmallVec<[(PatBoundCtx, FxHashSet<Ident>); 1]>,
3934+
bindings: &mut PatternBindings,
38963935
) {
38973936
// Visit all direct subpatterns of this pattern.
38983937
pat.walk(&mut |pat| {
@@ -3950,6 +3989,27 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
39503989
// Prevent visiting `ps` as we've already done so above.
39513990
return false;
39523991
}
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+
}
39534013
_ => {}
39544014
}
39554015
true
@@ -3988,20 +4048,17 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
39884048
ident: Ident,
39894049
pat_id: NodeId,
39904050
pat_src: PatternSource,
3991-
bindings: &mut SmallVec<[(PatBoundCtx, FxHashSet<Ident>); 1]>,
4051+
bindings: &mut PatternBindings,
39924052
) -> Res {
3993-
// Add the binding to the local ribs, if it doesn't already exist in the bindings map.
4053+
// Add the binding to the bindings map, if it doesn't already exist.
39944054
// (We must not add it if it's in the bindings map because that breaks the assumptions
39954055
// later passes make about or-patterns.)
39964056
let ident = ident.normalize_to_macro_rules();
39974057

3998-
let mut bound_iter = bindings.iter().filter(|(_, set)| set.contains(&ident));
39994058
// Already bound in a product pattern? e.g. `(a, a)` which is not allowed.
4000-
let already_bound_and = bound_iter.clone().any(|(ctx, _)| *ctx == PatBoundCtx::Product);
4001-
// Already bound in an or-pattern? e.g. `V1(a) | V2(a)`.
4002-
// This is *required* for consistency which is checked later.
4003-
let already_bound_or = bound_iter.any(|(ctx, _)| *ctx == PatBoundCtx::Or);
4004-
4059+
let already_bound_and = bindings
4060+
.iter()
4061+
.any(|(ctx, map)| *ctx == PatBoundCtx::Product && map.contains_key(&ident));
40054062
if already_bound_and {
40064063
// Overlap in a product pattern somewhere; report an error.
40074064
use ResolutionError::*;
@@ -4014,19 +4071,23 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
40144071
self.report_error(ident.span, error(ident));
40154072
}
40164073

4017-
// Record as bound.
4018-
bindings.last_mut().unwrap().1.insert(ident);
4019-
4020-
if already_bound_or {
4074+
// Already bound in an or-pattern? e.g. `V1(a) | V2(a)`.
4075+
// This is *required* for consistency which is checked later.
4076+
let already_bound_or = bindings
4077+
.iter()
4078+
.find_map(|(ctx, map)| if *ctx == PatBoundCtx::Or { map.get(&ident) } else { None });
4079+
let res = if let Some(&res) = already_bound_or {
40214080
// `Variant1(a) | Variant2(a)`, ok
40224081
// Reuse definition from the first `a`.
4023-
self.innermost_rib_bindings(ValueNS)[&ident]
4024-
} else {
4025-
// A completely fresh binding is added to the set.
4026-
let res = Res::Local(pat_id);
4027-
self.innermost_rib_bindings(ValueNS).insert(ident, res);
40284082
res
4029-
}
4083+
} else {
4084+
// A completely fresh binding is added to the map.
4085+
Res::Local(pat_id)
4086+
};
4087+
4088+
// Record as bound.
4089+
bindings.last_mut().unwrap().1.insert(ident, res);
4090+
res
40304091
}
40314092

40324093
fn innermost_rib_bindings(&mut self, ns: Namespace) -> &mut FxIndexMap<Ident, Res> {

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,66 @@
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+
}

0 commit comments

Comments
 (0)