Rollup merge of #106896 - Ezrashaw:str-cast-bool-emptyness, r=compiler-errors

suggest `is_empty` for collections when casting to `bool`

Fixes #106883

Matches on slices, `String` and `str`. It would be nice to do this with something like `Deref<Target=str>` as well, but AFAIK it's not possible in this part of the compiler.
This commit is contained in:
Matthias Krüger 2023-01-15 21:17:34 +01:00 committed by GitHub
commit 5610231454
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 4 deletions

View File

@ -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);
}
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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]
}

View File

@ -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`.