Skip to content

Commit 9436ffc

Browse files
authored
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`.
2 parents 07bb2e6 + 4ff83ce commit 9436ffc

File tree

10 files changed

+373
-61
lines changed

10 files changed

+373
-61
lines changed

compiler/rustc_hir_analysis/src/check/method/suggest.rs

+149-61
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! found or is otherwise invalid.
33
44
use crate::check::FnCtxt;
5+
use rustc_ast::ast::Mutability;
56
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
67
use rustc_errors::{
78
pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
@@ -30,7 +31,7 @@ use rustc_trait_selection::traits::{
3031
use std::cmp::Ordering;
3132
use std::iter;
3233

33-
use super::probe::{IsSuggestion, Mode, ProbeScope};
34+
use super::probe::{AutorefOrPtrAdjustment, IsSuggestion, Mode, ProbeScope};
3435
use super::{CandidateSource, MethodError, NoMatchData};
3536

3637
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
@@ -983,7 +984,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
983984
self.check_for_field_method(&mut err, source, span, actual, item_name);
984985
}
985986

986-
self.check_for_unwrap_self(&mut err, source, span, actual, item_name);
987+
self.check_for_inner_self(&mut err, source, span, actual, item_name);
987988

988989
bound_spans.sort();
989990
bound_spans.dedup();
@@ -1395,7 +1396,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13951396
}
13961397
}
13971398

1398-
fn check_for_unwrap_self(
1399+
fn check_for_inner_self(
13991400
&self,
14001401
err: &mut Diagnostic,
14011402
source: SelfSource<'tcx>,
@@ -1408,81 +1409,168 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14081409
let call_expr = tcx.hir().expect_expr(tcx.hir().get_parent_node(expr.hir_id));
14091410

14101411
let ty::Adt(kind, substs) = actual.kind() else { return; };
1411-
if !kind.is_enum() {
1412-
return;
1413-
}
1412+
match kind.adt_kind() {
1413+
ty::AdtKind::Enum => {
1414+
let matching_variants: Vec<_> = kind
1415+
.variants()
1416+
.iter()
1417+
.flat_map(|variant| {
1418+
let [field] = &variant.fields[..] else { return None; };
1419+
let field_ty = field.ty(tcx, substs);
1420+
1421+
// Skip `_`, since that'll just lead to ambiguity.
1422+
if self.resolve_vars_if_possible(field_ty).is_ty_var() {
1423+
return None;
1424+
}
14141425

1415-
let matching_variants: Vec<_> = kind
1416-
.variants()
1417-
.iter()
1418-
.flat_map(|variant| {
1419-
let [field] = &variant.fields[..] else { return None; };
1420-
let field_ty = field.ty(tcx, substs);
1426+
self.lookup_probe(
1427+
span,
1428+
item_name,
1429+
field_ty,
1430+
call_expr,
1431+
ProbeScope::TraitsInScope,
1432+
)
1433+
.ok()
1434+
.map(|pick| (variant, field, pick))
1435+
})
1436+
.collect();
1437+
1438+
let ret_ty_matches = |diagnostic_item| {
1439+
if let Some(ret_ty) = self
1440+
.ret_coercion
1441+
.as_ref()
1442+
.map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty()))
1443+
&& let ty::Adt(kind, _) = ret_ty.kind()
1444+
&& tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did())
1445+
{
1446+
true
1447+
} else {
1448+
false
1449+
}
1450+
};
14211451

1422-
// Skip `_`, since that'll just lead to ambiguity.
1423-
if self.resolve_vars_if_possible(field_ty).is_ty_var() {
1424-
return None;
1452+
match &matching_variants[..] {
1453+
[(_, field, pick)] => {
1454+
let self_ty = field.ty(tcx, substs);
1455+
err.span_note(
1456+
tcx.def_span(pick.item.def_id),
1457+
&format!("the method `{item_name}` exists on the type `{self_ty}`"),
1458+
);
1459+
let (article, kind, variant, question) =
1460+
if tcx.is_diagnostic_item(sym::Result, kind.did()) {
1461+
("a", "Result", "Err", ret_ty_matches(sym::Result))
1462+
} else if tcx.is_diagnostic_item(sym::Option, kind.did()) {
1463+
("an", "Option", "None", ret_ty_matches(sym::Option))
1464+
} else {
1465+
return;
1466+
};
1467+
if question {
1468+
err.span_suggestion_verbose(
1469+
expr.span.shrink_to_hi(),
1470+
format!(
1471+
"use the `?` operator to extract the `{self_ty}` value, propagating \
1472+
{article} `{kind}::{variant}` value to the caller"
1473+
),
1474+
"?",
1475+
Applicability::MachineApplicable,
1476+
);
1477+
} else {
1478+
err.span_suggestion_verbose(
1479+
expr.span.shrink_to_hi(),
1480+
format!(
1481+
"consider using `{kind}::expect` to unwrap the `{self_ty}` value, \
1482+
panicking if the value is {article} `{kind}::{variant}`"
1483+
),
1484+
".expect(\"REASON\")",
1485+
Applicability::HasPlaceholders,
1486+
);
1487+
}
1488+
}
1489+
// FIXME(compiler-errors): Support suggestions for other matching enum variants
1490+
_ => {}
14251491
}
1426-
1427-
self.lookup_probe(span, item_name, field_ty, call_expr, ProbeScope::AllTraits)
1428-
.ok()
1429-
.map(|pick| (variant, field, pick))
1430-
})
1431-
.collect();
1432-
1433-
let ret_ty_matches = |diagnostic_item| {
1434-
if let Some(ret_ty) = self
1435-
.ret_coercion
1436-
.as_ref()
1437-
.map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty()))
1438-
&& let ty::Adt(kind, _) = ret_ty.kind()
1439-
&& tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did())
1440-
{
1441-
true
1442-
} else {
1443-
false
14441492
}
1445-
};
1493+
// Target wrapper types - types that wrap or pretend to wrap another type,
1494+
// perhaps this inner type is meant to be called?
1495+
ty::AdtKind::Struct | ty::AdtKind::Union => {
1496+
let [first] = ***substs else { return; };
1497+
let ty::GenericArgKind::Type(ty) = first.unpack() else { return; };
1498+
let Ok(pick) = self.lookup_probe(
1499+
span,
1500+
item_name,
1501+
ty,
1502+
call_expr,
1503+
ProbeScope::TraitsInScope,
1504+
) else { return; };
14461505

1447-
match &matching_variants[..] {
1448-
[(_, field, pick)] => {
1449-
let self_ty = field.ty(tcx, substs);
1450-
err.span_note(
1451-
tcx.def_span(pick.item.def_id),
1452-
&format!("the method `{item_name}` exists on the type `{self_ty}`"),
1453-
);
1454-
let (article, kind, variant, question) =
1455-
if Some(kind.did()) == tcx.get_diagnostic_item(sym::Result) {
1456-
("a", "Result", "Err", ret_ty_matches(sym::Result))
1457-
} else if Some(kind.did()) == tcx.get_diagnostic_item(sym::Option) {
1458-
("an", "Option", "None", ret_ty_matches(sym::Option))
1459-
} else {
1460-
return;
1506+
let name = self.ty_to_value_string(actual);
1507+
let inner_id = kind.did();
1508+
let mutable = if let Some(AutorefOrPtrAdjustment::Autoref { mutbl, .. }) =
1509+
pick.autoref_or_ptr_adjustment
1510+
{
1511+
Some(mutbl)
1512+
} else {
1513+
None
1514+
};
1515+
1516+
if tcx.is_diagnostic_item(sym::LocalKey, inner_id) {
1517+
err.help("use `with` or `try_with` to access thread local storage");
1518+
} else if Some(kind.did()) == tcx.lang_items().maybe_uninit() {
1519+
err.help(format!(
1520+
"if this `{name}` has been initialized, \
1521+
use one of the `assume_init` methods to access the inner value"
1522+
));
1523+
} else if tcx.is_diagnostic_item(sym::RefCell, inner_id) {
1524+
let (suggestion, borrow_kind, panic_if) = match mutable {
1525+
Some(Mutability::Not) => (".borrow()", "borrow", "a mutable borrow exists"),
1526+
Some(Mutability::Mut) => {
1527+
(".borrow_mut()", "mutably borrow", "any borrows exist")
1528+
}
1529+
None => return,
14611530
};
1462-
if question {
14631531
err.span_suggestion_verbose(
14641532
expr.span.shrink_to_hi(),
14651533
format!(
1466-
"use the `?` operator to extract the `{self_ty}` value, propagating \
1467-
{article} `{kind}::{variant}` value to the caller"
1534+
"use `{suggestion}` to {borrow_kind} the `{ty}`, \
1535+
panicking if {panic_if}"
14681536
),
1469-
"?",
1470-
Applicability::MachineApplicable,
1537+
suggestion,
1538+
Applicability::MaybeIncorrect,
14711539
);
1472-
} else {
1540+
} else if tcx.is_diagnostic_item(sym::Mutex, inner_id) {
14731541
err.span_suggestion_verbose(
14741542
expr.span.shrink_to_hi(),
14751543
format!(
1476-
"consider using `{kind}::expect` to unwrap the `{self_ty}` value, \
1477-
panicking if the value is {article} `{kind}::{variant}`"
1544+
"use `.lock().unwrap()` to borrow the `{ty}`, \
1545+
blocking the current thread until it can be acquired"
14781546
),
1479-
".expect(\"REASON\")",
1480-
Applicability::HasPlaceholders,
1547+
".lock().unwrap()",
1548+
Applicability::MaybeIncorrect,
14811549
);
1482-
}
1550+
} else if tcx.is_diagnostic_item(sym::RwLock, inner_id) {
1551+
let (suggestion, borrow_kind) = match mutable {
1552+
Some(Mutability::Not) => (".read().unwrap()", "borrow"),
1553+
Some(Mutability::Mut) => (".write().unwrap()", "mutably borrow"),
1554+
None => return,
1555+
};
1556+
err.span_suggestion_verbose(
1557+
expr.span.shrink_to_hi(),
1558+
format!(
1559+
"use `{suggestion}` to {borrow_kind} the `{ty}`, \
1560+
blocking the current thread until it can be acquired"
1561+
),
1562+
suggestion,
1563+
Applicability::MaybeIncorrect,
1564+
);
1565+
} else {
1566+
return;
1567+
};
1568+
1569+
err.span_note(
1570+
tcx.def_span(pick.item.def_id),
1571+
&format!("the method `{item_name}` exists on the type `{ty}`"),
1572+
);
14831573
}
1484-
// FIXME(compiler-errors): Support suggestions for other matching enum variants
1485-
_ => {}
14861574
}
14871575
}
14881576

compiler/rustc_span/src/symbol.rs

+3
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ symbols! {
224224
Left,
225225
LinkedList,
226226
LintPass,
227+
LocalKey,
227228
Mutex,
228229
MutexGuard,
229230
N,
@@ -266,6 +267,7 @@ symbols! {
266267
Rc,
267268
Ready,
268269
Receiver,
270+
RefCell,
269271
Relaxed,
270272
Release,
271273
Result,
@@ -274,6 +276,7 @@ symbols! {
274276
Rust,
275277
RustcDecodable,
276278
RustcEncodable,
279+
RwLock,
277280
RwLockReadGuard,
278281
RwLockWriteGuard,
279282
Send,

library/core/src/cell.rs

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

library/std/src/sync/rwlock.rs

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ use crate::sys_common::rwlock as sys;
7676
///
7777
/// [`Mutex`]: super::Mutex
7878
#[stable(feature = "rust1", since = "1.0.0")]
79+
#[cfg_attr(not(test), rustc_diagnostic_item = "RwLock")]
7980
pub struct RwLock<T: ?Sized> {
8081
inner: sys::MovableRwLock,
8182
poison: poison::Flag,

library/std/src/thread/local.rs

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ use crate::fmt;
9595
/// [loader lock]: https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
9696
/// [`JoinHandle::join`]: crate::thread::JoinHandle::join
9797
/// [`with`]: LocalKey::with
98+
#[cfg_attr(not(test), rustc_diagnostic_item = "LocalKey")]
9899
#[stable(feature = "rust1", since = "1.0.0")]
99100
pub struct LocalKey<T: 'static> {
100101
// This outer `LocalKey<T>` type is what's going to be stored in statics,
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// compile-flags: --edition=2021
2+
// run-rustfix
3+
4+
pub struct Struct<T> {
5+
pub p: T,
6+
}
7+
8+
impl<T> Struct<T> {
9+
pub fn method(&self) {}
10+
11+
pub fn some_mutable_method(&mut self) {}
12+
}
13+
14+
fn main() {
15+
let other_item = std::cell::RefCell::new(Struct { p: 42_u32 });
16+
17+
other_item.borrow().method();
18+
//~^ ERROR no method named `method` found for struct `RefCell` in the current scope [E0599]
19+
//~| HELP use `.borrow()` to borrow the `Struct<u32>`, panicking if a mutable borrow exists
20+
21+
other_item.borrow_mut().some_mutable_method();
22+
//~^ ERROR no method named `some_mutable_method` found for struct `RefCell` in the current scope [E0599]
23+
//~| HELP .borrow_mut()` to mutably borrow the `Struct<u32>`, panicking if any borrows exist
24+
25+
let another_item = std::sync::Mutex::new(Struct { p: 42_u32 });
26+
27+
another_item.lock().unwrap().method();
28+
//~^ ERROR no method named `method` found for struct `Mutex` in the current scope [E0599]
29+
//~| HELP use `.lock().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
30+
31+
let another_item = std::sync::RwLock::new(Struct { p: 42_u32 });
32+
33+
another_item.read().unwrap().method();
34+
//~^ ERROR no method named `method` found for struct `RwLock` in the current scope [E0599]
35+
//~| HELP use `.read().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
36+
37+
another_item.write().unwrap().some_mutable_method();
38+
//~^ ERROR no method named `some_mutable_method` found for struct `RwLock` in the current scope [E0599]
39+
//~| HELP use `.write().unwrap()` to mutably borrow the `Struct<u32>`, blocking the current thread until it can be acquired
40+
}

src/test/ui/suggestions/inner_type.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// compile-flags: --edition=2021
2+
// run-rustfix
3+
4+
pub struct Struct<T> {
5+
pub p: T,
6+
}
7+
8+
impl<T> Struct<T> {
9+
pub fn method(&self) {}
10+
11+
pub fn some_mutable_method(&mut self) {}
12+
}
13+
14+
fn main() {
15+
let other_item = std::cell::RefCell::new(Struct { p: 42_u32 });
16+
17+
other_item.method();
18+
//~^ ERROR no method named `method` found for struct `RefCell` in the current scope [E0599]
19+
//~| HELP use `.borrow()` to borrow the `Struct<u32>`, panicking if a mutable borrow exists
20+
21+
other_item.some_mutable_method();
22+
//~^ ERROR no method named `some_mutable_method` found for struct `RefCell` in the current scope [E0599]
23+
//~| HELP .borrow_mut()` to mutably borrow the `Struct<u32>`, panicking if any borrows exist
24+
25+
let another_item = std::sync::Mutex::new(Struct { p: 42_u32 });
26+
27+
another_item.method();
28+
//~^ ERROR no method named `method` found for struct `Mutex` in the current scope [E0599]
29+
//~| HELP use `.lock().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
30+
31+
let another_item = std::sync::RwLock::new(Struct { p: 42_u32 });
32+
33+
another_item.method();
34+
//~^ ERROR no method named `method` found for struct `RwLock` in the current scope [E0599]
35+
//~| HELP use `.read().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
36+
37+
another_item.some_mutable_method();
38+
//~^ ERROR no method named `some_mutable_method` found for struct `RwLock` in the current scope [E0599]
39+
//~| HELP use `.write().unwrap()` to mutably borrow the `Struct<u32>`, blocking the current thread until it can be acquired
40+
}

0 commit comments

Comments
 (0)