diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 042a50f2fd4..0a230fca107 100644 --- a/compiler/rustc_hir_typeck/src/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -31,7 +31,9 @@ use super::FnCtxt; use crate::type_error_struct; -use rustc_errors::{struct_span_err, Applicability, DelayDm, DiagnosticBuilder, ErrorGuaranteed}; +use rustc_errors::{ + struct_span_err, Applicability, DelayDm, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, +}; use rustc_hir as hir; use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::mir::Mutability; @@ -270,6 +272,9 @@ impl<'a, 'tcx> CastCheck<'tcx> { } )); } + + self.try_suggest_collection_to_bool(fcx, &mut err); + err.emit(); } CastError::NeedViaInt => { @@ -517,6 +522,9 @@ impl<'a, 'tcx> CastCheck<'tcx> { } else { err.span_label(self.span, "invalid cast"); } + + self.try_suggest_collection_to_bool(fcx, &mut err); + err.emit(); } CastError::SizedUnsizedCast => { @@ -1080,4 +1088,40 @@ impl<'a, 'tcx> CastCheck<'tcx> { }, ); } + + /// Attempt to suggest using `.is_empty` when trying to cast from a + /// collection type to a boolean. + fn try_suggest_collection_to_bool(&self, fcx: &FnCtxt<'a, 'tcx>, err: &mut Diagnostic) { + if self.cast_ty.is_bool() { + let derefed = fcx + .autoderef(self.expr_span, self.expr_ty) + .silence_errors() + .find(|t| matches!(t.0.kind(), ty::Str | ty::Slice(..))); + + if let Some((deref_ty, _)) = derefed { + // Give a note about what the expr derefs to. + if deref_ty != self.expr_ty.peel_refs() { + err.span_note( + self.expr_span, + format!( + "this expression `Deref`s to `{}` which implements `is_empty`", + fcx.ty_to_string(deref_ty) + ), + ); + } + + // Create a multipart suggestion: add `!` and `.is_empty()` in + // place of the cast. + let suggestion = vec![ + (self.expr_span.shrink_to_lo(), "!".to_string()), + (self.span.with_lo(self.expr_span.hi()), ".is_empty()".to_string()), + ]; + + err.multipart_suggestion_verbose(format!( + "consider using the `is_empty` method on `{}` to determine if it contains anything", + fcx.ty_to_string(self.expr_ty), + ), suggestion, Applicability::MaybeIncorrect); + } + } + } } diff --git a/tests/ui/cast/cast-as-bool.rs b/tests/ui/cast/cast-as-bool.rs index 1aed218aeb4..fbebc80d91c 100644 --- a/tests/ui/cast/cast-as-bool.rs +++ b/tests/ui/cast/cast-as-bool.rs @@ -2,8 +2,12 @@ fn main() { let u = 5 as bool; //~ ERROR cannot cast as `bool` //~| HELP compare with zero instead //~| SUGGESTION 5 != 0 + let t = (1 + 2) as bool; //~ ERROR cannot cast as `bool` //~| HELP compare with zero instead //~| SUGGESTION (1 + 2) != 0 - let v = "hello" as bool; //~ ERROR casting `&'static str` as `bool` is invalid + + let v = "hello" as bool; + //~^ ERROR casting `&'static str` as `bool` is invalid + //~| HELP consider using the `is_empty` method on `&'static str` to determine if it contains anything } diff --git a/tests/ui/cast/cast-as-bool.stderr b/tests/ui/cast/cast-as-bool.stderr index 15d94ab69d8..19ac8f10fec 100644 --- a/tests/ui/cast/cast-as-bool.stderr +++ b/tests/ui/cast/cast-as-bool.stderr @@ -5,16 +5,21 @@ LL | let u = 5 as bool; | ^^^^^^^^^ help: compare with zero instead: `5 != 0` error[E0054]: cannot cast as `bool` - --> $DIR/cast-as-bool.rs:5:13 + --> $DIR/cast-as-bool.rs:6:13 | LL | let t = (1 + 2) as bool; | ^^^^^^^^^^^^^^^ help: compare with zero instead: `(1 + 2) != 0` error[E0606]: casting `&'static str` as `bool` is invalid - --> $DIR/cast-as-bool.rs:8:13 + --> $DIR/cast-as-bool.rs:10:13 | LL | let v = "hello" as bool; | ^^^^^^^^^^^^^^^ + | +help: consider using the `is_empty` method on `&'static str` to determine if it contains anything + | +LL | let v = !"hello".is_empty(); + | + ~~~~~~~~~~~ error: aborting due to 3 previous errors diff --git a/tests/ui/cast/issue-106883-is-empty.rs b/tests/ui/cast/issue-106883-is-empty.rs new file mode 100644 index 00000000000..27e0816dd1c --- /dev/null +++ b/tests/ui/cast/issue-106883-is-empty.rs @@ -0,0 +1,27 @@ +use std::ops::Deref; + +struct Foo; + +impl Deref for Foo { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &[] + } +} + +fn main() { + let _ = "foo" as bool; + //~^ ERROR casting `&'static str` as `bool` is invalid [E0606] + + let _ = String::from("foo") as bool; + //~^ ERROR non-primitive cast: `String` as `bool` [E0605] + + let _ = Foo as bool; + //~^ ERROR non-primitive cast: `Foo` as `bool` [E0605] +} + +fn _slice(bar: &[i32]) -> bool { + bar as bool + //~^ ERROR casting `&[i32]` as `bool` is invalid [E0606] +} diff --git a/tests/ui/cast/issue-106883-is-empty.stderr b/tests/ui/cast/issue-106883-is-empty.stderr new file mode 100644 index 00000000000..7115c7704ca --- /dev/null +++ b/tests/ui/cast/issue-106883-is-empty.stderr @@ -0,0 +1,58 @@ +error[E0606]: casting `&'static str` as `bool` is invalid + --> $DIR/issue-106883-is-empty.rs:14:13 + | +LL | let _ = "foo" as bool; + | ^^^^^^^^^^^^^ + | +help: consider using the `is_empty` method on `&'static str` to determine if it contains anything + | +LL | let _ = !"foo".is_empty(); + | + ~~~~~~~~~~~ + +error[E0605]: non-primitive cast: `String` as `bool` + --> $DIR/issue-106883-is-empty.rs:17:13 + | +LL | let _ = String::from("foo") as bool; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object + | +note: this expression `Deref`s to `str` which implements `is_empty` + --> $DIR/issue-106883-is-empty.rs:17:13 + | +LL | let _ = String::from("foo") as bool; + | ^^^^^^^^^^^^^^^^^^^ +help: consider using the `is_empty` method on `String` to determine if it contains anything + | +LL | let _ = !String::from("foo").is_empty(); + | + ~~~~~~~~~~~ + +error[E0605]: non-primitive cast: `Foo` as `bool` + --> $DIR/issue-106883-is-empty.rs:20:13 + | +LL | let _ = Foo as bool; + | ^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object + | +note: this expression `Deref`s to `[u8]` which implements `is_empty` + --> $DIR/issue-106883-is-empty.rs:20:13 + | +LL | let _ = Foo as bool; + | ^^^ +help: consider using the `is_empty` method on `Foo` to determine if it contains anything + | +LL | let _ = !Foo.is_empty(); + | + ~~~~~~~~~~~ + +error[E0606]: casting `&[i32]` as `bool` is invalid + --> $DIR/issue-106883-is-empty.rs:25:5 + | +LL | bar as bool + | ^^^^^^^^^^^ + | +help: consider using the `is_empty` method on `&[i32]` to determine if it contains anything + | +LL | !bar.is_empty() + | + ~~~~~~~~~~~ + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0605, E0606. +For more information about an error, try `rustc --explain E0605`.