summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2022-06-01 17:11:04 +0200
committerGitHub <noreply@github.com>2022-06-01 17:11:04 +0200
commit7d4cf710e2678d31fd899c452e1d4baf48eb8a86 (patch)
tree761f3cec0ff35c0f36bf9f8be24f52b32cce5ddc
parent395a09c3dafe0c7838c9ca41d2b47bb5e79a5b6d (diff)
parent2a61f0cc45c809c62dd149477cb2672c2022c3a4 (diff)
downloadrust-7d4cf710e2678d31fd899c452e1d4baf48eb8a86.tar.gz
Rollup merge of #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 #95729
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs203
-rw-r--r--src/test/ui/suggestions/enum-method-probe.fixed59
-rw-r--r--src/test/ui/suggestions/enum-method-probe.rs59
-rw-r--r--src/test/ui/suggestions/enum-method-probe.stderr99
4 files changed, 368 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..0e198907c8d 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,145 @@ 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()
+ .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;
+ }
+
+ 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))
+ } 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"
+ ),
+ "?".to_owned(),
+ 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\")".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 +1765,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 +1797,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..6499c92bc6f
--- /dev/null
+++ b/src/test/ui/suggestions/enum-method-probe.fixed
@@ -0,0 +1,59 @@
+// compile-flags: --edition=2021
+// 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(())
+}
+
+async fn async_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_unit_return() {
+ 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 a `Result::Err`
+}
+
+async fn async_test_result_in_unit_return() {
+ 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 a `Result::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_unit_return() {
+ 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 an `Option::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..18ea8ed8a58
--- /dev/null
+++ b/src/test/ui/suggestions/enum-method-probe.rs
@@ -0,0 +1,59 @@
+// compile-flags: --edition=2021
+// 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(())
+}
+
+async fn async_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_unit_return() {
+ 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 a `Result::Err`
+}
+
+async fn async_test_result_in_unit_return() {
+ 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 a `Result::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_unit_return() {
+ 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 an `Option::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..6ed14984f47
--- /dev/null
+++ b/src/test/ui/suggestions/enum-method-probe.stderr
@@ -0,0 +1,99 @@
+error[E0599]: no method named `get` found for enum `Result` in the current scope
+ --> $DIR/enum-method-probe.rs:24:9
+ |
+LL | res.get();
+ | ^^^ method not found in `Result<Foo, ()>`
+ |
+note: the method `get` exists on the type `Foo`
+ --> $DIR/enum-method-probe.rs:9: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:39:9
+ |
+LL | res.get();
+ | ^^^ method not found in `Result<Foo, ()>`
+ |
+note: the method `get` exists on the type `Foo`
+ --> $DIR/enum-method-probe.rs:9:5
+ |
+LL | fn get(&self) -> u8 {
+ | ^^^^^^^^^^^^^^^^^^^
+help: consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is a `Result::Err`
+ |
+LL | res.expect("REASON").get();
+ | +++++++++++++++++
+
+error[E0599]: no method named `get` found for enum `Result` in the current scope
+ --> $DIR/enum-method-probe.rs:16:9
+ |
+LL | res.get();
+ | ^^^ method not found in `Result<Foo, ()>`
+ |
+note: the method `get` exists on the type `Foo`
+ --> $DIR/enum-method-probe.rs:9: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:32:9
+ |
+LL | res.get();
+ | ^^^ method not found in `Result<Foo, ()>`
+ |
+note: the method `get` exists on the type `Foo`
+ --> $DIR/enum-method-probe.rs:9:5
+ |
+LL | fn get(&self) -> u8 {
+ | ^^^^^^^^^^^^^^^^^^^
+help: consider using `Result::expect` to unwrap the `Foo` value, panicking if the value is a `Result::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:46:9
+ |
+LL | res.get();
+ | ^^^ method not found in `Option<Foo>`
+ |
+note: the method `get` exists on the type `Foo`
+ --> $DIR/enum-method-probe.rs:9:5
+ |
+LL | fn get(&self) -> u8 {
+ | ^^^^^^^^^^^^^^^^^^^
+help: use the `?` operator to extract the `Foo` value, propagating an `Option::None` value 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:54:9
+ |
+LL | res.get();
+ | ^^^ method not found in `Option<Foo>`
+ |
+note: the method `get` exists on the type `Foo`
+ --> $DIR/enum-method-probe.rs:9:5
+ |
+LL | fn get(&self) -> u8 {
+ | ^^^^^^^^^^^^^^^^^^^
+help: consider using `Option::expect` to unwrap the `Foo` value, panicking if the value is an `Option::None`
+ |
+LL | res.expect("REASON").get();
+ | +++++++++++++++++
+
+error: aborting due to 6 previous errors
+
+For more information about this error, try `rustc --explain E0599`.