Skip to content

Commit e275d2d

Browse files
authored
Rollup merge of #124283 - surechen:fix_123558, r=estebank
Note for E0599 if shadowed bindings has the method. implement #123558 Use a visitor to find earlier shadowed bingings which has the method. r? ``@estebank``
2 parents 8903de3 + b092b5d commit e275d2d

7 files changed

+249
-6
lines changed

compiler/rustc_hir_typeck/src/expr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13461346
if segment.ident.name != kw::Empty {
13471347
if let Some(err) = self.report_method_error(
13481348
span,
1349+
Some(rcvr),
13491350
rcvr_t,
13501351
segment.ident,
13511352
SelfSource::MethodCall(rcvr),

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+1
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
834834
if item_name.name != kw::Empty {
835835
if let Some(e) = self.report_method_error(
836836
span,
837+
None,
837838
ty.normalized,
838839
item_name,
839840
SelfSource::QPath(qself),

compiler/rustc_hir_typeck/src/method/suggest.rs

+207-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::errors::{self, CandidateTraitNote, NoAssociatedItem};
77
use crate::Expectation;
88
use crate::FnCtxt;
99
use core::ops::ControlFlow;
10+
use hir::Expr;
1011
use rustc_ast::ast::Mutability;
1112
use rustc_attr::parse_confusables;
1213
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
@@ -19,7 +20,6 @@ use rustc_hir as hir;
1920
use rustc_hir::def::DefKind;
2021
use rustc_hir::def_id::DefId;
2122
use rustc_hir::lang_items::LangItem;
22-
use rustc_hir::PatKind::Binding;
2323
use rustc_hir::PathSegment;
2424
use rustc_hir::{ExprKind, Node, QPath};
2525
use rustc_infer::infer::{self, RegionVariableOrigin};
@@ -46,7 +46,7 @@ use std::borrow::Cow;
4646

4747
use super::probe::{AutorefOrPtrAdjustment, IsSuggestion, Mode, ProbeScope};
4848
use super::{CandidateSource, MethodError, NoMatchData};
49-
use rustc_hir::intravisit::Visitor;
49+
use rustc_hir::intravisit::{self, Visitor};
5050

5151
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5252
fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool {
@@ -188,6 +188,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
188188
pub fn report_method_error(
189189
&self,
190190
span: Span,
191+
rcvr_opt: Option<&'tcx hir::Expr<'tcx>>,
191192
rcvr_ty: Ty<'tcx>,
192193
item_name: Ident,
193194
source: SelfSource<'tcx>,
@@ -212,6 +213,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
212213
MethodError::NoMatch(mut no_match_data) => {
213214
return self.report_no_match_method_error(
214215
span,
216+
rcvr_opt,
215217
rcvr_ty,
216218
item_name,
217219
source,
@@ -356,9 +358,197 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
356358
err
357359
}
358360

361+
pub fn suggest_use_shadowed_binding_with_method(
362+
&self,
363+
rcvr_opt: Option<&'tcx hir::Expr<'tcx>>,
364+
method_name: Ident,
365+
ty_str_reported: &str,
366+
err: &mut Diag<'_>,
367+
) {
368+
#[derive(Debug)]
369+
struct LetStmt {
370+
ty_hir_id_opt: Option<hir::HirId>,
371+
binding_id: hir::HirId,
372+
span: Span,
373+
init_hir_id: hir::HirId,
374+
}
375+
376+
// Used for finding suggest binding.
377+
// ```rust
378+
// earlier binding for suggesting:
379+
// let y = vec![1, 2];
380+
// now binding:
381+
// if let Some(y) = x {
382+
// y.push(y);
383+
// }
384+
// ```
385+
struct LetVisitor<'a, 'tcx> {
386+
// Error binding which don't have `method_name`.
387+
binding_name: Symbol,
388+
binding_id: hir::HirId,
389+
// Used for check if the suggest binding has `method_name`.
390+
fcx: &'a FnCtxt<'a, 'tcx>,
391+
call_expr: &'tcx Expr<'tcx>,
392+
method_name: Ident,
393+
// Suggest the binding which is shallowed.
394+
sugg_let: Option<LetStmt>,
395+
}
396+
397+
impl<'a, 'tcx> LetVisitor<'a, 'tcx> {
398+
// Check scope of binding.
399+
fn is_sub_scope(&self, sub_id: hir::ItemLocalId, super_id: hir::ItemLocalId) -> bool {
400+
let scope_tree = self.fcx.tcx.region_scope_tree(self.fcx.body_id);
401+
if let Some(sub_var_scope) = scope_tree.var_scope(sub_id)
402+
&& let Some(super_var_scope) = scope_tree.var_scope(super_id)
403+
&& scope_tree.is_subscope_of(sub_var_scope, super_var_scope)
404+
{
405+
return true;
406+
}
407+
false
408+
}
409+
410+
// Check if an earlier shadowed binding make `the receiver` of a MethodCall has the method.
411+
// If it does, record the earlier binding for subsequent notes.
412+
fn check_and_add_sugg_binding(&mut self, binding: LetStmt) -> bool {
413+
if !self.is_sub_scope(self.binding_id.local_id, binding.binding_id.local_id) {
414+
return false;
415+
}
416+
417+
// Get the earlier shadowed binding'ty and use it to check the method.
418+
if let Some(ty_hir_id) = binding.ty_hir_id_opt
419+
&& let Some(tyck_ty) = self.fcx.node_ty_opt(ty_hir_id)
420+
{
421+
if self
422+
.fcx
423+
.lookup_probe_for_diagnostic(
424+
self.method_name,
425+
tyck_ty,
426+
self.call_expr,
427+
ProbeScope::TraitsInScope,
428+
None,
429+
)
430+
.is_ok()
431+
{
432+
self.sugg_let = Some(binding);
433+
return true;
434+
} else {
435+
return false;
436+
}
437+
}
438+
439+
// If the shadowed binding has an an itializer expression,
440+
// use the initializer expression'ty to try to find the method again.
441+
// For example like: `let mut x = Vec::new();`,
442+
// `Vec::new()` is the itializer expression.
443+
if let Some(self_ty) = self.fcx.node_ty_opt(binding.init_hir_id)
444+
&& self
445+
.fcx
446+
.lookup_probe_for_diagnostic(
447+
self.method_name,
448+
self_ty,
449+
self.call_expr,
450+
ProbeScope::TraitsInScope,
451+
None,
452+
)
453+
.is_ok()
454+
{
455+
self.sugg_let = Some(binding);
456+
return true;
457+
}
458+
return false;
459+
}
460+
}
461+
462+
impl<'v> Visitor<'v> for LetVisitor<'_, '_> {
463+
type Result = ControlFlow<()>;
464+
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) -> Self::Result {
465+
if let hir::StmtKind::Let(&hir::LetStmt { pat, ty, init, .. }) = ex.kind
466+
&& let hir::PatKind::Binding(_, binding_id, binding_name, ..) = pat.kind
467+
&& let Some(init) = init
468+
&& binding_name.name == self.binding_name
469+
&& binding_id != self.binding_id
470+
{
471+
if self.check_and_add_sugg_binding(LetStmt {
472+
ty_hir_id_opt: if let Some(ty) = ty { Some(ty.hir_id) } else { None },
473+
binding_id: binding_id,
474+
span: pat.span,
475+
init_hir_id: init.hir_id,
476+
}) {
477+
return ControlFlow::Break(());
478+
}
479+
ControlFlow::Continue(())
480+
} else {
481+
hir::intravisit::walk_stmt(self, ex)
482+
}
483+
}
484+
485+
// Used for find the error binding.
486+
// When the visitor reaches this point, all the shadowed bindings
487+
// have been found, so the visitor ends.
488+
fn visit_pat(&mut self, p: &'v hir::Pat<'v>) -> Self::Result {
489+
match p.kind {
490+
hir::PatKind::Binding(_, binding_id, binding_name, _) => {
491+
if binding_name.name == self.binding_name && binding_id == self.binding_id {
492+
return ControlFlow::Break(());
493+
}
494+
}
495+
_ => {
496+
intravisit::walk_pat(self, p);
497+
}
498+
}
499+
ControlFlow::Continue(())
500+
}
501+
}
502+
503+
if let Some(rcvr) = rcvr_opt
504+
&& let hir::ExprKind::Path(QPath::Resolved(_, path)) = rcvr.kind
505+
&& let hir::def::Res::Local(recv_id) = path.res
506+
&& let Some(segment) = path.segments.first()
507+
{
508+
let map = self.infcx.tcx.hir();
509+
let body_id = self.tcx.hir().body_owned_by(self.body_id);
510+
let body = map.body(body_id);
511+
512+
if let Node::Expr(call_expr) = self.tcx.parent_hir_node(rcvr.hir_id) {
513+
let mut let_visitor = LetVisitor {
514+
fcx: self,
515+
call_expr,
516+
binding_name: segment.ident.name,
517+
binding_id: recv_id,
518+
method_name,
519+
sugg_let: None,
520+
};
521+
let_visitor.visit_body(body);
522+
if let Some(sugg_let) = let_visitor.sugg_let
523+
&& let Some(self_ty) = self.node_ty_opt(sugg_let.init_hir_id)
524+
{
525+
let _sm = self.infcx.tcx.sess.source_map();
526+
let rcvr_name = segment.ident.name;
527+
let mut span = MultiSpan::from_span(sugg_let.span);
528+
span.push_span_label(sugg_let.span,
529+
format!("`{rcvr_name}` of type `{self_ty}` that has method `{method_name}` defined earlier here"));
530+
span.push_span_label(
531+
self.tcx.hir().span(recv_id),
532+
format!(
533+
"earlier `{rcvr_name}` shadowed here with type `{ty_str_reported}`"
534+
),
535+
);
536+
err.span_note(
537+
span,
538+
format!(
539+
"there's an earlier shadowed binding `{rcvr_name}` of type `{self_ty}` \
540+
that has method `{method_name}` available"
541+
),
542+
);
543+
}
544+
}
545+
}
546+
}
547+
359548
pub fn report_no_match_method_error(
360549
&self,
361550
mut span: Span,
551+
rcvr_opt: Option<&'tcx hir::Expr<'tcx>>,
362552
rcvr_ty: Ty<'tcx>,
363553
item_name: Ident,
364554
source: SelfSource<'tcx>,
@@ -451,7 +641,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
451641
let mut err = if is_write && let SelfSource::MethodCall(rcvr_expr) = source {
452642
self.suggest_missing_writer(rcvr_ty, rcvr_expr)
453643
} else {
454-
tcx.dcx().create_err(NoAssociatedItem {
644+
let mut err = tcx.dcx().create_err(NoAssociatedItem {
455645
span,
456646
item_kind,
457647
item_name,
@@ -461,9 +651,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
461651
} else {
462652
rcvr_ty.prefix_string(self.tcx)
463653
},
464-
ty_str: ty_str_reported,
654+
ty_str: ty_str_reported.clone(),
465655
trait_missing_method,
466-
})
656+
});
657+
658+
if is_method {
659+
self.suggest_use_shadowed_binding_with_method(
660+
rcvr_opt,
661+
item_name,
662+
&ty_str_reported,
663+
&mut err,
664+
);
665+
}
666+
667+
err
467668
};
468669
if tcx.sess.source_map().is_multiline(sugg_span) {
469670
err.span_label(sugg_span.with_hi(span.lo()), "");
@@ -2240,7 +2441,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22402441
type Result = ControlFlow<Option<&'v hir::Expr<'v>>>;
22412442
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) -> Self::Result {
22422443
if let hir::StmtKind::Let(&hir::LetStmt { pat, init, .. }) = ex.kind
2243-
&& let Binding(_, _, ident, ..) = pat.kind
2444+
&& let hir::PatKind::Binding(_, _, ident, ..) = pat.kind
22442445
&& ident.name == self.ident_name
22452446
{
22462447
ControlFlow::Break(init)

tests/ui/attributes/rustc_confusables_std_cases.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ error[E0599]: no method named `push` found for struct `VecDeque` in the current
2626
LL | x.push(1);
2727
| ^^^^ method not found in `VecDeque<_>`
2828
|
29+
note: there's an earlier shadowed binding `x` of type `Vec<_>` that has method `push` available
30+
--> $DIR/rustc_confusables_std_cases.rs:8:9
31+
|
32+
LL | let mut x = Vec::new();
33+
| ^^^^^ `x` of type `Vec<_>` that has method `push` defined earlier here
34+
...
35+
LL | let mut x = VecDeque::new();
36+
| ----- earlier `x` shadowed here with type `VecDeque`
2937
help: you might have meant to use `push_back`
3038
|
3139
LL | x.push_back(1);

tests/ui/derives/deriving-with-repr-packed-2.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ LL | struct NonCopy;
1010
LL | _ = x.clone();
1111
| ^^^^^ method cannot be called on `Foo<NonCopy>` due to unsatisfied trait bounds
1212
|
13+
note: there's an earlier shadowed binding `x` of type `Foo<u32>` that has method `clone` available
14+
--> $DIR/deriving-with-repr-packed-2.rs:13:9
15+
|
16+
LL | let x: Foo<u32> = Foo(1, 2, 3);
17+
| ^ `x` of type `Foo<u32>` that has method `clone` defined earlier here
18+
...
19+
LL | let x: Foo<NonCopy> = Foo(NonCopy, NonCopy, NonCopy);
20+
| - earlier `x` shadowed here with type `Foo<NonCopy>`
1321
note: the following trait bounds were not satisfied:
1422
`NonCopy: Clone`
1523
`NonCopy: Copy`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
let x = Some(3);
3+
let y = vec![1, 2];
4+
if let Some(y) = x {
5+
y.push(y); //~ ERROR E0599
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0599]: no method named `push` found for type `{integer}` in the current scope
2+
--> $DIR/account-for-shadowed-bindings-issue-123558.rs:5:11
3+
|
4+
LL | y.push(y);
5+
| ^^^^ method not found in `{integer}`
6+
|
7+
note: there's an earlier shadowed binding `y` of type `Vec<{integer}>` that has method `push` available
8+
--> $DIR/account-for-shadowed-bindings-issue-123558.rs:3:9
9+
|
10+
LL | let y = vec![1, 2];
11+
| ^ `y` of type `Vec<{integer}>` that has method `push` defined earlier here
12+
LL | if let Some(y) = x {
13+
| - earlier `y` shadowed here with type `{integer}`
14+
15+
error: aborting due to 1 previous error
16+
17+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)