diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 233a4c48862ac..71af76884e90e 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -440,6 +440,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { self.detect_missing_binding_available_from_pattern(&mut err, path, following_seg); self.suggest_at_operator_in_slice_pat_with_range(&mut err, path); + self.suggest_range_struct_destructuring(&mut err, path, source); self.suggest_swapping_misplaced_self_ty_and_trait(&mut err, source, res, base_error.span); if let Some((span, label)) = base_error.span_label { @@ -1383,6 +1384,84 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } } + fn suggest_range_struct_destructuring( + &mut self, + err: &mut Diag<'_>, + path: &[Segment], + source: PathSource<'_, '_, '_>, + ) { + if !matches!(source, PathSource::Pat | PathSource::TupleStruct(..) | PathSource::Expr(..)) { + return; + } + + let Some(pat) = self.diag_metadata.current_pat else { return }; + let ast::PatKind::Range(start, end, end_kind) = &pat.kind else { return }; + + let [segment] = path else { return }; + let failing_span = segment.ident.span; + + let start_snippet = + start.as_ref().and_then(|e| self.r.tcx.sess.source_map().span_to_snippet(e.span).ok()); + let end_snippet = + end.as_ref().and_then(|e| self.r.tcx.sess.source_map().span_to_snippet(e.span).ok()); + + let in_start = start.as_ref().is_some_and(|e| e.span.contains(failing_span)); + let in_end = end.as_ref().is_some_and(|e| e.span.contains(failing_span)); + + if !in_start && !in_end { + return; + } + + let field = |name: &str, val: String| { + if val == name { val } else { format!("{name}: {val}") } + }; + + let mut resolve_short_name = |short: &str, full: &str| -> String { + let ident = Ident::from_str(short); + let path = Segment::from_path(&Path::from_ident(ident)); + + match self.resolve_path(&path, Some(TypeNS), None, PathSource::Type) { + PathResult::Module(..) | PathResult::NonModule(..) => short.to_string(), + _ => full.to_string(), + } + }; + // FIXME(new_range): Also account for new range types + let (struct_path, fields) = match (start_snippet, end_snippet, &end_kind.node) { + (Some(start), Some(end), ast::RangeEnd::Excluded) => ( + resolve_short_name("Range", "std::ops::Range"), + vec![field("start", start), field("end", end)], + ), + (Some(start), Some(end), ast::RangeEnd::Included(_)) => ( + resolve_short_name("RangeInclusive", "std::ops::RangeInclusive"), + vec![field("start", start), field("end", end)], + ), + (Some(start), None, _) => ( + resolve_short_name("RangeFrom", "std::ops::RangeFrom"), + vec![field("start", start)], + ), + (None, Some(end), ast::RangeEnd::Excluded) => { + (resolve_short_name("RangeTo", "std::ops::RangeTo"), vec![field("end", end)]) + } + (None, Some(end), ast::RangeEnd::Included(_)) => ( + resolve_short_name("RangeToInclusive", "std::ops::RangeToInclusive"), + vec![field("end", end)], + ), + _ => return, + }; + + err.span_suggestion_verbose( + pat.span, + format!("if you meant to destructure a range use a struct pattern"), + format!("{} {{ {} }}", struct_path, fields.join(", ")), + Applicability::MaybeIncorrect, + ); + + err.note( + "range patterns match against the start and end of a range; \ + to bind the components, use a struct pattern", + ); + } + fn suggest_swapping_misplaced_self_ty_and_trait( &mut self, err: &mut Diag<'_>, diff --git a/tests/ui/match/issue-92100.stderr b/tests/ui/match/issue-92100.stderr index eb9f4ba1ad698..13aacc4782af2 100644 --- a/tests/ui/match/issue-92100.stderr +++ b/tests/ui/match/issue-92100.stderr @@ -4,10 +4,16 @@ error[E0425]: cannot find value `a` in this scope LL | [a.., a] => {} | ^ not found in this scope | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern help: if you meant to collect the rest of the slice in `a`, use the at operator | LL | [a @ .., a] => {} | + +help: if you meant to destructure a range use a struct pattern + | +LL - [a.., a] => {} +LL + [std::ops::RangeFrom { start: a }, a] => {} + | error: aborting due to 1 previous error diff --git a/tests/ui/pattern/range-pattern-meant-to-be-slice-rest-pattern.stderr b/tests/ui/pattern/range-pattern-meant-to-be-slice-rest-pattern.stderr index 37b2d96bb0194..378ff04d3a1af 100644 --- a/tests/ui/pattern/range-pattern-meant-to-be-slice-rest-pattern.stderr +++ b/tests/ui/pattern/range-pattern-meant-to-be-slice-rest-pattern.stderr @@ -16,10 +16,16 @@ error[E0425]: cannot find value `rest` in this scope LL | [1, rest..] => println!("{rest}"), | ^^^^ not found in this scope | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern help: if you meant to collect the rest of the slice in `rest`, use the at operator | LL | [1, rest @ ..] => println!("{rest}"), | + +help: if you meant to destructure a range use a struct pattern + | +LL - [1, rest..] => println!("{rest}"), +LL + [1, std::ops::RangeFrom { start: rest }] => println!("{rest}"), + | error[E0425]: cannot find value `rest` in this scope --> $DIR/range-pattern-meant-to-be-slice-rest-pattern.rs:3:35 @@ -33,11 +39,17 @@ error[E0425]: cannot find value `tail` in this scope LL | [_, ..tail] => println!("{tail}"), | ^^^^ not found in this scope | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern help: if you meant to collect the rest of the slice in `tail`, use the at operator | LL - [_, ..tail] => println!("{tail}"), LL + [_, tail @ ..] => println!("{tail}"), | +help: if you meant to destructure a range use a struct pattern + | +LL - [_, ..tail] => println!("{tail}"), +LL + [_, std::ops::RangeTo { end: tail }] => println!("{tail}"), + | error[E0425]: cannot find value `tail` in this scope --> $DIR/range-pattern-meant-to-be-slice-rest-pattern.rs:11:35 @@ -51,11 +63,17 @@ error[E0425]: cannot find value `tail` in this scope LL | [_, ...tail] => println!("{tail}"), | ^^^^ not found in this scope | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern help: if you meant to collect the rest of the slice in `tail`, use the at operator | LL - [_, ...tail] => println!("{tail}"), LL + [_, tail @ ..] => println!("{tail}"), | +help: if you meant to destructure a range use a struct pattern + | +LL - [_, ...tail] => println!("{tail}"), +LL + [_, std::ops::RangeToInclusive { end: tail }] => println!("{tail}"), + | error[E0425]: cannot find value `tail` in this scope --> $DIR/range-pattern-meant-to-be-slice-rest-pattern.rs:17:36 diff --git a/tests/ui/resolve/suggest-range-struct-destructuring.rs b/tests/ui/resolve/suggest-range-struct-destructuring.rs new file mode 100644 index 0000000000000..ee8a99ceaa1a3 --- /dev/null +++ b/tests/ui/resolve/suggest-range-struct-destructuring.rs @@ -0,0 +1,40 @@ +use std::ops::{Range, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}; + +fn test_range(r: Range) { + let start..end = r; + //~^ ERROR cannot find value `start` + //~| ERROR cannot find value `end` +} + +fn test_inclusive(r: RangeInclusive) { + let start..=end = r; + //~^ ERROR cannot find value `start` + //~| ERROR cannot find value `end` +} + +fn test_from(r: RangeFrom) { + let start.. = r; + //~^ ERROR cannot find value `start` +} + +fn test_to(r: RangeTo) { + let ..end = r; + //~^ ERROR cannot find value `end` +} + +fn test_to_inclusive(r: RangeToInclusive) { + let ..=end = r; + //~^ ERROR cannot find value `end` +} + +// Case 6: Complex Path (Keep this! It works!) +mod my { + // We don't define MISSING here to trigger the error +} +fn test_path(r: Range) { + let my::MISSING..end = r; + //~^ ERROR cannot find value `MISSING` + //~| ERROR cannot find value `end` +} + +fn main() {} diff --git a/tests/ui/resolve/suggest-range-struct-destructuring.stderr b/tests/ui/resolve/suggest-range-struct-destructuring.stderr new file mode 100644 index 0000000000000..78a248ed777d1 --- /dev/null +++ b/tests/ui/resolve/suggest-range-struct-destructuring.stderr @@ -0,0 +1,127 @@ +error[E0425]: cannot find value `start` in this scope + --> $DIR/suggest-range-struct-destructuring.rs:4:9 + | +LL | let start..end = r; + | ^^^^^ not found in this scope + | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern +help: if you meant to destructure a range use a struct pattern + | +LL - let start..end = r; +LL + let Range { start, end } = r; + | + +error[E0425]: cannot find value `end` in this scope + --> $DIR/suggest-range-struct-destructuring.rs:4:16 + | +LL | let start..end = r; + | ^^^ not found in this scope + | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern +help: if you meant to destructure a range use a struct pattern + | +LL - let start..end = r; +LL + let Range { start, end } = r; + | + +error[E0425]: cannot find value `start` in this scope + --> $DIR/suggest-range-struct-destructuring.rs:10:9 + | +LL | let start..=end = r; + | ^^^^^ not found in this scope + | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern +help: if you meant to destructure a range use a struct pattern + | +LL - let start..=end = r; +LL + let RangeInclusive { start, end } = r; + | + +error[E0425]: cannot find value `end` in this scope + --> $DIR/suggest-range-struct-destructuring.rs:10:17 + | +LL | let start..=end = r; + | ^^^ not found in this scope + | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern +help: if you meant to destructure a range use a struct pattern + | +LL - let start..=end = r; +LL + let RangeInclusive { start, end } = r; + | + +error[E0425]: cannot find value `start` in this scope + --> $DIR/suggest-range-struct-destructuring.rs:16:9 + | +LL | let start.. = r; + | ^^^^^ not found in this scope + | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern +help: if you meant to collect the rest of the slice in `start`, use the at operator + | +LL | let start @ .. = r; + | + +help: if you meant to destructure a range use a struct pattern + | +LL - let start.. = r; +LL + let RangeFrom { start } = r; + | + +error[E0425]: cannot find value `end` in this scope + --> $DIR/suggest-range-struct-destructuring.rs:21:11 + | +LL | let ..end = r; + | ^^^ not found in this scope + | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern +help: if you meant to collect the rest of the slice in `end`, use the at operator + | +LL - let ..end = r; +LL + let end @ .. = r; + | +help: if you meant to destructure a range use a struct pattern + | +LL - let ..end = r; +LL + let RangeTo { end } = r; + | + +error[E0425]: cannot find value `end` in this scope + --> $DIR/suggest-range-struct-destructuring.rs:26:12 + | +LL | let ..=end = r; + | ^^^ not found in this scope + | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern +help: if you meant to collect the rest of the slice in `end`, use the at operator + | +LL - let ..=end = r; +LL + let end @ .. = r; + | +help: if you meant to destructure a range use a struct pattern + | +LL - let ..=end = r; +LL + let RangeToInclusive { end } = r; + | + +error[E0425]: cannot find value `MISSING` in module `my` + --> $DIR/suggest-range-struct-destructuring.rs:35:13 + | +LL | let my::MISSING..end = r; + | ^^^^^^^ not found in `my` + +error[E0425]: cannot find value `end` in this scope + --> $DIR/suggest-range-struct-destructuring.rs:35:22 + | +LL | let my::MISSING..end = r; + | ^^^ not found in this scope + | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern +help: if you meant to destructure a range use a struct pattern + | +LL - let my::MISSING..end = r; +LL + let Range { start: my::MISSING, end } = r; + | + +error: aborting due to 9 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/typeck/issue-105946.stderr b/tests/ui/typeck/issue-105946.stderr index 30fe2000a4619..3f8733bda7631 100644 --- a/tests/ui/typeck/issue-105946.stderr +++ b/tests/ui/typeck/issue-105946.stderr @@ -4,10 +4,16 @@ error[E0425]: cannot find value `_y` in this scope LL | let [_y..] = [Box::new(1), Box::new(2)]; | ^^ not found in this scope | + = note: range patterns match against the start and end of a range; to bind the components, use a struct pattern help: if you meant to collect the rest of the slice in `_y`, use the at operator | LL | let [_y @ ..] = [Box::new(1), Box::new(2)]; | + +help: if you meant to destructure a range use a struct pattern + | +LL - let [_y..] = [Box::new(1), Box::new(2)]; +LL + let [std::ops::RangeFrom { start: _y }] = [Box::new(1), Box::new(2)]; + | error[E0658]: `X..` patterns in slices are experimental --> $DIR/issue-105946.rs:7:10