Consider method return type for various method suggestions

This commit is contained in:
Michael Goulet 2023-01-08 22:31:47 +00:00
parent 7ee6cebd8a
commit 38491c6579
18 changed files with 224 additions and 38 deletions

View file

@ -499,6 +499,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.probe_for_name(
Mode::MethodCall,
segment.ident,
expected.only_has_type(self),
IsSuggestion(true),
callee_ty,
call_expr.hir_id,

View file

@ -560,6 +560,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.probe_for_name(
probe::Mode::MethodCall,
path.ident,
None,
probe::IsSuggestion(true),
self_ty,
deref.hir_id,
@ -570,6 +571,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let in_scope_methods = self.probe_for_name_many(
probe::Mode::MethodCall,
path.ident,
Some(expected),
probe::IsSuggestion(true),
self_ty,
deref.hir_id,
@ -581,6 +583,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let all_methods = self.probe_for_name_many(
probe::Mode::MethodCall,
path.ident,
Some(expected),
probe::IsSuggestion(true),
self_ty,
deref.hir_id,
@ -1850,10 +1853,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return;
}
let mut expr = end.expr;
let mut expectation = Some(expected_ty);
while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind {
// Getting to the root receiver and asserting it is a fn call let's us ignore cases in
// `src/test/ui/methods/issues/issue-90315.stderr`.
expr = rcvr;
// If we have more than one layer of calls, then the expected ty
// cannot guide the method probe.
expectation = None;
}
let hir::ExprKind::Call(method_name, _) = expr.kind else { return; };
let ty::Adt(adt, _) = checked_ty.kind() else { return; };
@ -1872,6 +1879,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let Ok(_pick) = self.probe_for_name(
probe::Mode::MethodCall,
*ident,
expectation,
probe::IsSuggestion(true),
self_ty,
expr.hir_id,

View file

@ -351,7 +351,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ExprKind::Struct(qpath, fields, ref base_expr) => {
self.check_expr_struct(expr, expected, qpath, fields, base_expr)
}
ExprKind::Field(base, field) => self.check_field(expr, &base, field),
ExprKind::Field(base, field) => self.check_field(expr, &base, field, expected),
ExprKind::Index(base, idx) => self.check_expr_index(base, idx, expr),
ExprKind::Yield(value, ref src) => self.check_expr_yield(value, expr, src),
hir::ExprKind::Err => tcx.ty_error(),
@ -1244,6 +1244,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
SelfSource::MethodCall(rcvr),
error,
Some((rcvr, args)),
expected,
) {
err.emit();
}
@ -2186,6 +2187,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
base: &'tcx hir::Expr<'tcx>,
field: Ident,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field);
let base_ty = self.check_expr(base);
@ -2244,12 +2246,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// (#90483) apply adjustments to avoid ExprUseVisitor from
// creating erroneous projection.
self.apply_adjustments(base, adjustments);
self.ban_private_field_access(expr, base_ty, field, did);
self.ban_private_field_access(expr, base_ty, field, did, expected.only_has_type(self));
return self.tcx().ty_error();
}
if field.name == kw::Empty {
} else if self.method_exists(field, base_ty, expr.hir_id, true) {
} else if self.method_exists(
field,
base_ty,
expr.hir_id,
true,
expected.only_has_type(self),
) {
self.ban_take_value_of_method(expr, base_ty, field);
} else if !base_ty.is_primitive_ty() {
self.ban_nonexisting_field(field, base, expr, base_ty);
@ -2427,6 +2435,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr_t: Ty<'tcx>,
field: Ident,
base_did: DefId,
return_ty: Option<Ty<'tcx>>,
) {
let struct_path = self.tcx().def_path_str(base_did);
let kind_name = self.tcx().def_kind(base_did).descr(base_did);
@ -2438,7 +2447,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
err.span_label(field.span, "private field");
// Also check if an accessible method exists, which is often what is meant.
if self.method_exists(field, expr_t, expr.hir_id, false) && !self.expr_in_place(expr.hir_id)
if self.method_exists(field, expr_t, expr.hir_id, false, return_ty)
&& !self.expr_in_place(expr.hir_id)
{
self.suggest_method_call(
&mut err,

View file

@ -841,6 +841,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
SelfSource::QPath(qself),
error,
None,
Expectation::NoExpectation,
) {
e.emit();
}

View file

@ -1343,6 +1343,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let Ok(pick) = self.probe_for_name(
Mode::Path,
Ident::new(capitalized_name, segment.ident.span),
Some(expected_ty),
IsSuggestion(true),
self_ty,
expr.hir_id,

View file

@ -97,10 +97,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self_ty: Ty<'tcx>,
call_expr_id: hir::HirId,
allow_private: bool,
return_type: Option<Ty<'tcx>>,
) -> bool {
match self.probe_for_name(
probe::Mode::MethodCall,
method_name,
return_type,
IsSuggestion(false),
self_ty,
call_expr_id,
@ -118,7 +120,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Err(Ambiguity(..)) => true,
Err(PrivateMatch(..)) => allow_private,
Err(IllegalSizedBound { .. }) => true,
Err(BadReturnType) => bug!("no return type expectations but got BadReturnType"),
Err(BadReturnType) => false,
}
}
@ -137,6 +139,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.probe_for_name(
probe::Mode::MethodCall,
method_name,
None,
IsSuggestion(true),
self_ty,
call_expr.hir_id,
@ -258,6 +261,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let pick = self.probe_for_name(
probe::Mode::MethodCall,
method_name,
None,
IsSuggestion(false),
self_ty,
call_expr.hir_id,
@ -484,6 +488,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let pick = self.probe_for_name(
probe::Mode::Path,
method_name,
None,
IsSuggestion(false),
self_ty,
expr_id,

View file

@ -304,6 +304,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&self,
mode: Mode,
item_name: Ident,
return_type: Option<Ty<'tcx>>,
is_suggestion: IsSuggestion,
self_ty: Ty<'tcx>,
scope_expr_id: hir::HirId,
@ -313,7 +314,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
item_name.span,
mode,
Some(item_name),
None,
return_type,
is_suggestion,
self_ty,
scope_expr_id,
@ -327,6 +328,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&self,
mode: Mode,
item_name: Ident,
return_type: Option<Ty<'tcx>>,
is_suggestion: IsSuggestion,
self_ty: Ty<'tcx>,
scope_expr_id: hir::HirId,
@ -336,7 +338,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
item_name.span,
mode,
Some(item_name),
None,
return_type,
is_suggestion,
self_ty,
scope_expr_id,

View file

@ -2,6 +2,7 @@
//! found or is otherwise invalid.
use crate::errors;
use crate::Expectation;
use crate::FnCtxt;
use rustc_ast::ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -108,6 +109,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
source: SelfSource<'tcx>,
error: MethodError<'tcx>,
args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>,
expected: Expectation<'tcx>,
) -> Option<DiagnosticBuilder<'_, ErrorGuaranteed>> {
// Avoid suggestions when we don't know what's going on.
if rcvr_ty.references_error() {
@ -131,6 +133,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
args,
sugg_span,
&mut no_match_data,
expected,
);
}
@ -250,6 +253,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>,
sugg_span: Span,
no_match_data: &mut NoMatchData<'tcx>,
expected: Expectation<'tcx>,
) -> Option<DiagnosticBuilder<'_, ErrorGuaranteed>> {
let mode = no_match_data.mode;
let tcx = self.tcx;
@ -320,7 +324,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let Mode::MethodCall = mode && let SelfSource::MethodCall(cal) = source {
self.suggest_await_before_method(
&mut err, item_name, rcvr_ty, cal, span,
&mut err, item_name, rcvr_ty, cal, span, expected.only_has_type(self),
);
}
if let Some(span) =
@ -898,7 +902,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Don't suggest (for example) `expr.field.clone()` if `expr.clone()`
// can't be called due to `typeof(expr): Clone` not holding.
if unsatisfied_predicates.is_empty() {
self.suggest_calling_method_on_field(&mut err, source, span, rcvr_ty, item_name);
self.suggest_calling_method_on_field(
&mut err,
source,
span,
rcvr_ty,
item_name,
expected.only_has_type(self),
);
}
self.check_for_inner_self(&mut err, source, rcvr_ty, item_name);
@ -922,6 +933,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&unsatisfied_predicates,
&static_candidates,
unsatisfied_bounds,
expected.only_has_type(self),
);
}
@ -987,7 +999,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
self.check_for_deref_method(&mut err, source, rcvr_ty, item_name);
self.check_for_deref_method(&mut err, source, rcvr_ty, item_name, expected);
return Some(err);
}
@ -1377,6 +1389,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let pick = self.probe_for_name(
Mode::MethodCall,
item_name,
None,
IsSuggestion(true),
range_ty,
expr.hir_id,
@ -1587,6 +1600,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
span: Span,
actual: Ty<'tcx>,
item_name: Ident,
return_type: Option<Ty<'tcx>>,
) {
if let SelfSource::MethodCall(expr) = source
&& let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id()
@ -1610,10 +1624,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.check_for_nested_field_satisfying(
span,
&|_, field_ty| {
self.lookup_probe(
self.probe_for_name(
Mode::MethodCall,
item_name,
return_type,
IsSuggestion(true),
field_ty,
call_expr,
call_expr.hir_id,
ProbeScope::TraitsInScope,
)
.map_or(false, |pick| {
@ -2010,12 +2027,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self_source: SelfSource<'tcx>,
rcvr_ty: Ty<'tcx>,
item_name: Ident,
expected: Expectation<'tcx>,
) {
let SelfSource::QPath(ty) = self_source else { return; };
for (deref_ty, _) in self.autoderef(rustc_span::DUMMY_SP, rcvr_ty).skip(1) {
if let Ok(pick) = self.probe_for_name(
Mode::Path,
item_name,
expected.only_has_type(self),
IsSuggestion(true),
deref_ty,
ty.hir_id,
@ -2080,12 +2099,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty: Ty<'tcx>,
call: &hir::Expr<'_>,
span: Span,
return_type: Option<Ty<'tcx>>,
) {
let output_ty = match self.get_impl_future_output_ty(ty) {
Some(output_ty) => self.resolve_vars_if_possible(output_ty),
_ => return,
};
let method_exists = self.method_exists(item_name, output_ty, call.hir_id, true);
let method_exists =
self.method_exists(item_name, output_ty, call.hir_id, true, return_type);
debug!("suggest_await_before_method: is_method_exist={}", method_exists);
if method_exists {
err.span_suggestion_verbose(
@ -2199,6 +2220,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
)],
static_candidates: &[CandidateSource],
unsatisfied_bounds: bool,
return_type: Option<Ty<'tcx>>,
) {
let mut alt_rcvr_sugg = false;
if let (SelfSource::MethodCall(rcvr), false) = (source, unsatisfied_bounds) {
@ -2221,7 +2243,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
(self.tcx.mk_mut_ref(self.tcx.lifetimes.re_erased, rcvr_ty), "&mut "),
(self.tcx.mk_imm_ref(self.tcx.lifetimes.re_erased, rcvr_ty), "&"),
] {
match self.lookup_probe(item_name, *rcvr_ty, rcvr, ProbeScope::AllTraits) {
match self.probe_for_name(
Mode::MethodCall,
item_name,
return_type,
IsSuggestion(true),
*rcvr_ty,
rcvr.hir_id,
ProbeScope::AllTraits,
) {
Ok(pick) => {
// If the method is defined for the receiver we have, it likely wasn't `use`d.
// We point at the method, but we just skip the rest of the check for arbitrary
@ -2254,10 +2284,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
(self.tcx.mk_diagnostic_item(*rcvr_ty, sym::Rc), "Rc::new"),
] {
if let Some(new_rcvr_t) = *rcvr_ty
&& let Ok(pick) = self.lookup_probe(
&& let Ok(pick) = self.probe_for_name(
Mode::MethodCall,
item_name,
return_type,
IsSuggestion(true),
new_rcvr_t,
rcvr,
rcvr.hir_id,
ProbeScope::AllTraits,
)
{

View file

@ -0,0 +1,24 @@
// edition:2021
// Test that we do not suggest `.await` when it doesn't make sense.
struct A;
impl A {
fn test(&self) -> i32 {
1
}
}
async fn foo() -> A {
A
}
async fn async_main() {
let x: u32 = foo().test();
//~^ ERROR no method named `test` found for opaque type `impl Future<Output = A>` in the current scope
}
fn main() {
let _ = async_main();
}

View file

@ -0,0 +1,9 @@
error[E0599]: no method named `test` found for opaque type `impl Future<Output = A>` in the current scope
--> $DIR/dont-suggest-await-on-method-return-mismatch.rs:18:24
|
LL | let x: u32 = foo().test();
| ^^^^ method not found in `impl Future<Output = A>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0599`.

View file

@ -0,0 +1,18 @@
struct Wrapper<T>(T);
impl Wrapper<Option<i32>> {
fn inner_mut(&self) -> Option<&mut i32> {
self.as_mut()
//~^ ERROR no method named `as_mut` found for reference `&Wrapper<Option<i32>>` in the current scope
//~| HELP one of the expressions' fields has a method of the same name
//~| HELP items from traits can only be used if
}
fn inner_mut_bad(&self) -> Option<&mut u32> {
self.as_mut()
//~^ ERROR no method named `as_mut` found for reference `&Wrapper<Option<i32>>` in the current scope
//~| HELP items from traits can only be used if
}
}
fn main() {}

View file

@ -0,0 +1,27 @@
error[E0599]: no method named `as_mut` found for reference `&Wrapper<Option<i32>>` in the current scope
--> $DIR/field-method-suggestion-using-return-ty.rs:5:14
|
LL | self.as_mut()
| ^^^^^^ method not found in `&Wrapper<Option<i32>>`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `as_mut`, perhaps you need to implement it:
candidate #1: `AsMut`
help: one of the expressions' fields has a method of the same name
|
LL | self.0.as_mut()
| ++
error[E0599]: no method named `as_mut` found for reference `&Wrapper<Option<i32>>` in the current scope
--> $DIR/field-method-suggestion-using-return-ty.rs:12:14
|
LL | self.as_mut()
| ^^^^^^ method not found in `&Wrapper<Option<i32>>`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `as_mut`, perhaps you need to implement it:
candidate #1: `AsMut`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0599`.

View file

@ -3,11 +3,6 @@ error[E0616]: field `len` of struct `Foo` is private
|
LL | if x.len {
| ^^^ private field
|
help: a method `len` also exists, call it with parentheses
|
LL | if x.len() {
| ++
error: aborting due to previous error

View file

@ -0,0 +1,34 @@
// run-rustfix
#![allow(unused)]
fn as_ref() -> Option<Vec<u8>> {
None
}
struct Type {
option: Option<Vec<u8>>
}
trait Trait {
fn foo(&self) -> &Vec<u8>;
}
impl Trait for Option<Vec<u8>> {
fn foo(&self) -> &Vec<u8> {
self.as_ref().unwrap()
}
}
impl Type {
fn method(&self) -> Option<&Vec<u8>> {
self.option.as_ref().map(|x| x)
//~^ ERROR E0308
}
fn method2(&self) -> Option<&u8> {
self.option.foo().get(0)
//~^ ERROR E0425
//~| ERROR E0308
}
}
fn main() {
let _ = Type { option: None }.method();
}

View file

@ -1,3 +1,7 @@
// run-rustfix
#![allow(unused)]
fn as_ref() -> Option<Vec<u8>> {
None
}
@ -5,20 +9,20 @@ struct Type {
option: Option<Vec<u8>>
}
trait Trait {
fn foo(&self) -> Vec<u8>;
fn foo(&self) -> &Vec<u8>;
}
impl Trait for Option<Vec<u8>> {
fn foo(&self) -> Vec<u8> {
vec![1, 2, 3]
fn foo(&self) -> &Vec<u8> {
self.as_ref().unwrap()
}
}
impl Type {
fn method(&self) -> Option<Vec<u8>> {
fn method(&self) -> Option<&Vec<u8>> {
self.option..as_ref().map(|x| x)
//~^ ERROR E0308
}
fn method2(&self) -> &u8 {
fn method2(&self) -> Option<&u8> {
self.option..foo().get(0)
//~^ ERROR E0425
//~| ERROR E0308

View file

@ -1,5 +1,5 @@
error[E0425]: cannot find function `foo` in this scope
--> $DIR/method-access-to-range-literal-typo.rs:22:22
--> $DIR/method-access-to-range-literal-typo.rs:26:22
|
LL | self.option..foo().get(0)
| ^^^ not found in this scope
@ -11,15 +11,15 @@ LL + self.option.foo().get(0)
|
error[E0308]: mismatched types
--> $DIR/method-access-to-range-literal-typo.rs:18:9
--> $DIR/method-access-to-range-literal-typo.rs:22:9
|
LL | fn method(&self) -> Option<Vec<u8>> {
| --------------- expected `Option<Vec<u8>>` because of return type
LL | fn method(&self) -> Option<&Vec<u8>> {
| ---------------- expected `Option<&Vec<u8>>` because of return type
LL | self.option..as_ref().map(|x| x)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range`
|
= note: expected enum `Option<_>`
found struct `std::ops::Range<Option<_>>`
= note: expected enum `Option<&Vec<u8>>`
found struct `std::ops::Range<Option<Vec<u8>>>`
help: you likely meant to write a method call instead of a range
|
LL - self.option..as_ref().map(|x| x)
@ -27,15 +27,15 @@ LL + self.option.as_ref().map(|x| x)
|
error[E0308]: mismatched types
--> $DIR/method-access-to-range-literal-typo.rs:22:9
--> $DIR/method-access-to-range-literal-typo.rs:26:9
|
LL | fn method2(&self) -> &u8 {
| --- expected `&u8` because of return type
LL | fn method2(&self) -> Option<&u8> {
| ----------- expected `Option<&u8>` because of return type
LL | self.option..foo().get(0)
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&u8`, found struct `Range`
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range`
|
= note: expected reference `&u8`
found struct `std::ops::Range<Option<Vec<u8>>>`
= note: expected enum `Option<&u8>`
found struct `std::ops::Range<Option<Vec<u8>>>`
help: you likely meant to write a method call instead of a range
|
LL - self.option..foo().get(0)

View file

@ -17,6 +17,13 @@ LL | impl<'a, T: Perpetrator /*+ ?Sized*/> VictimTrait for Victim<'a, T> {
| ^ ----------- -------------
| |
| unsatisfied trait bound introduced here
= note: the following trait bounds were not satisfied:
`&Victim<'_, Self>: VictimTrait`
`&mut Victim<'_, Self>: VictimTrait`
help: consider relaxing the type parameter's implicit `Sized` bound
|
LL | impl<'a, T: ?Sized + Perpetrator /*+ ?Sized*/> VictimTrait for Victim<'a, T> {
| ++++++++
help: consider relaxing the type parameter's implicit `Sized` bound
|
LL | impl<'a, T: ?Sized + Perpetrator /*+ ?Sized*/> VictimTrait for Victim<'a, T> {

View file

@ -17,6 +17,13 @@ LL | impl<'a, T: Perpetrator /*+ ?Sized*/> VictimTrait for Victim<'a, T> {
| ^ ----------- -------------
| |
| unsatisfied trait bound introduced here
= note: the following trait bounds were not satisfied:
`&Victim<'_, Self>: VictimTrait`
`&mut Victim<'_, Self>: VictimTrait`
help: consider relaxing the type parameter's implicit `Sized` bound
|
LL | impl<'a, T: ?Sized + Perpetrator /*+ ?Sized*/> VictimTrait for Victim<'a, T> {
| ++++++++
help: consider relaxing the type parameter's implicit `Sized` bound
|
LL | impl<'a, T: ?Sized + Perpetrator /*+ ?Sized*/> VictimTrait for Victim<'a, T> {