diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index c9ec32159f476..9686d3f6d26f7 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -917,6 +917,30 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "`count` is defined on `{iterator_trait}`, which `{rcvr_ty}` does not implement" )); } + } else if matches!(item_ident.name.as_str(), "cloned" | "copied") + && let ty::Adt(adt_def, args) = rcvr_ty.kind() + && tcx.is_diagnostic_item(sym::Option, adt_def.did()) + && let inner_ty = args.type_at(0) + // Skip refs (`Option<&T>.into_iter().cloned()` is valid, let the default branch + // handle it), and params/infer where we can't statically rule out a reference. + && !matches!(inner_ty.kind(), ty::Ref(..) | ty::Param(_) | ty::Infer(_)) + { + // The default branch below would suggest `.into_iter()`, but that still + // fails: `Option` yields `T` by value, not `&T`, so `.cloned()`/`.copied()` + // can't resolve. Give a targeted diagnostic instead. + err.span_label(span, format!("this method is only available on `Option<&_>`")); + if let SelfSource::MethodCall(rcvr_expr) = source + && !span.in_external_macro(tcx.sess.source_map()) + { + let call_expr = self.tcx.hir_expect_expr(self.tcx.parent_hir_id(rcvr_expr.hir_id)); + err.span_suggestion( + rcvr_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi()), + format!("consider removing the `.{}()` call", item_ident.name), + "", + Applicability::MaybeIncorrect, + ); + } + return Err(()); } else if self.impl_into_iterator_should_be_iterator(rcvr_ty, span, unsatisfied_predicates) { err.span_label(span, format!("`{rcvr_ty}` is not an iterator")); diff --git a/tests/ui/methods/option-cloned-copied-non-ref-excluded.rs b/tests/ui/methods/option-cloned-copied-non-ref-excluded.rs new file mode 100644 index 0000000000000..317ec4b39c55d --- /dev/null +++ b/tests/ui/methods/option-cloned-copied-non-ref-excluded.rs @@ -0,0 +1,27 @@ +// Tests that the guard for `.cloned()`/`.copied()` on `Option` correctly +// excludes inner types where the targeted diagnostic would be wrong: +// +// - `Option<&T>`: `Option<&T>` has inherent `cloned()`/`copied()` methods, +// so these compile successfully and the guard must not fire. +// - `Option` (generic param): falls through to the standard "not an +// iterator / call .into_iter() first" diagnostic. +// +// See https://github.com/rust-lang/rust/issues/151147 + +// Reference inner type: these should compile without error. +pub fn cloned_on_ref(x: Option<&i32>) -> Option { + x.cloned() +} + +pub fn copied_on_ref(x: Option<&i32>) -> Option { + x.copied() +} + +// Generic param inner type: falls through to the standard diagnostic. +pub fn cloned_on_param(x: Option) { + x.cloned(); + //~^ ERROR no method named `cloned` found for enum `Option` in the current scope + //~| HELP call `.into_iter()` first +} + +fn main() {} diff --git a/tests/ui/methods/option-cloned-copied-non-ref-excluded.stderr b/tests/ui/methods/option-cloned-copied-non-ref-excluded.stderr new file mode 100644 index 0000000000000..7d442349e4e7f --- /dev/null +++ b/tests/ui/methods/option-cloned-copied-non-ref-excluded.stderr @@ -0,0 +1,14 @@ +error[E0599]: no method named `cloned` found for enum `Option` in the current scope + --> $DIR/option-cloned-copied-non-ref-excluded.rs:22:7 + | +LL | x.cloned(); + | ^^^^^^ `Option` is not an iterator + | +help: call `.into_iter()` first + | +LL | x.into_iter().cloned(); + | ++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0599`. diff --git a/tests/ui/methods/option-cloned-copied-non-ref.fixed b/tests/ui/methods/option-cloned-copied-non-ref.fixed new file mode 100644 index 0000000000000..203edf880c1c8 --- /dev/null +++ b/tests/ui/methods/option-cloned-copied-non-ref.fixed @@ -0,0 +1,26 @@ +// Tests that calling `.cloned()` or `.copied()` on `Option` where `T` is an +// owned (non-reference) type gives a targeted diagnostic instead of the +// misleading "not an iterator / call .into_iter() first" suggestion. +// See https://github.com/rust-lang/rust/issues/151147 + +//@ run-rustfix + +pub fn cloned_on_owned(x: Option) -> i32 { + x.unwrap() + //~^ ERROR no method named `cloned` found for enum `Option` in the current scope + //~| HELP consider removing the `.cloned()` call +} + +pub fn copied_on_owned(x: Option) -> i32 { + x.unwrap() + //~^ ERROR no method named `copied` found for enum `Option` in the current scope + //~| HELP consider removing the `.copied()` call +} + +pub fn cloned_on_string(x: Option) -> String { + x.unwrap() + //~^ ERROR no method named `cloned` found for enum `Option` in the current scope + //~| HELP consider removing the `.cloned()` call +} + +fn main() {} diff --git a/tests/ui/methods/option-cloned-copied-non-ref.rs b/tests/ui/methods/option-cloned-copied-non-ref.rs new file mode 100644 index 0000000000000..bf0c68ef30b74 --- /dev/null +++ b/tests/ui/methods/option-cloned-copied-non-ref.rs @@ -0,0 +1,26 @@ +// Tests that calling `.cloned()` or `.copied()` on `Option` where `T` is an +// owned (non-reference) type gives a targeted diagnostic instead of the +// misleading "not an iterator / call .into_iter() first" suggestion. +// See https://github.com/rust-lang/rust/issues/151147 + +//@ run-rustfix + +pub fn cloned_on_owned(x: Option) -> i32 { + x.cloned().unwrap() + //~^ ERROR no method named `cloned` found for enum `Option` in the current scope + //~| HELP consider removing the `.cloned()` call +} + +pub fn copied_on_owned(x: Option) -> i32 { + x.copied().unwrap() + //~^ ERROR no method named `copied` found for enum `Option` in the current scope + //~| HELP consider removing the `.copied()` call +} + +pub fn cloned_on_string(x: Option) -> String { + x.cloned().unwrap() + //~^ ERROR no method named `cloned` found for enum `Option` in the current scope + //~| HELP consider removing the `.cloned()` call +} + +fn main() {} diff --git a/tests/ui/methods/option-cloned-copied-non-ref.stderr b/tests/ui/methods/option-cloned-copied-non-ref.stderr new file mode 100644 index 0000000000000..81072c2d7231a --- /dev/null +++ b/tests/ui/methods/option-cloned-copied-non-ref.stderr @@ -0,0 +1,30 @@ +error[E0599]: no method named `cloned` found for enum `Option` in the current scope + --> $DIR/option-cloned-copied-non-ref.rs:9:7 + | +LL | x.cloned().unwrap() + | -^^^^^^-- + | || + | |this method is only available on `Option<&_>` + | help: consider removing the `.cloned()` call + +error[E0599]: no method named `copied` found for enum `Option` in the current scope + --> $DIR/option-cloned-copied-non-ref.rs:15:7 + | +LL | x.copied().unwrap() + | -^^^^^^-- + | || + | |this method is only available on `Option<&_>` + | help: consider removing the `.copied()` call + +error[E0599]: no method named `cloned` found for enum `Option` in the current scope + --> $DIR/option-cloned-copied-non-ref.rs:21:7 + | +LL | x.cloned().unwrap() + | -^^^^^^-- + | || + | |this method is only available on `Option<&_>` + | help: consider removing the `.cloned()` call + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0599`.