Skip to content

Commit 4089e39

Browse files
authored
Rollup merge of rust-lang#96271 - compiler-errors:suggest-question-mark, r=estebank
suggest `?` when method is missing on `Result<T, _>` but found on `T` The wording needs help, I think. Fixes rust-lang#95729
2 parents 8fb4f05 + 2a61f0c commit 4089e39

File tree

4 files changed

+368
-52
lines changed

4 files changed

+368
-52
lines changed

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

+151-52
Original file line numberDiff line numberDiff line change
@@ -978,45 +978,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
978978
label_span_not_found(&mut err);
979979
}
980980

981-
if let SelfSource::MethodCall(expr) = source
982-
&& let Some((fields, substs)) = self.get_field_candidates(span, actual)
983-
{
984-
let call_expr =
985-
self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id));
986-
for candidate_field in fields.iter() {
987-
if let Some(field_path) = self.check_for_nested_field_satisfying(
988-
span,
989-
&|_, field_ty| {
990-
self.lookup_probe(
991-
span,
992-
item_name,
993-
field_ty,
994-
call_expr,
995-
ProbeScope::AllTraits,
996-
)
997-
.is_ok()
998-
},
999-
candidate_field,
1000-
substs,
1001-
vec![],
1002-
self.tcx.parent_module(expr.hir_id).to_def_id(),
1003-
) {
1004-
let field_path_str = field_path
1005-
.iter()
1006-
.map(|id| id.name.to_ident_string())
1007-
.collect::<Vec<String>>()
1008-
.join(".");
1009-
debug!("field_path_str: {:?}", field_path_str);
981+
self.check_for_field_method(&mut err, source, span, actual, item_name);
1010982

1011-
err.span_suggestion_verbose(
1012-
item_name.span.shrink_to_lo(),
1013-
"one of the expressions' fields has a method of the same name",
1014-
format!("{field_path_str}."),
1015-
Applicability::MaybeIncorrect,
1016-
);
1017-
}
1018-
}
1019-
}
983+
self.check_for_unwrap_self(&mut err, source, span, actual, item_name);
1020984

1021985
bound_spans.sort();
1022986
bound_spans.dedup();
@@ -1343,6 +1307,145 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13431307
false
13441308
}
13451309

1310+
fn check_for_field_method(
1311+
&self,
1312+
err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
1313+
source: SelfSource<'tcx>,
1314+
span: Span,
1315+
actual: Ty<'tcx>,
1316+
item_name: Ident,
1317+
) {
1318+
if let SelfSource::MethodCall(expr) = source
1319+
&& let Some((fields, substs)) = self.get_field_candidates(span, actual)
1320+
{
1321+
let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id));
1322+
for candidate_field in fields.iter() {
1323+
if let Some(field_path) = self.check_for_nested_field_satisfying(
1324+
span,
1325+
&|_, field_ty| {
1326+
self.lookup_probe(
1327+
span,
1328+
item_name,
1329+
field_ty,
1330+
call_expr,
1331+
ProbeScope::AllTraits,
1332+
)
1333+
.is_ok()
1334+
},
1335+
candidate_field,
1336+
substs,
1337+
vec![],
1338+
self.tcx.parent_module(expr.hir_id).to_def_id(),
1339+
) {
1340+
let field_path_str = field_path
1341+
.iter()
1342+
.map(|id| id.name.to_ident_string())
1343+
.collect::<Vec<String>>()
1344+
.join(".");
1345+
debug!("field_path_str: {:?}", field_path_str);
1346+
1347+
err.span_suggestion_verbose(
1348+
item_name.span.shrink_to_lo(),
1349+
"one of the expressions' fields has a method of the same name",
1350+
format!("{field_path_str}."),
1351+
Applicability::MaybeIncorrect,
1352+
);
1353+
}
1354+
}
1355+
}
1356+
}
1357+
1358+
fn check_for_unwrap_self(
1359+
&self,
1360+
err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
1361+
source: SelfSource<'tcx>,
1362+
span: Span,
1363+
actual: Ty<'tcx>,
1364+
item_name: Ident,
1365+
) {
1366+
let tcx = self.tcx;
1367+
let SelfSource::MethodCall(expr) = source else { return; };
1368+
let call_expr = tcx.hir().expect_expr(tcx.hir().get_parent_node(expr.hir_id));
1369+
1370+
let ty::Adt(kind, substs) = actual.kind() else { return; };
1371+
if !kind.is_enum() {
1372+
return;
1373+
}
1374+
1375+
let matching_variants: Vec<_> = kind
1376+
.variants()
1377+
.iter()
1378+
.flat_map(|variant| {
1379+
let [field] = &variant.fields[..] else { return None; };
1380+
let field_ty = field.ty(tcx, substs);
1381+
1382+
// Skip `_`, since that'll just lead to ambiguity.
1383+
if self.resolve_vars_if_possible(field_ty).is_ty_var() {
1384+
return None;
1385+
}
1386+
1387+
self.lookup_probe(span, item_name, field_ty, call_expr, ProbeScope::AllTraits)
1388+
.ok()
1389+
.map(|pick| (variant, field, pick))
1390+
})
1391+
.collect();
1392+
1393+
let ret_ty_matches = |diagnostic_item| {
1394+
if let Some(ret_ty) = self
1395+
.ret_coercion
1396+
.as_ref()
1397+
.map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty()))
1398+
&& let ty::Adt(kind, _) = ret_ty.kind()
1399+
&& tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did())
1400+
{
1401+
true
1402+
} else {
1403+
false
1404+
}
1405+
};
1406+
1407+
match &matching_variants[..] {
1408+
[(_, field, pick)] => {
1409+
let self_ty = field.ty(tcx, substs);
1410+
err.span_note(
1411+
tcx.def_span(pick.item.def_id),
1412+
&format!("the method `{item_name}` exists on the type `{self_ty}`"),
1413+
);
1414+
let (article, kind, variant, question) =
1415+
if Some(kind.did()) == tcx.get_diagnostic_item(sym::Result) {
1416+
("a", "Result", "Err", ret_ty_matches(sym::Result))
1417+
} else if Some(kind.did()) == tcx.get_diagnostic_item(sym::Option) {
1418+
("an", "Option", "None", ret_ty_matches(sym::Option))
1419+
} else {
1420+
return;
1421+
};
1422+
if question {
1423+
err.span_suggestion_verbose(
1424+
expr.span.shrink_to_hi(),
1425+
format!(
1426+
"use the `?` operator to extract the `{self_ty}` value, propagating \
1427+
{article} `{kind}::{variant}` value to the caller"
1428+
),
1429+
"?".to_owned(),
1430+
Applicability::MachineApplicable,
1431+
);
1432+
} else {
1433+
err.span_suggestion_verbose(
1434+
expr.span.shrink_to_hi(),
1435+
format!(
1436+
"consider using `{kind}::expect` to unwrap the `{self_ty}` value, \
1437+
panicking if the value is {article} `{kind}::{variant}`"
1438+
),
1439+
".expect(\"REASON\")".to_owned(),
1440+
Applicability::HasPlaceholders,
1441+
);
1442+
}
1443+
}
1444+
// FIXME(compiler-errors): Support suggestions for other matching enum variants
1445+
_ => {}
1446+
}
1447+
}
1448+
13461449
pub(crate) fn note_unmet_impls_on_type(
13471450
&self,
13481451
err: &mut Diagnostic,
@@ -1662,13 +1765,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16621765
(self.tcx.mk_mut_ref(self.tcx.lifetimes.re_erased, rcvr_ty), "&mut "),
16631766
(self.tcx.mk_imm_ref(self.tcx.lifetimes.re_erased, rcvr_ty), "&"),
16641767
] {
1665-
match self.lookup_probe(
1666-
span,
1667-
item_name,
1668-
*rcvr_ty,
1669-
rcvr,
1670-
crate::check::method::probe::ProbeScope::AllTraits,
1671-
) {
1768+
match self.lookup_probe(span, item_name, *rcvr_ty, rcvr, ProbeScope::AllTraits) {
16721769
Ok(pick) => {
16731770
// If the method is defined for the receiver we have, it likely wasn't `use`d.
16741771
// We point at the method, but we just skip the rest of the check for arbitrary
@@ -1700,13 +1797,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17001797
(self.tcx.mk_diagnostic_item(*rcvr_ty, sym::Arc), "Arc::new"),
17011798
(self.tcx.mk_diagnostic_item(*rcvr_ty, sym::Rc), "Rc::new"),
17021799
] {
1703-
if let Some(new_rcvr_t) = *rcvr_ty && let Ok(pick) = self.lookup_probe(
1704-
span,
1705-
item_name,
1706-
new_rcvr_t,
1707-
rcvr,
1708-
crate::check::method::probe::ProbeScope::AllTraits,
1709-
) {
1800+
if let Some(new_rcvr_t) = *rcvr_ty
1801+
&& let Ok(pick) = self.lookup_probe(
1802+
span,
1803+
item_name,
1804+
new_rcvr_t,
1805+
rcvr,
1806+
ProbeScope::AllTraits,
1807+
)
1808+
{
17101809
debug!("try_alt_rcvr: pick candidate {:?}", pick);
17111810
let did = Some(pick.item.container.id());
17121811
// We don't want to suggest a container type when the missing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// compile-flags: --edition=2021
2+
// run-rustfix
3+
4+
#![allow(unused)]
5+
6+
struct Foo;
7+
8+
impl Foo {
9+
fn get(&self) -> u8 {
10+
42
11+
}
12+
}
13+
14+
fn test_result_in_result() -> Result<(), ()> {
15+
let res: Result<_, ()> = Ok(Foo);
16+
res?.get();
17+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
18+
//~| HELP use the `?` operator
19+
Ok(())
20+
}
21+
22+
async fn async_test_result_in_result() -> Result<(), ()> {
23+
let res: Result<_, ()> = Ok(Foo);
24+
res?.get();
25+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
26+
//~| HELP use the `?` operator
27+
Ok(())
28+
}
29+
30+
fn test_result_in_unit_return() {
31+
let res: Result<_, ()> = Ok(Foo);
32+
res.expect("REASON").get();
33+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
34+
//~| HELP consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is a `Result::Err`
35+
}
36+
37+
async fn async_test_result_in_unit_return() {
38+
let res: Result<_, ()> = Ok(Foo);
39+
res.expect("REASON").get();
40+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
41+
//~| HELP consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is a `Result::Err`
42+
}
43+
44+
fn test_option_in_option() -> Option<()> {
45+
let res: Option<_> = Some(Foo);
46+
res?.get();
47+
//~^ ERROR no method named `get` found for enum `Option` in the current scope
48+
//~| HELP use the `?` operator
49+
Some(())
50+
}
51+
52+
fn test_option_in_unit_return() {
53+
let res: Option<_> = Some(Foo);
54+
res.expect("REASON").get();
55+
//~^ ERROR no method named `get` found for enum `Option` in the current scope
56+
//~| HELP consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is an `Option::None`
57+
}
58+
59+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// compile-flags: --edition=2021
2+
// run-rustfix
3+
4+
#![allow(unused)]
5+
6+
struct Foo;
7+
8+
impl Foo {
9+
fn get(&self) -> u8 {
10+
42
11+
}
12+
}
13+
14+
fn test_result_in_result() -> Result<(), ()> {
15+
let res: Result<_, ()> = Ok(Foo);
16+
res.get();
17+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
18+
//~| HELP use the `?` operator
19+
Ok(())
20+
}
21+
22+
async fn async_test_result_in_result() -> Result<(), ()> {
23+
let res: Result<_, ()> = Ok(Foo);
24+
res.get();
25+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
26+
//~| HELP use the `?` operator
27+
Ok(())
28+
}
29+
30+
fn test_result_in_unit_return() {
31+
let res: Result<_, ()> = Ok(Foo);
32+
res.get();
33+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
34+
//~| HELP consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is a `Result::Err`
35+
}
36+
37+
async fn async_test_result_in_unit_return() {
38+
let res: Result<_, ()> = Ok(Foo);
39+
res.get();
40+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
41+
//~| HELP consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is a `Result::Err`
42+
}
43+
44+
fn test_option_in_option() -> Option<()> {
45+
let res: Option<_> = Some(Foo);
46+
res.get();
47+
//~^ ERROR no method named `get` found for enum `Option` in the current scope
48+
//~| HELP use the `?` operator
49+
Some(())
50+
}
51+
52+
fn test_option_in_unit_return() {
53+
let res: Option<_> = Some(Foo);
54+
res.get();
55+
//~^ ERROR no method named `get` found for enum `Option` in the current scope
56+
//~| HELP consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is an `Option::None`
57+
}
58+
59+
fn main() {}

0 commit comments

Comments
 (0)