Skip to content

Commit aeb765b

Browse files
better enum suggestions
1 parent 4a8d2e3 commit aeb765b

File tree

4 files changed

+314
-52
lines changed

4 files changed

+314
-52
lines changed

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

+163-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,157 @@ 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+
.filter_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 matches!(self.resolve_vars_if_possible(field_ty).kind(), ty::Infer(_)) {
1384+
return None;
1385+
}
1386+
1387+
if let Ok(pick) =
1388+
self.lookup_probe(span, item_name, field_ty, call_expr, ProbeScope::AllTraits)
1389+
{
1390+
Some((variant, field, pick))
1391+
} else {
1392+
None
1393+
}
1394+
})
1395+
.collect();
1396+
1397+
let ret_ty_matches = |diagnostic_item| {
1398+
if let Some(ret_ty) = self
1399+
.ret_coercion
1400+
.as_ref()
1401+
.map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty()))
1402+
&& let ty::Adt(kind, _) = ret_ty.kind()
1403+
&& tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did())
1404+
{
1405+
true
1406+
} else {
1407+
false
1408+
}
1409+
};
1410+
1411+
match &matching_variants[..] {
1412+
[(_, field, pick)] if Some(kind.did()) == tcx.get_diagnostic_item(sym::Result) => {
1413+
let self_ty = field.ty(tcx, substs);
1414+
err.span_note(
1415+
tcx.def_span(pick.item.def_id),
1416+
&format!("the method `{item_name}` exists on the type `{self_ty}`"),
1417+
);
1418+
if ret_ty_matches(sym::Result) {
1419+
err.span_suggestion_verbose(
1420+
expr.span.shrink_to_hi(),
1421+
format!("use the `?` operator to extract the `{self_ty}` value, propagating a `Result::Err` value to the caller"),
1422+
"?".to_owned(),
1423+
Applicability::MachineApplicable,
1424+
);
1425+
} else {
1426+
err.span_suggestion_verbose(
1427+
expr.span.shrink_to_hi(),
1428+
format!("consider using `Result::expect` to unwrap the `{self_ty}` value, panicking if the value is an `Err`"),
1429+
".expect(\"REASON\")".to_owned(),
1430+
Applicability::HasPlaceholders,
1431+
);
1432+
}
1433+
}
1434+
[(_, field, pick)] if Some(kind.did()) == tcx.get_diagnostic_item(sym::Option) => {
1435+
let self_ty = field.ty(tcx, substs);
1436+
err.span_note(
1437+
tcx.def_span(pick.item.def_id),
1438+
&format!("the method `{item_name}` exists on the type `{self_ty}`"),
1439+
);
1440+
if ret_ty_matches(sym::Option) {
1441+
err.span_suggestion_verbose(
1442+
expr.span.shrink_to_hi(),
1443+
format!("use the `?` operator to extract the `{self_ty}` value, propagating a `None` to the caller"),
1444+
"?".to_owned(),
1445+
Applicability::MachineApplicable,
1446+
);
1447+
} else {
1448+
err.span_suggestion_verbose(
1449+
expr.span.shrink_to_hi(),
1450+
format!("consider using `Option::expect` to unwrap the `{self_ty}` value, panicking if the value is `None`"),
1451+
".expect(\"REASON\")".to_owned(),
1452+
Applicability::HasPlaceholders,
1453+
);
1454+
}
1455+
}
1456+
// FIXME(compiler-errors): Support suggestions for other matching enum variants
1457+
_ => {}
1458+
}
1459+
}
1460+
13461461
pub(crate) fn note_unmet_impls_on_type(
13471462
&self,
13481463
err: &mut Diagnostic,
@@ -1662,13 +1777,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16621777
(self.tcx.mk_mut_ref(self.tcx.lifetimes.re_erased, rcvr_ty), "&mut "),
16631778
(self.tcx.mk_imm_ref(self.tcx.lifetimes.re_erased, rcvr_ty), "&"),
16641779
] {
1665-
match self.lookup_probe(
1666-
span,
1667-
item_name,
1668-
*rcvr_ty,
1669-
rcvr,
1670-
crate::check::method::probe::ProbeScope::AllTraits,
1671-
) {
1780+
match self.lookup_probe(span, item_name, *rcvr_ty, rcvr, ProbeScope::AllTraits) {
16721781
Ok(pick) => {
16731782
// If the method is defined for the receiver we have, it likely wasn't `use`d.
16741783
// We point at the method, but we just skip the rest of the check for arbitrary
@@ -1700,13 +1809,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17001809
(self.tcx.mk_diagnostic_item(*rcvr_ty, sym::Arc), "Arc::new"),
17011810
(self.tcx.mk_diagnostic_item(*rcvr_ty, sym::Rc), "Rc::new"),
17021811
] {
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-
) {
1812+
if let Some(new_rcvr_t) = *rcvr_ty
1813+
&& let Ok(pick) = self.lookup_probe(
1814+
span,
1815+
item_name,
1816+
new_rcvr_t,
1817+
rcvr,
1818+
ProbeScope::AllTraits,
1819+
)
1820+
{
17101821
debug!("try_alt_rcvr: pick candidate {:?}", pick);
17111822
let did = Some(pick.item.container.id());
17121823
// We don't want to suggest a container type when the missing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
4+
struct Foo;
5+
6+
impl Foo {
7+
fn get(&self) -> u8 {
8+
42
9+
}
10+
}
11+
12+
fn test_result_in_result() -> Result<(), ()> {
13+
let res: Result<_, ()> = Ok(Foo);
14+
res?.get();
15+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
16+
//~| HELP use the `?` operator
17+
Ok(())
18+
}
19+
20+
fn test_result_in_plain() {
21+
let res: Result<_, ()> = Ok(Foo);
22+
res.expect("REASON").get();
23+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
24+
//~| HELP consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is an `Err`
25+
}
26+
27+
fn test_option_in_option() -> Option<()> {
28+
let res: Option<_> = Some(Foo);
29+
res?.get();
30+
//~^ ERROR no method named `get` found for enum `Option` in the current scope
31+
//~| HELP use the `?` operator
32+
Some(())
33+
}
34+
35+
fn test_option_in_plain() {
36+
let res: Option<_> = Some(Foo);
37+
res.expect("REASON").get();
38+
//~^ ERROR no method named `get` found for enum `Option` in the current scope
39+
//~| HELP consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is `None`
40+
}
41+
42+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
4+
struct Foo;
5+
6+
impl Foo {
7+
fn get(&self) -> u8 {
8+
42
9+
}
10+
}
11+
12+
fn test_result_in_result() -> Result<(), ()> {
13+
let res: Result<_, ()> = Ok(Foo);
14+
res.get();
15+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
16+
//~| HELP use the `?` operator
17+
Ok(())
18+
}
19+
20+
fn test_result_in_plain() {
21+
let res: Result<_, ()> = Ok(Foo);
22+
res.get();
23+
//~^ ERROR no method named `get` found for enum `Result` in the current scope
24+
//~| HELP consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is an `Err`
25+
}
26+
27+
fn test_option_in_option() -> Option<()> {
28+
let res: Option<_> = Some(Foo);
29+
res.get();
30+
//~^ ERROR no method named `get` found for enum `Option` in the current scope
31+
//~| HELP use the `?` operator
32+
Some(())
33+
}
34+
35+
fn test_option_in_plain() {
36+
let res: Option<_> = Some(Foo);
37+
res.get();
38+
//~^ ERROR no method named `get` found for enum `Option` in the current scope
39+
//~| HELP consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is `None`
40+
}
41+
42+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
error[E0599]: no method named `get` found for enum `Result` in the current scope
2+
--> $DIR/enum-method-probe.rs:14:9
3+
|
4+
LL | res.get();
5+
| ^^^ method not found in `Result<Foo, ()>`
6+
|
7+
note: the method `get` exists on the type `Foo`
8+
--> $DIR/enum-method-probe.rs:7:5
9+
|
10+
LL | fn get(&self) -> u8 {
11+
| ^^^^^^^^^^^^^^^^^^^
12+
help: use the `?` operator to extract the `Foo` value, propagating a `Result::Err` value to the caller
13+
|
14+
LL | res?.get();
15+
| +
16+
17+
error[E0599]: no method named `get` found for enum `Result` in the current scope
18+
--> $DIR/enum-method-probe.rs:22:9
19+
|
20+
LL | res.get();
21+
| ^^^ method not found in `Result<Foo, ()>`
22+
|
23+
note: the method `get` exists on the type `Foo`
24+
--> $DIR/enum-method-probe.rs:7:5
25+
|
26+
LL | fn get(&self) -> u8 {
27+
| ^^^^^^^^^^^^^^^^^^^
28+
help: consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is an `Err`
29+
|
30+
LL | res.expect("REASON").get();
31+
| +++++++++++++++++
32+
33+
error[E0599]: no method named `get` found for enum `Option` in the current scope
34+
--> $DIR/enum-method-probe.rs:29:9
35+
|
36+
LL | res.get();
37+
| ^^^ method not found in `Option<Foo>`
38+
|
39+
note: the method `get` exists on the type `Foo`
40+
--> $DIR/enum-method-probe.rs:7:5
41+
|
42+
LL | fn get(&self) -> u8 {
43+
| ^^^^^^^^^^^^^^^^^^^
44+
help: use the `?` operator to extract the `Foo` value, propagating a `None` to the caller
45+
|
46+
LL | res?.get();
47+
| +
48+
49+
error[E0599]: no method named `get` found for enum `Option` in the current scope
50+
--> $DIR/enum-method-probe.rs:37:9
51+
|
52+
LL | res.get();
53+
| ^^^ method not found in `Option<Foo>`
54+
|
55+
note: the method `get` exists on the type `Foo`
56+
--> $DIR/enum-method-probe.rs:7:5
57+
|
58+
LL | fn get(&self) -> u8 {
59+
| ^^^^^^^^^^^^^^^^^^^
60+
help: consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is `None`
61+
|
62+
LL | res.expect("REASON").get();
63+
| +++++++++++++++++
64+
65+
error: aborting due to 4 previous errors
66+
67+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)