Skip to content

Commit 518d5eb

Browse files
authored
Rollup merge of rust-lang#101908 - chenyukang:fix-101880, r=estebank
Suggest let for assignment, and some code refactor Fixes rust-lang#101880
2 parents e029c1f + eb68e27 commit 518d5eb

File tree

6 files changed

+165
-53
lines changed

6 files changed

+165
-53
lines changed

compiler/rustc_resolve/src/late.rs

+8
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,9 @@ struct DiagnosticMetadata<'ast> {
524524
/// Used to detect possible `if let` written without `let` and to provide structured suggestion.
525525
in_if_condition: Option<&'ast Expr>,
526526

527+
/// Used to detect possible new binding written without `let` and to provide structured suggestion.
528+
in_assignment: Option<&'ast Expr>,
529+
527530
/// If we are currently in a trait object definition. Used to point at the bounds when
528531
/// encountering a struct or enum.
529532
current_trait_object: Option<&'ast [ast::GenericBound]>,
@@ -3905,6 +3908,11 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
39053908
self.resolve_expr(elem, Some(expr));
39063909
self.visit_expr(idx);
39073910
}
3911+
ExprKind::Assign(..) => {
3912+
let old = self.diagnostic_metadata.in_assignment.replace(expr);
3913+
visit::walk_expr(self, expr);
3914+
self.diagnostic_metadata.in_assignment = old;
3915+
}
39083916
_ => {
39093917
visit::walk_expr(self, expr);
39103918
}

compiler/rustc_resolve/src/late/diagnostics.rs

+43-53
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,9 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
708708

709709
// If the trait has a single item (which wasn't matched by Levenshtein), suggest it
710710
let suggestion = self.get_single_associated_item(&path, &source, is_expected);
711-
self.r.add_typo_suggestion(err, suggestion, ident_span);
711+
if !self.r.add_typo_suggestion(err, suggestion, ident_span) {
712+
fallback = !self.let_binding_suggestion(err, ident_span);
713+
}
712714
}
713715
fallback
714716
}
@@ -1105,41 +1107,14 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
11051107
// where a brace being opened means a block is being started. Look
11061108
// ahead for the next text to see if `span` is followed by a `{`.
11071109
let sm = self.r.session.source_map();
1108-
let mut sp = span;
1109-
loop {
1110-
sp = sm.next_point(sp);
1111-
match sm.span_to_snippet(sp) {
1112-
Ok(ref snippet) => {
1113-
if snippet.chars().any(|c| !c.is_whitespace()) {
1114-
break;
1115-
}
1116-
}
1117-
_ => break,
1118-
}
1119-
}
1110+
let sp = sm.span_look_ahead(span, None, Some(50));
11201111
let followed_by_brace = matches!(sm.span_to_snippet(sp), Ok(ref snippet) if snippet == "{");
11211112
// In case this could be a struct literal that needs to be surrounded
11221113
// by parentheses, find the appropriate span.
1123-
let mut i = 0;
1124-
let mut closing_brace = None;
1125-
loop {
1126-
sp = sm.next_point(sp);
1127-
match sm.span_to_snippet(sp) {
1128-
Ok(ref snippet) => {
1129-
if snippet == "}" {
1130-
closing_brace = Some(span.to(sp));
1131-
break;
1132-
}
1133-
}
1134-
_ => break,
1135-
}
1136-
i += 1;
1137-
// The bigger the span, the more likely we're incorrect --
1138-
// bound it to 100 chars long.
1139-
if i > 100 {
1140-
break;
1141-
}
1142-
}
1114+
let closing_span = sm.span_look_ahead(span, Some("}"), Some(50));
1115+
let closing_brace: Option<Span> = sm
1116+
.span_to_snippet(closing_span)
1117+
.map_or(None, |s| if s == "}" { Some(span.to(closing_span)) } else { None });
11431118
(followed_by_brace, closing_brace)
11441119
}
11451120

@@ -1779,26 +1754,16 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
17791754
}
17801755
}
17811756
if let Ok(base_snippet) = base_snippet {
1782-
let mut sp = after_colon_sp;
1783-
for _ in 0..100 {
1784-
// Try to find an assignment
1785-
sp = sm.next_point(sp);
1786-
let snippet = sm.span_to_snippet(sp);
1787-
match snippet {
1788-
Ok(ref x) if x.as_str() == "=" => {
1789-
err.span_suggestion(
1790-
base_span,
1791-
"maybe you meant to write an assignment here",
1792-
format!("let {}", base_snippet),
1793-
Applicability::MaybeIncorrect,
1794-
);
1795-
show_label = false;
1796-
break;
1797-
}
1798-
Ok(ref x) if x.as_str() == "\n" => break,
1799-
Err(_) => break,
1800-
Ok(_) => {}
1801-
}
1757+
// Try to find an assignment
1758+
let eq_span = sm.span_look_ahead(after_colon_sp, Some("="), Some(50));
1759+
if let Ok(ref snippet) = sm.span_to_snippet(eq_span) && snippet == "=" {
1760+
err.span_suggestion(
1761+
base_span,
1762+
"maybe you meant to write an assignment here",
1763+
format!("let {}", base_snippet),
1764+
Applicability::MaybeIncorrect,
1765+
);
1766+
show_label = false;
18021767
}
18031768
}
18041769
}
@@ -1815,6 +1780,31 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
18151780
false
18161781
}
18171782

1783+
fn let_binding_suggestion(&self, err: &mut Diagnostic, ident_span: Span) -> bool {
1784+
// try to give a suggestion for this pattern: `name = 1`, which is common in other languages
1785+
let mut added_suggestion = false;
1786+
if let Some(Expr { kind: ExprKind::Assign(lhs, _rhs, _), .. }) = self.diagnostic_metadata.in_assignment &&
1787+
let ast::ExprKind::Path(None, _) = lhs.kind {
1788+
let sm = self.r.session.source_map();
1789+
let line_span = sm.span_extend_to_line(ident_span);
1790+
let ident_name = sm.span_to_snippet(ident_span).unwrap();
1791+
// HACK(chenyukang): make sure ident_name is at the starting of the line to protect against macros
1792+
if sm
1793+
.span_to_snippet(line_span)
1794+
.map_or(false, |s| s.trim().starts_with(&ident_name))
1795+
{
1796+
err.span_suggestion_verbose(
1797+
ident_span.shrink_to_lo(),
1798+
"you might have meant to introduce a new binding",
1799+
"let ".to_string(),
1800+
Applicability::MaybeIncorrect,
1801+
);
1802+
added_suggestion = true;
1803+
}
1804+
}
1805+
added_suggestion
1806+
}
1807+
18181808
fn find_module(&mut self, def_id: DefId) -> Option<(Module<'a>, ImportSuggestion)> {
18191809
let mut result = None;
18201810
let mut seen_modules = FxHashSet::default();

compiler/rustc_span/src/source_map.rs

+20
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,26 @@ impl SourceMap {
877877
Span::new(BytePos(start_of_next_point), end_of_next_point, sp.ctxt(), None)
878878
}
879879

880+
/// Returns a new span to check next none-whitespace character or some specified expected character
881+
/// If `expect` is none, the first span of non-whitespace character is returned.
882+
/// If `expect` presented, the first span of the character `expect` is returned
883+
/// Otherwise, the span reached to limit is returned.
884+
pub fn span_look_ahead(&self, span: Span, expect: Option<&str>, limit: Option<usize>) -> Span {
885+
let mut sp = span;
886+
for _ in 0..limit.unwrap_or(100 as usize) {
887+
sp = self.next_point(sp);
888+
if let Ok(ref snippet) = self.span_to_snippet(sp) {
889+
if expect.map_or(false, |es| snippet == es) {
890+
break;
891+
}
892+
if expect.is_none() && snippet.chars().any(|c| !c.is_whitespace()) {
893+
break;
894+
}
895+
}
896+
}
897+
sp
898+
}
899+
880900
/// Finds the width of the character, either before or after the end of provided span,
881901
/// depending on the `forwards` parameter.
882902
fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// run-rustfix
2+
3+
fn main() {
4+
let demo = 1; //~ ERROR cannot find value `demo` in this scope
5+
dbg!(demo); //~ ERROR cannot find value `demo` in this scope
6+
7+
let x = "x"; //~ ERROR cannot find value `x` in this scope
8+
println!("x: {}", x); //~ ERROR cannot find value `x` in this scope
9+
10+
if x == "x" {
11+
//~^ ERROR cannot find value `x` in this scope
12+
println!("x is 1");
13+
}
14+
15+
let y = 1 + 2; //~ ERROR cannot find value `y` in this scope
16+
println!("y: {}", y); //~ ERROR cannot find value `y` in this scope
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// run-rustfix
2+
3+
fn main() {
4+
demo = 1; //~ ERROR cannot find value `demo` in this scope
5+
dbg!(demo); //~ ERROR cannot find value `demo` in this scope
6+
7+
x = "x"; //~ ERROR cannot find value `x` in this scope
8+
println!("x: {}", x); //~ ERROR cannot find value `x` in this scope
9+
10+
if x == "x" {
11+
//~^ ERROR cannot find value `x` in this scope
12+
println!("x is 1");
13+
}
14+
15+
y = 1 + 2; //~ ERROR cannot find value `y` in this scope
16+
println!("y: {}", y); //~ ERROR cannot find value `y` in this scope
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
error[E0425]: cannot find value `demo` in this scope
2+
--> $DIR/suggest-let-for-assignment.rs:4:5
3+
|
4+
LL | demo = 1;
5+
| ^^^^
6+
|
7+
help: you might have meant to introduce a new binding
8+
|
9+
LL | let demo = 1;
10+
| +++
11+
12+
error[E0425]: cannot find value `demo` in this scope
13+
--> $DIR/suggest-let-for-assignment.rs:5:10
14+
|
15+
LL | dbg!(demo);
16+
| ^^^^ not found in this scope
17+
18+
error[E0425]: cannot find value `x` in this scope
19+
--> $DIR/suggest-let-for-assignment.rs:7:5
20+
|
21+
LL | x = "x";
22+
| ^
23+
|
24+
help: you might have meant to introduce a new binding
25+
|
26+
LL | let x = "x";
27+
| +++
28+
29+
error[E0425]: cannot find value `x` in this scope
30+
--> $DIR/suggest-let-for-assignment.rs:8:23
31+
|
32+
LL | println!("x: {}", x);
33+
| ^ not found in this scope
34+
35+
error[E0425]: cannot find value `x` in this scope
36+
--> $DIR/suggest-let-for-assignment.rs:10:8
37+
|
38+
LL | if x == "x" {
39+
| ^ not found in this scope
40+
41+
error[E0425]: cannot find value `y` in this scope
42+
--> $DIR/suggest-let-for-assignment.rs:15:5
43+
|
44+
LL | y = 1 + 2;
45+
| ^
46+
|
47+
help: you might have meant to introduce a new binding
48+
|
49+
LL | let y = 1 + 2;
50+
| +++
51+
52+
error[E0425]: cannot find value `y` in this scope
53+
--> $DIR/suggest-let-for-assignment.rs:16:23
54+
|
55+
LL | println!("y: {}", y);
56+
| ^ not found in this scope
57+
58+
error: aborting due to 7 previous errors
59+
60+
For more information about this error, try `rustc --explain E0425`.

0 commit comments

Comments
 (0)