about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2022-04-26 19:59:50 -0700
committerMichael Goulet <michael@errs.io>2022-05-30 13:52:40 -0700
commitaeb765b299640ee0674d2c01df600c296af7d691 (patch)
treed24a65fe3728fe13bd4f8def0297c14f8af18980
parent4a8d2e3856c0c75c71998b6c85937203839b946d (diff)
better enum suggestions
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs215
-rw-r--r--src/test/ui/suggestions/enum-method-probe.fixed42
-rw-r--r--src/test/ui/suggestions/enum-method-probe.rs42
-rw-r--r--src/test/ui/suggestions/enum-method-probe.stderr67
4 files changed, 314 insertions, 52 deletions
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs
index 4e54d554c6e..b5fd3eb4462 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_typeck/src/check/method/suggest.rs
@@ -978,45 +978,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     label_span_not_found(&mut err);
                 }
 
-                if let SelfSource::MethodCall(expr) = source
-                    && let Some((fields, substs)) = self.get_field_candidates(span, actual)
-                {
-                    let call_expr =
-                        self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id));
-                    for candidate_field in fields.iter() {
-                        if let Some(field_path) = self.check_for_nested_field_satisfying(
-                            span,
-                            &|_, field_ty| {
-                                self.lookup_probe(
-                                    span,
-                                    item_name,
-                                    field_ty,
-                                    call_expr,
-                                    ProbeScope::AllTraits,
-                                )
-                                .is_ok()
-                            },
-                            candidate_field,
-                            substs,
-                            vec![],
-                            self.tcx.parent_module(expr.hir_id).to_def_id(),
-                        ) {
-                            let field_path_str = field_path
-                                .iter()
-                                .map(|id| id.name.to_ident_string())
-                                .collect::<Vec<String>>()
-                                .join(".");
-                            debug!("field_path_str: {:?}", field_path_str);
+                self.check_for_field_method(&mut err, source, span, actual, item_name);
 
-                            err.span_suggestion_verbose(
-                                item_name.span.shrink_to_lo(),
-                                "one of the expressions' fields has a method of the same name",
-                                format!("{field_path_str}."),
-                                Applicability::MaybeIncorrect,
-                            );
-                        }
-                    }
-                }
+                self.check_for_unwrap_self(&mut err, source, span, actual, item_name);
 
                 bound_spans.sort();
                 bound_spans.dedup();
@@ -1343,6 +1307,157 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         false
     }
 
+    fn check_for_field_method(
+        &self,
+        err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
+        source: SelfSource<'tcx>,
+        span: Span,
+        actual: Ty<'tcx>,
+        item_name: Ident,
+    ) {
+        if let SelfSource::MethodCall(expr) = source
+            && let Some((fields, substs)) = self.get_field_candidates(span, actual)
+        {
+            let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id));
+            for candidate_field in fields.iter() {
+                if let Some(field_path) = self.check_for_nested_field_satisfying(
+                    span,
+                    &|_, field_ty| {
+                        self.lookup_probe(
+                            span,
+                            item_name,
+                            field_ty,
+                            call_expr,
+                            ProbeScope::AllTraits,
+                        )
+                        .is_ok()
+                    },
+                    candidate_field,
+                    substs,
+                    vec![],
+                    self.tcx.parent_module(expr.hir_id).to_def_id(),
+                ) {
+                    let field_path_str = field_path
+                        .iter()
+                        .map(|id| id.name.to_ident_string())
+                        .collect::<Vec<String>>()
+                        .join(".");
+                    debug!("field_path_str: {:?}", field_path_str);
+
+                    err.span_suggestion_verbose(
+                        item_name.span.shrink_to_lo(),
+                        "one of the expressions' fields has a method of the same name",
+                        format!("{field_path_str}."),
+                        Applicability::MaybeIncorrect,
+                    );
+                }
+            }
+        }
+    }
+
+    fn check_for_unwrap_self(
+        &self,
+        err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
+        source: SelfSource<'tcx>,
+        span: Span,
+        actual: Ty<'tcx>,
+        item_name: Ident,
+    ) {
+        let tcx = self.tcx;
+        let SelfSource::MethodCall(expr) = source else { return; };
+        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;
+        }
+
+        let matching_variants: Vec<_> = kind
+            .variants()
+            .iter()
+            .filter_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 matches!(self.resolve_vars_if_possible(field_ty).kind(), ty::Infer(_)) {
+                    return None;
+                }
+
+                if let Ok(pick) =
+                    self.lookup_probe(span, item_name, field_ty, call_expr, ProbeScope::AllTraits)
+                {
+                    Some((variant, field, pick))
+                } else {
+                    None
+                }
+            })
+            .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)] if Some(kind.did()) == tcx.get_diagnostic_item(sym::Result) => {
+                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}`"),
+                );
+                if ret_ty_matches(sym::Result) {
+                    err.span_suggestion_verbose(
+                        expr.span.shrink_to_hi(),
+                        format!("use the `?` operator to extract the `{self_ty}` value, propagating a `Result::Err` value to the caller"),
+                        "?".to_owned(),
+                        Applicability::MachineApplicable,
+                    );
+                } else {
+                    err.span_suggestion_verbose(
+                        expr.span.shrink_to_hi(),
+                        format!("consider using `Result::expect` to unwrap the `{self_ty}` value, panicking if the value is an `Err`"),
+                        ".expect(\"REASON\")".to_owned(),
+                        Applicability::HasPlaceholders,
+                    );
+                }
+            }
+            [(_, field, pick)] if Some(kind.did()) == tcx.get_diagnostic_item(sym::Option) => {
+                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}`"),
+                );
+                if ret_ty_matches(sym::Option) {
+                    err.span_suggestion_verbose(
+                        expr.span.shrink_to_hi(),
+                        format!("use the `?` operator to extract the `{self_ty}` value, propagating a `None` to the caller"),
+                        "?".to_owned(),
+                        Applicability::MachineApplicable,
+                    );
+                } else {
+                    err.span_suggestion_verbose(
+                        expr.span.shrink_to_hi(),
+                        format!("consider using `Option::expect` to unwrap the `{self_ty}` value, panicking if the value is `None`"),
+                        ".expect(\"REASON\")".to_owned(),
+                        Applicability::HasPlaceholders,
+                    );
+                }
+            }
+            // FIXME(compiler-errors): Support suggestions for other matching enum variants
+            _ => {}
+        }
+    }
+
     pub(crate) fn note_unmet_impls_on_type(
         &self,
         err: &mut Diagnostic,
@@ -1662,13 +1777,7 @@ 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(
-                    span,
-                    item_name,
-                    *rcvr_ty,
-                    rcvr,
-                    crate::check::method::probe::ProbeScope::AllTraits,
-                ) {
+                match self.lookup_probe(span, item_name, *rcvr_ty, rcvr, 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
@@ -1700,13 +1809,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     (self.tcx.mk_diagnostic_item(*rcvr_ty, sym::Arc), "Arc::new"),
                     (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(
-                        span,
-                        item_name,
-                        new_rcvr_t,
-                        rcvr,
-                        crate::check::method::probe::ProbeScope::AllTraits,
-                    ) {
+                    if let Some(new_rcvr_t) = *rcvr_ty
+                        && let Ok(pick) = self.lookup_probe(
+                            span,
+                            item_name,
+                            new_rcvr_t,
+                            rcvr,
+                            ProbeScope::AllTraits,
+                        )
+                    {
                         debug!("try_alt_rcvr: pick candidate {:?}", pick);
                         let did = Some(pick.item.container.id());
                         // We don't want to suggest a container type when the missing
diff --git a/src/test/ui/suggestions/enum-method-probe.fixed b/src/test/ui/suggestions/enum-method-probe.fixed
new file mode 100644
index 00000000000..990f7900f22
--- /dev/null
+++ b/src/test/ui/suggestions/enum-method-probe.fixed
@@ -0,0 +1,42 @@
+// run-rustfix
+#![allow(unused)]
+
+struct Foo;
+
+impl Foo {
+    fn get(&self) -> u8 {
+        42
+    }
+}
+
+fn test_result_in_result() -> Result<(), ()> {
+    let res: Result<_, ()> = Ok(Foo);
+    res?.get();
+    //~^ ERROR no method named `get` found for enum `Result` in the current scope
+    //~| HELP use the `?` operator
+    Ok(())
+}
+
+fn test_result_in_plain() {
+    let res: Result<_, ()> = Ok(Foo);
+    res.expect("REASON").get();
+    //~^ ERROR no method named `get` found for enum `Result` in the current scope
+    //~| HELP consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is an `Err`
+}
+
+fn test_option_in_option() -> Option<()> {
+    let res: Option<_> = Some(Foo);
+    res?.get();
+    //~^ ERROR no method named `get` found for enum `Option` in the current scope
+    //~| HELP use the `?` operator
+    Some(())
+}
+
+fn test_option_in_plain() {
+    let res: Option<_> = Some(Foo);
+    res.expect("REASON").get();
+    //~^ ERROR no method named `get` found for enum `Option` in the current scope
+    //~| HELP consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is `None`
+}
+
+fn main() {}
diff --git a/src/test/ui/suggestions/enum-method-probe.rs b/src/test/ui/suggestions/enum-method-probe.rs
new file mode 100644
index 00000000000..6270fa9fea5
--- /dev/null
+++ b/src/test/ui/suggestions/enum-method-probe.rs
@@ -0,0 +1,42 @@
+// run-rustfix
+#![allow(unused)]
+
+struct Foo;
+
+impl Foo {
+    fn get(&self) -> u8 {
+        42
+    }
+}
+
+fn test_result_in_result() -> Result<(), ()> {
+    let res: Result<_, ()> = Ok(Foo);
+    res.get();
+    //~^ ERROR no method named `get` found for enum `Result` in the current scope
+    //~| HELP use the `?` operator
+    Ok(())
+}
+
+fn test_result_in_plain() {
+    let res: Result<_, ()> = Ok(Foo);
+    res.get();
+    //~^ ERROR no method named `get` found for enum `Result` in the current scope
+    //~| HELP consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is an `Err`
+}
+
+fn test_option_in_option() -> Option<()> {
+    let res: Option<_> = Some(Foo);
+    res.get();
+    //~^ ERROR no method named `get` found for enum `Option` in the current scope
+    //~| HELP use the `?` operator
+    Some(())
+}
+
+fn test_option_in_plain() {
+    let res: Option<_> = Some(Foo);
+    res.get();
+    //~^ ERROR no method named `get` found for enum `Option` in the current scope
+    //~| HELP consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is `None`
+}
+
+fn main() {}
diff --git a/src/test/ui/suggestions/enum-method-probe.stderr b/src/test/ui/suggestions/enum-method-probe.stderr
new file mode 100644
index 00000000000..4af1775f66b
--- /dev/null
+++ b/src/test/ui/suggestions/enum-method-probe.stderr
@@ -0,0 +1,67 @@
+error[E0599]: no method named `get` found for enum `Result` in the current scope
+  --> $DIR/enum-method-probe.rs:14:9
+   |
+LL |     res.get();
+   |         ^^^ method not found in `Result<Foo, ()>`
+   |
+note: the method `get` exists on the type `Foo`
+  --> $DIR/enum-method-probe.rs:7:5
+   |
+LL |     fn get(&self) -> u8 {
+   |     ^^^^^^^^^^^^^^^^^^^
+help: use the `?` operator to extract the `Foo` value, propagating a `Result::Err` value to the caller
+   |
+LL |     res?.get();
+   |        +
+
+error[E0599]: no method named `get` found for enum `Result` in the current scope
+  --> $DIR/enum-method-probe.rs:22:9
+   |
+LL |     res.get();
+   |         ^^^ method not found in `Result<Foo, ()>`
+   |
+note: the method `get` exists on the type `Foo`
+  --> $DIR/enum-method-probe.rs:7:5
+   |
+LL |     fn get(&self) -> u8 {
+   |     ^^^^^^^^^^^^^^^^^^^
+help: consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is an `Err`
+   |
+LL |     res.expect("REASON").get();
+   |        +++++++++++++++++
+
+error[E0599]: no method named `get` found for enum `Option` in the current scope
+  --> $DIR/enum-method-probe.rs:29:9
+   |
+LL |     res.get();
+   |         ^^^ method not found in `Option<Foo>`
+   |
+note: the method `get` exists on the type `Foo`
+  --> $DIR/enum-method-probe.rs:7:5
+   |
+LL |     fn get(&self) -> u8 {
+   |     ^^^^^^^^^^^^^^^^^^^
+help: use the `?` operator to extract the `Foo` value, propagating a `None` to the caller
+   |
+LL |     res?.get();
+   |        +
+
+error[E0599]: no method named `get` found for enum `Option` in the current scope
+  --> $DIR/enum-method-probe.rs:37:9
+   |
+LL |     res.get();
+   |         ^^^ method not found in `Option<Foo>`
+   |
+note: the method `get` exists on the type `Foo`
+  --> $DIR/enum-method-probe.rs:7:5
+   |
+LL |     fn get(&self) -> u8 {
+   |     ^^^^^^^^^^^^^^^^^^^
+help: consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is `None`
+   |
+LL |     res.expect("REASON").get();
+   |        +++++++++++++++++
+
+error: aborting due to 4 previous errors
+
+For more information about this error, try `rustc --explain E0599`.