Rollup merge of #102288 - mejrs:inner, r=compiler-errors

Suggest unwrapping `???<T>` if a method cannot be found on it but is present on `T`.

This suggests various ways to get inside wrapper types if the method cannot be found on the wrapper type, but is present on the wrappee.

For this PR, those wrapper types include `Localkey`, `MaybeUninit`, `RefCell`, `RwLock` and `Mutex`.
This commit is contained in:
Yuki Okushi 2022-09-28 13:07:17 +09:00 committed by GitHub
commit 9436ffc226
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 378 additions and 66 deletions

View File

@ -2,6 +2,7 @@
//! found or is otherwise invalid.
use crate::check::FnCtxt;
use rustc_ast::ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{
pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
@ -30,7 +31,7 @@ use rustc_trait_selection::traits::{
use std::cmp::Ordering;
use std::iter;
use super::probe::{IsSuggestion, Mode, ProbeScope};
use super::probe::{AutorefOrPtrAdjustment, IsSuggestion, Mode, ProbeScope};
use super::{CandidateSource, MethodError, NoMatchData};
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
@ -983,7 +984,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.check_for_field_method(&mut err, source, span, actual, item_name);
}
self.check_for_unwrap_self(&mut err, source, span, actual, item_name);
self.check_for_inner_self(&mut err, source, span, actual, item_name);
bound_spans.sort();
bound_spans.dedup();
@ -1395,7 +1396,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
fn check_for_unwrap_self(
fn check_for_inner_self(
&self,
err: &mut Diagnostic,
source: SelfSource<'tcx>,
@ -1408,81 +1409,168 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let call_expr = tcx.hir().expect_expr(tcx.hir().get_parent_node(expr.hir_id));
let ty::Adt(kind, substs) = actual.kind() else { return; };
if !kind.is_enum() {
return;
}
match kind.adt_kind() {
ty::AdtKind::Enum => {
let matching_variants: Vec<_> = kind
.variants()
.iter()
.flat_map(|variant| {
let [field] = &variant.fields[..] else { return None; };
let field_ty = field.ty(tcx, substs);
let matching_variants: Vec<_> = kind
.variants()
.iter()
.flat_map(|variant| {
let [field] = &variant.fields[..] else { return None; };
let field_ty = field.ty(tcx, substs);
// Skip `_`, since that'll just lead to ambiguity.
if self.resolve_vars_if_possible(field_ty).is_ty_var() {
return None;
}
// Skip `_`, since that'll just lead to ambiguity.
if self.resolve_vars_if_possible(field_ty).is_ty_var() {
return None;
}
self.lookup_probe(
span,
item_name,
field_ty,
call_expr,
ProbeScope::TraitsInScope,
)
.ok()
.map(|pick| (variant, field, pick))
})
.collect();
self.lookup_probe(span, item_name, field_ty, call_expr, ProbeScope::AllTraits)
.ok()
.map(|pick| (variant, field, pick))
})
.collect();
let ret_ty_matches = |diagnostic_item| {
if let Some(ret_ty) = self
.ret_coercion
.as_ref()
.map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty()))
&& let ty::Adt(kind, _) = ret_ty.kind()
&& tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did())
{
true
} else {
false
}
};
match &matching_variants[..] {
[(_, field, pick)] => {
let self_ty = field.ty(tcx, substs);
err.span_note(
tcx.def_span(pick.item.def_id),
&format!("the method `{item_name}` exists on the type `{self_ty}`"),
);
let (article, kind, variant, question) =
if Some(kind.did()) == tcx.get_diagnostic_item(sym::Result) {
("a", "Result", "Err", ret_ty_matches(sym::Result))
} else if Some(kind.did()) == tcx.get_diagnostic_item(sym::Option) {
("an", "Option", "None", ret_ty_matches(sym::Option))
let ret_ty_matches = |diagnostic_item| {
if let Some(ret_ty) = self
.ret_coercion
.as_ref()
.map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty()))
&& let ty::Adt(kind, _) = ret_ty.kind()
&& tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did())
{
true
} else {
return;
false
}
};
match &matching_variants[..] {
[(_, field, pick)] => {
let self_ty = field.ty(tcx, substs);
err.span_note(
tcx.def_span(pick.item.def_id),
&format!("the method `{item_name}` exists on the type `{self_ty}`"),
);
let (article, kind, variant, question) =
if tcx.is_diagnostic_item(sym::Result, kind.did()) {
("a", "Result", "Err", ret_ty_matches(sym::Result))
} else if tcx.is_diagnostic_item(sym::Option, kind.did()) {
("an", "Option", "None", ret_ty_matches(sym::Option))
} else {
return;
};
if question {
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"use the `?` operator to extract the `{self_ty}` value, propagating \
{article} `{kind}::{variant}` value to the caller"
),
"?",
Applicability::MachineApplicable,
);
} else {
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"consider using `{kind}::expect` to unwrap the `{self_ty}` value, \
panicking if the value is {article} `{kind}::{variant}`"
),
".expect(\"REASON\")",
Applicability::HasPlaceholders,
);
}
}
// FIXME(compiler-errors): Support suggestions for other matching enum variants
_ => {}
}
}
// Target wrapper types - types that wrap or pretend to wrap another type,
// perhaps this inner type is meant to be called?
ty::AdtKind::Struct | ty::AdtKind::Union => {
let [first] = ***substs else { return; };
let ty::GenericArgKind::Type(ty) = first.unpack() else { return; };
let Ok(pick) = self.lookup_probe(
span,
item_name,
ty,
call_expr,
ProbeScope::TraitsInScope,
) else { return; };
let name = self.ty_to_value_string(actual);
let inner_id = kind.did();
let mutable = if let Some(AutorefOrPtrAdjustment::Autoref { mutbl, .. }) =
pick.autoref_or_ptr_adjustment
{
Some(mutbl)
} else {
None
};
if tcx.is_diagnostic_item(sym::LocalKey, inner_id) {
err.help("use `with` or `try_with` to access thread local storage");
} else if Some(kind.did()) == tcx.lang_items().maybe_uninit() {
err.help(format!(
"if this `{name}` has been initialized, \
use one of the `assume_init` methods to access the inner value"
));
} else if tcx.is_diagnostic_item(sym::RefCell, inner_id) {
let (suggestion, borrow_kind, panic_if) = match mutable {
Some(Mutability::Not) => (".borrow()", "borrow", "a mutable borrow exists"),
Some(Mutability::Mut) => {
(".borrow_mut()", "mutably borrow", "any borrows exist")
}
None => return,
};
if question {
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"use the `?` operator to extract the `{self_ty}` value, propagating \
{article} `{kind}::{variant}` value to the caller"
"use `{suggestion}` to {borrow_kind} the `{ty}`, \
panicking if {panic_if}"
),
"?",
Applicability::MachineApplicable,
suggestion,
Applicability::MaybeIncorrect,
);
} else if tcx.is_diagnostic_item(sym::Mutex, inner_id) {
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"use `.lock().unwrap()` to borrow the `{ty}`, \
blocking the current thread until it can be acquired"
),
".lock().unwrap()",
Applicability::MaybeIncorrect,
);
} else if tcx.is_diagnostic_item(sym::RwLock, inner_id) {
let (suggestion, borrow_kind) = match mutable {
Some(Mutability::Not) => (".read().unwrap()", "borrow"),
Some(Mutability::Mut) => (".write().unwrap()", "mutably borrow"),
None => return,
};
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"use `{suggestion}` to {borrow_kind} the `{ty}`, \
blocking the current thread until it can be acquired"
),
suggestion,
Applicability::MaybeIncorrect,
);
} else {
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"consider using `{kind}::expect` to unwrap the `{self_ty}` value, \
panicking if the value is {article} `{kind}::{variant}`"
),
".expect(\"REASON\")",
Applicability::HasPlaceholders,
);
}
return;
};
err.span_note(
tcx.def_span(pick.item.def_id),
&format!("the method `{item_name}` exists on the type `{ty}`"),
);
}
// FIXME(compiler-errors): Support suggestions for other matching enum variants
_ => {}
}
}

View File

@ -224,6 +224,7 @@ symbols! {
Left,
LinkedList,
LintPass,
LocalKey,
Mutex,
MutexGuard,
N,
@ -266,6 +267,7 @@ symbols! {
Rc,
Ready,
Receiver,
RefCell,
Relaxed,
Release,
Result,
@ -274,6 +276,7 @@ symbols! {
Rust,
RustcDecodable,
RustcEncodable,
RwLock,
RwLockReadGuard,
RwLockWriteGuard,
Send,

View File

@ -614,6 +614,7 @@ impl<T, const N: usize> Cell<[T; N]> {
/// A mutable memory location with dynamically checked borrow rules
///
/// See the [module-level documentation](self) for more.
#[cfg_attr(not(test), rustc_diagnostic_item = "RefCell")]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct RefCell<T: ?Sized> {
borrow: Cell<BorrowFlag>,

View File

@ -76,6 +76,7 @@ use crate::sys_common::rwlock as sys;
///
/// [`Mutex`]: super::Mutex
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "RwLock")]
pub struct RwLock<T: ?Sized> {
inner: sys::MovableRwLock,
poison: poison::Flag,

View File

@ -95,6 +95,7 @@ use crate::fmt;
/// [loader lock]: https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
/// [`JoinHandle::join`]: crate::thread::JoinHandle::join
/// [`with`]: LocalKey::with
#[cfg_attr(not(test), rustc_diagnostic_item = "LocalKey")]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct LocalKey<T: 'static> {
// This outer `LocalKey<T>` type is what's going to be stored in statics,

View File

@ -0,0 +1,40 @@
// compile-flags: --edition=2021
// run-rustfix
pub struct Struct<T> {
pub p: T,
}
impl<T> Struct<T> {
pub fn method(&self) {}
pub fn some_mutable_method(&mut self) {}
}
fn main() {
let other_item = std::cell::RefCell::new(Struct { p: 42_u32 });
other_item.borrow().method();
//~^ ERROR no method named `method` found for struct `RefCell` in the current scope [E0599]
//~| HELP use `.borrow()` to borrow the `Struct<u32>`, panicking if a mutable borrow exists
other_item.borrow_mut().some_mutable_method();
//~^ ERROR no method named `some_mutable_method` found for struct `RefCell` in the current scope [E0599]
//~| HELP .borrow_mut()` to mutably borrow the `Struct<u32>`, panicking if any borrows exist
let another_item = std::sync::Mutex::new(Struct { p: 42_u32 });
another_item.lock().unwrap().method();
//~^ ERROR no method named `method` found for struct `Mutex` in the current scope [E0599]
//~| HELP use `.lock().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
let another_item = std::sync::RwLock::new(Struct { p: 42_u32 });
another_item.read().unwrap().method();
//~^ ERROR no method named `method` found for struct `RwLock` in the current scope [E0599]
//~| HELP use `.read().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
another_item.write().unwrap().some_mutable_method();
//~^ ERROR no method named `some_mutable_method` found for struct `RwLock` in the current scope [E0599]
//~| HELP use `.write().unwrap()` to mutably borrow the `Struct<u32>`, blocking the current thread until it can be acquired
}

View File

@ -0,0 +1,40 @@
// compile-flags: --edition=2021
// run-rustfix
pub struct Struct<T> {
pub p: T,
}
impl<T> Struct<T> {
pub fn method(&self) {}
pub fn some_mutable_method(&mut self) {}
}
fn main() {
let other_item = std::cell::RefCell::new(Struct { p: 42_u32 });
other_item.method();
//~^ ERROR no method named `method` found for struct `RefCell` in the current scope [E0599]
//~| HELP use `.borrow()` to borrow the `Struct<u32>`, panicking if a mutable borrow exists
other_item.some_mutable_method();
//~^ ERROR no method named `some_mutable_method` found for struct `RefCell` in the current scope [E0599]
//~| HELP .borrow_mut()` to mutably borrow the `Struct<u32>`, panicking if any borrows exist
let another_item = std::sync::Mutex::new(Struct { p: 42_u32 });
another_item.method();
//~^ ERROR no method named `method` found for struct `Mutex` in the current scope [E0599]
//~| HELP use `.lock().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
let another_item = std::sync::RwLock::new(Struct { p: 42_u32 });
another_item.method();
//~^ ERROR no method named `method` found for struct `RwLock` in the current scope [E0599]
//~| HELP use `.read().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
another_item.some_mutable_method();
//~^ ERROR no method named `some_mutable_method` found for struct `RwLock` in the current scope [E0599]
//~| HELP use `.write().unwrap()` to mutably borrow the `Struct<u32>`, blocking the current thread until it can be acquired
}

View File

@ -0,0 +1,83 @@
error[E0599]: no method named `method` found for struct `RefCell` in the current scope
--> $DIR/inner_type.rs:17:16
|
LL | other_item.method();
| ^^^^^^ method not found in `RefCell<Struct<u32>>`
|
note: the method `method` exists on the type `Struct<u32>`
--> $DIR/inner_type.rs:9:5
|
LL | pub fn method(&self) {}
| ^^^^^^^^^^^^^^^^^^^^
help: use `.borrow()` to borrow the `Struct<u32>`, panicking if a mutable borrow exists
|
LL | other_item.borrow().method();
| +++++++++
error[E0599]: no method named `some_mutable_method` found for struct `RefCell` in the current scope
--> $DIR/inner_type.rs:21:16
|
LL | other_item.some_mutable_method();
| ^^^^^^^^^^^^^^^^^^^ method not found in `RefCell<Struct<u32>>`
|
note: the method `some_mutable_method` exists on the type `Struct<u32>`
--> $DIR/inner_type.rs:11:5
|
LL | pub fn some_mutable_method(&mut self) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: use `.borrow_mut()` to mutably borrow the `Struct<u32>`, panicking if any borrows exist
|
LL | other_item.borrow_mut().some_mutable_method();
| +++++++++++++
error[E0599]: no method named `method` found for struct `Mutex` in the current scope
--> $DIR/inner_type.rs:27:18
|
LL | another_item.method();
| ^^^^^^ method not found in `Mutex<Struct<u32>>`
|
note: the method `method` exists on the type `Struct<u32>`
--> $DIR/inner_type.rs:9:5
|
LL | pub fn method(&self) {}
| ^^^^^^^^^^^^^^^^^^^^
help: use `.lock().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
|
LL | another_item.lock().unwrap().method();
| ++++++++++++++++
error[E0599]: no method named `method` found for struct `RwLock` in the current scope
--> $DIR/inner_type.rs:33:18
|
LL | another_item.method();
| ^^^^^^ method not found in `RwLock<Struct<u32>>`
|
note: the method `method` exists on the type `Struct<u32>`
--> $DIR/inner_type.rs:9:5
|
LL | pub fn method(&self) {}
| ^^^^^^^^^^^^^^^^^^^^
help: use `.read().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
|
LL | another_item.read().unwrap().method();
| ++++++++++++++++
error[E0599]: no method named `some_mutable_method` found for struct `RwLock` in the current scope
--> $DIR/inner_type.rs:37:18
|
LL | another_item.some_mutable_method();
| ^^^^^^^^^^^^^^^^^^^ method not found in `RwLock<Struct<u32>>`
|
note: the method `some_mutable_method` exists on the type `Struct<u32>`
--> $DIR/inner_type.rs:11:5
|
LL | pub fn some_mutable_method(&mut self) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: use `.write().unwrap()` to mutably borrow the `Struct<u32>`, blocking the current thread until it can be acquired
|
LL | another_item.write().unwrap().some_mutable_method();
| +++++++++++++++++
error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0599`.

View File

@ -0,0 +1,26 @@
pub struct Struct<T> {
pub p: T,
}
impl<T> Struct<T> {
pub fn method(&self) {}
pub fn some_mutable_method(&mut self) {}
}
thread_local! {
static STRUCT: Struct<u32> = Struct {
p: 42_u32
};
}
fn main() {
STRUCT.method();
//~^ ERROR no method named `method` found for struct `LocalKey` in the current scope [E0599]
//~| HELP use `with` or `try_with` to access thread local storage
let item = std::mem::MaybeUninit::new(Struct { p: 42_u32 });
item.method();
//~^ ERROR no method named `method` found for union `MaybeUninit` in the current scope [E0599]
//~| HELP if this `MaybeUninit::<Struct<u32>>` has been initialized, use one of the `assume_init` methods to access the inner value
}

View File

@ -0,0 +1,29 @@
error[E0599]: no method named `method` found for struct `LocalKey` in the current scope
--> $DIR/inner_type2.rs:18:12
|
LL | STRUCT.method();
| ^^^^^^ method not found in `LocalKey<Struct<u32>>`
|
= help: use `with` or `try_with` to access thread local storage
note: the method `method` exists on the type `Struct<u32>`
--> $DIR/inner_type2.rs:6:5
|
LL | pub fn method(&self) {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0599]: no method named `method` found for union `MaybeUninit` in the current scope
--> $DIR/inner_type2.rs:23:10
|
LL | item.method();
| ^^^^^^ method not found in `MaybeUninit<Struct<u32>>`
|
= help: if this `MaybeUninit::<Struct<u32>>` has been initialized, use one of the `assume_init` methods to access the inner value
note: the method `method` exists on the type `Struct<u32>`
--> $DIR/inner_type2.rs:6:5
|
LL | pub fn method(&self) {}
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0599`.