Skip to content

Commit

Permalink
fix #101749, use . instead of :: when accessing a method of an object
Browse files Browse the repository at this point in the history
  • Loading branch information
chenyukang committed Nov 20, 2022
1 parent 2ed65da commit 54b58fc
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 8 deletions.
1 change: 1 addition & 0 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ pub enum StashKey {
/// When an invalid lifetime e.g. `'2` should be reinterpreted
/// as a char literal in the parser
LifetimeIsChar,
CallAssocMethod,
}

fn default_track_diagnostic(_: &Diagnostic) {}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.resolve_ty_and_res_fully_qualified_call(qpath, expr.hir_id, expr.span);
let ty = match res {
Res::Err => {
self.suggest_assoc_method_call(segs);
let e =
self.tcx.sess.delay_span_bug(qpath.span(), "`Res::Err` but no error emitted");
self.set_tainted_by_errors(e);
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
hir_id: hir::HirId,
) -> (Ty<'tcx>, Res) {
let tcx = self.tcx;

let path_segs = match res {
Res::Local(_) | Res::SelfCtor(_) => vec![],
Res::Def(kind, def_id) => <dyn AstConv<'_>>::def_ids_for_value_path_segments(
Expand Down
63 changes: 60 additions & 3 deletions compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::errors;
use crate::FnCtxt;
use rustc_ast::ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::StashKey;
use rustc_errors::{
pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
MultiSpan,
Expand All @@ -13,6 +14,8 @@ use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_hir::PatKind::Binding;
use rustc_hir::PathSegment;
use rustc_hir::{ExprKind, Node, QPath};
use rustc_infer::infer::{
type_variable::{TypeVariableOrigin, TypeVariableOriginKind},
Expand All @@ -35,11 +38,11 @@ use rustc_trait_selection::traits::{
FulfillmentError, Obligation, ObligationCause, ObligationCauseCode,
};

use std::cmp::Ordering;
use std::iter;

use super::probe::{AutorefOrPtrAdjustment, IsSuggestion, Mode, ProbeScope};
use super::{CandidateSource, MethodError, NoMatchData};
use rustc_hir::intravisit::Visitor;
use std::cmp::Ordering;
use std::iter;

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool {
Expand Down Expand Up @@ -1470,6 +1473,60 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
false
}

/// For code `rect::area(...)`,
/// if `rect` is a local variable and `area` is a valid assoc method for it,
/// we try to suggest `rect.area()`
pub(crate) fn suggest_assoc_method_call(&self, segs: &[PathSegment<'_>]) {
debug!("suggest_assoc_method_call segs: {:?}", segs);
if segs.len() != 2 {
return;
}
let (seg1, seg2) = (&segs[0], &segs[1]);
let Some(mut diag) =
self.tcx.sess.diagnostic().steal_diagnostic(seg1.ident.span, StashKey::CallAssocMethod)
else { return };

let map = self.infcx.tcx.hir();
let body_id = map.body_owned_by(seg1.hir_id.owner.def_id);
let body = map.body(body_id);
struct LetVisitor<'a> {
result: Option<&'a hir::Expr<'a>>,
ident_name: Symbol,
}

impl<'v> Visitor<'v> for LetVisitor<'v> {
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
if let hir::StmtKind::Local(hir::Local { pat, init, .. }) = &ex.kind {
if let Binding(_, _, ident, ..) = pat.kind &&
ident.name == self.ident_name {
self.result = *init;
}
}
hir::intravisit::walk_stmt(self, ex);
}
}

let mut visitor = LetVisitor { result: None, ident_name: seg1.ident.name };
visitor.visit_body(&body);

let parent = self.tcx.hir().get_parent_node(seg1.hir_id);
if let Some(Node::Expr(call_expr)) = self.tcx.hir().find(parent) &&
let Some(expr) = visitor.result {
let self_ty = self.check_expr(expr);
let probe = self.lookup_probe(
seg2.ident,
self_ty,
call_expr,
ProbeScope::TraitsInScope,
);
if probe.is_ok() {
diag.emit();
return;
}
}
diag.cancel();
}

/// Suggest calling a method on a field i.e. `a.field.bar()` instead of `a.bar()`
fn suggest_calling_method_on_field(
&self,
Expand Down
64 changes: 60 additions & 4 deletions compiler/rustc_resolve/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1840,13 +1840,20 @@ impl<'a> Resolver<'a> {

(format!("use of undeclared type `{}`", ident), suggestion)
} else {
let suggestion = if ident.name == sym::alloc {
Some((
// crate or module resolve failed, here we try three things:

// 1. special handling for `alloc`
let mut suggestion = None;
if ident.name == sym::alloc {
suggestion = Some((
vec![],
String::from("add `extern crate alloc` to use the `alloc` crate"),
Applicability::MaybeIncorrect,
))
} else {
}

// 2. check whether there is a similar name
suggestion = suggestion.or_else(|| {
self.find_similarly_named_module_or_crate(ident.name, &parent_scope.module).map(
|sugg| {
(
Expand All @@ -1856,7 +1863,56 @@ impl<'a> Resolver<'a> {
)
},
)
};
});

// 3. check whether the name refers to an item in local scope
// then delay to check whether methond name is an associated function or not
// For example:
// ```
// struct Foo;
// impl Foo {
// fn bar(&self) {}
// }
//
// fn main() {
// let foo = Foo {};
// foo::bar(); // suggest to foo.bar();
//}
//```
if suggestion.is_none() &&
let Some(ribs) = ribs &&
let Some(LexicalScopeBinding::Res(Res::Local(local_id))) = self.resolve_ident_in_lexical_scope(
ident,
ValueNS,
parent_scope,
None,
&ribs[ValueNS],
ignore_binding,
)
{
let sm = self.session.source_map();
// only check path with two segments, like `foo::bar(..)`
if let Ok(span) = sm.span_extend_while(ident.span.shrink_to_hi(), |c| c != ' ') &&
let Ok(snippet) = sm.span_to_snippet(span) &&
snippet.starts_with("::") && snippet.matches("::").count() == 1 {
let local_span = *self.pat_span_map.get(&local_id).unwrap();
let mut err = self.session.struct_span_err(
ident.span,
format!(
"`{}` is not a crate or module",
ident
)
);
err.span_suggestion_verbose(
sm.span_extend_while(ident.span.shrink_to_hi(), |c| c == ':').unwrap(),
"maybe you meant to call instance method",
".".to_string(),
Applicability::MaybeIncorrect
);
err.span_warn(local_span, format!("ident `{}` is defined at here", ident));
err.stash(ident.span, rustc_errors::StashKey::CallAssocMethod);
}
};
(format!("use of undeclared crate or module `{}`", ident), suggestion)
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/test/ui/resolve/issue-101749-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
struct Rectangle {
width: i32,
height: i32,
}
impl Rectangle {
fn new(width: i32, height: i32) -> Self {
Self { width, height }
}
}

fn main() {
let rect = Rectangle::new(3, 4);
// `area` is not implemented for `Rectangle`, so this should not suggest
let _ = rect::area();
//~^ ERROR failed to resolve: use of undeclared crate or module `rect`
}
9 changes: 9 additions & 0 deletions src/test/ui/resolve/issue-101749-2.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
error[E0433]: failed to resolve: use of undeclared crate or module `rect`
--> $DIR/issue-101749-2.rs:14:13
|
LL | let _ = rect::area();
| ^^^^ use of undeclared crate or module `rect`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0433`.
21 changes: 21 additions & 0 deletions src/test/ui/resolve/issue-101749.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// run-rustfix
struct Rectangle {
width: i32,
height: i32,
}
impl Rectangle {
fn new(width: i32, height: i32) -> Self {
Self { width, height }
}
fn area(&self) -> i32 {
self.height * self.width
}
}

fn main() {
let rect = Rectangle::new(3, 4);
//~^ WARNING ident `rect` is defined at here
let _ = rect.area();
//~^ ERROR failed to resolve: use of undeclared crate or module `rect`
//~| ERROR `rect` is not a crate or module
}
21 changes: 21 additions & 0 deletions src/test/ui/resolve/issue-101749.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// run-rustfix
struct Rectangle {
width: i32,
height: i32,
}
impl Rectangle {
fn new(width: i32, height: i32) -> Self {
Self { width, height }
}
fn area(&self) -> i32 {
self.height * self.width
}
}

fn main() {
let rect = Rectangle::new(3, 4);
//~^ WARNING ident `rect` is defined at here
let _ = rect::area();
//~^ ERROR failed to resolve: use of undeclared crate or module `rect`
//~| ERROR `rect` is not a crate or module
}
25 changes: 25 additions & 0 deletions src/test/ui/resolve/issue-101749.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
error[E0433]: failed to resolve: use of undeclared crate or module `rect`
--> $DIR/issue-101749.rs:18:13
|
LL | let _ = rect::area();
| ^^^^ use of undeclared crate or module `rect`

error: `rect` is not a crate or module
--> $DIR/issue-101749.rs:18:13
|
LL | let _ = rect::area();
| ^^^^
|
warning: ident `rect` is defined at here
--> $DIR/issue-101749.rs:16:9
|
LL | let rect = Rectangle::new(3, 4);
| ^^^^
help: maybe you meant to call instance method
|
LL | let _ = rect.area();
| ~

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0433`.

0 comments on commit 54b58fc

Please sign in to comment.