Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lint assert!(true / false) #3575

Closed
matthiaskrgr opened this issue Dec 23, 2018 · 10 comments
Closed

lint assert!(true / false) #3575

matthiaskrgr opened this issue Dec 23, 2018 · 10 comments
Labels
C-enhancement Category: Enhancement of lints, like adding more cases or adding help messages good-first-issue These issues are a good way to get started with Clippy L-suggestion Lint: Improving, adding or fixing lint suggestions

Comments

@matthiaskrgr
Copy link
Member

assert!(true); will be optimized out by the compiler
assert!(false); should probably be replaced by a panic!().

@Arkweid
Copy link

Arkweid commented Dec 24, 2018

Take this one :)

@ordovicia
Copy link
Contributor

How about replacing assert!(false) with unreachable!()?
I guess programmers often use assert!(false) and equivalent in other languages with the intention that the code is unreachable.
Of course panic!() is more versatile, so it may be better to use panic!() for the replacement.

@Arkweid
Copy link

Arkweid commented Dec 25, 2018

PR added :)
@matthiaskrgr can you check? Especially the error messages, I'm not sure what the description is as it should be

bors added a commit that referenced this issue Jan 22, 2019
Add assert(true) and assert(false) lints

This PR add two lints on assert!(true) and assert!(false).
#3575
@flip1995
Copy link
Member

After #3582 was merged this lint can be enhanced by suggestions for assert(true/false):

@flip1995 flip1995 added good-first-issue These issues are a good way to get started with Clippy C-enhancement Category: Enhancement of lints, like adding more cases or adding help messages L-suggestion Lint: Improving, adding or fixing lint suggestions labels Jan 22, 2019
flip1995 added a commit to flip1995/rust-clippy that referenced this issue Jan 22, 2019
…lip1995

Add assert(true) and assert(false) lints

This PR add two lints on assert!(true) and assert!(false).
rust-lang#3575
flip1995 added a commit to flip1995/rust-clippy that referenced this issue Jan 22, 2019
…lip1995

Add assert(true) and assert(false) lints

This PR add two lints on assert!(true) and assert!(false).
rust-lang#3575
bors added a commit that referenced this issue Jan 23, 2019
Add assert(true) and assert(false) lints

This PR add two lints on assert!(true) and assert!(false).
#3575
@Lythenas
Copy link
Contributor

Lythenas commented Oct 4, 2019

I would like to try and implement the first suggestion (assert(true) -> "" and assert(false) -> panic!()). And after maybe the second suggestion too.

@flip1995
Copy link
Member

flip1995 commented Oct 4, 2019

Just for clarification:

assert(true) -> "" and assert(false) -> panic!()

suggesting "" instead of assert doesn't make sense. Then I saw, that I recommended to do this:

suggest ""/"panic!()"

What I meant with that is, to suggest removing the assert completely if it is assert!(true) and to suggest panic!() if it is assert!(false).

@Lythenas
Copy link
Contributor

Lythenas commented Oct 4, 2019

Right. I didn't think that far. Also I notices the first part is already implemented so I will start on the second part assert!(false, "msg") -> panic/unreachable!("msg")

@Lythenas
Copy link
Contributor

Lythenas commented Oct 4, 2019

I will need some help here.

I found

fn check_tts<'a>(cx: &EarlyContext<'a>, tts: &TokenStream, is_write: bool) -> (Option<FmtStr>, Option<Expr>) {
use fmt_macros::*;
let tts = tts.clone();
let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, None, false, false, None);
let mut expr: Option<Expr> = None;
if is_write {
expr = match parser.parse_expr().map_err(|mut err| err.cancel()) {
Ok(p) => Some(p.into_inner()),
Err(_) => return (None, None),
};
// might be `writeln!(foo)`
if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() {
return (None, expr);
}
}
let (fmtstr, fmtstyle) = match parser.parse_str().map_err(|mut err| err.cancel()) {
Ok((fmtstr, fmtstyle)) => (fmtstr.to_string(), fmtstyle),
Err(_) => return (None, expr),
};
let fmtspan = parser.prev_span;
let tmp = fmtstr.clone();
let mut args = vec![];
let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false);
while let Some(piece) = fmt_parser.next() {
if !fmt_parser.errors.is_empty() {
return (None, expr);
}
if let Piece::NextArgument(arg) = piece {
if arg.format.ty == "?" {
// FIXME: modify rustc's fmt string parser to give us the current span
span_lint(cx, USE_DEBUG, parser.prev_span, "use of `Debug`-based formatting");
}
args.push(arg);
}
}
let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
let mut idx = 0;
loop {
const SIMPLE: FormatSpec<'_> = FormatSpec {
fill: None,
align: AlignUnknown,
flags: 0,
precision: CountImplied,
precision_span: None,
width: CountImplied,
width_span: None,
ty: "",
};
if !parser.eat(&token::Comma) {
return (
Some(FmtStr {
contents: fmtstr,
style: fmtstyle,
span: fmtspan,
}),
expr,
);
}
let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
expr
} else {
return (
Some(FmtStr {
contents: fmtstr,
style: fmtstyle,
span: fmtspan,
}),
None,
);
};
match &token_expr.kind {
ExprKind::Lit(_) => {
let mut all_simple = true;
let mut seen = false;
for arg in &args {
match arg.position {
ArgumentImplicitlyIs(n) | ArgumentIs(n) => {
if n == idx {
all_simple &= arg.format == SIMPLE;
seen = true;
}
},
ArgumentNamed(_) => {},
}
}
if all_simple && seen {
span_lint(cx, lint, token_expr.span, "literal with an empty format string");
}
idx += 1;
},
ExprKind::Assign(lhs, rhs) => {
if let ExprKind::Lit(_) = rhs.kind {
if let ExprKind::Path(_, p) = &lhs.kind {
let mut all_simple = true;
let mut seen = false;
for arg in &args {
match arg.position {
ArgumentImplicitlyIs(_) | ArgumentIs(_) => {},
ArgumentNamed(name) => {
if *p == name {
seen = true;
all_simple &= arg.format == SIMPLE;
}
},
}
}
if all_simple && seen {
span_lint(cx, lint, rhs.span, "literal with an empty format string");
}
}
}
},
_ => idx += 1,
}
}
}

But it uses EarlyContext and the assertions_on_constants is a LateLintPass. Is there still some way to reuse this? Or am I look at this the wrong way?

@flip1995
Copy link
Member

flip1995 commented Oct 4, 2019

assertions_on_constants is a LateLintPass

Yes, that's why I wrote (advanced) behind this. What you need to do is to look at the expanded code. You can do this with the rustc flag -Zunpretty=hir.

For example:

fn main() {
    assert!(false, "some {} things", "String");
}

expands to

#[prelude_import]
use ::std::prelude::v1::*;
#[macro_use]
extern crate std;
fn main() {
              match { let _t = !false; _t } {
                  true => {
                      {
                          ::std::rt::begin_panic_fmt(&<::core::fmt::Arguments>::new_v1(&["some ",
                                                                                         " things"],
                                                                                       &match (&"String",)
                                                                                            {
                                                                                            (arg0,)
                                                                                            =>
                                                                                            [<::core::fmt::ArgumentV1>::new(arg0,
                                                                                                                            ::core::fmt::Display::fmt)],
                                                                                        }),
                                                     &("main.rs", 2u32, 5u32))
                      }
                  }
                  _ => { }
              };
          }

You then have to match the expanded construct. Something similar was done before in clippy_lints/src/format.rs, so you may be able to reuse this code. It would also be great to have a utils function that preduces a suggestion for this core::fmt::Arguments::new_v1 things, since this is not trivial. But a first step would be to only produce suggestions for assert!s without format args. (assert!(false, "no fmt args")), since something similar is already done in clippy_lints/src/format.rs.

bors added a commit that referenced this issue Oct 9, 2019
Add assert message to suggestion in lint assertions_on_constants

<!--
Thank you for making Clippy better!

We're collecting our changelog from pull request descriptions.
If your PR only updates to the latest nightly, you can leave the
`changelog` entry as `none`. Otherwise, please write a short comment
explaining your change.

If your PR fixes an issue, you can add "fixes #issue_number" into this
PR description. This way the issue will be automatically closed when
your PR is merged.

If you added a new lint, here's a checklist for things that will be
checked during review or continuous integration.

- [x] Followed [lint naming conventions][lint_naming]
- [x] Added passing UI tests (including committed `.stderr` file)
- [ ] `cargo test` passes locally
- [x] Executed `./util/dev update_lints`
- [ ] Added lint documentation
- [ ] Run `./util/dev fmt`

[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints

Note that you can skip the above if you are just opening a WIP PR in
order to get feedback.

Delete this line and everything above before opening your PR -->

- [x] suggest replacing `assert!(false, "msg")` with `panic!("msg")`
- [x] extend to allow ~~variables~~ any expression for `"msg"`
- ~~suggest replacing `assert!(false, "msg {}", "arg")` with `panic!("msg {}", "arg")`~~

changelog: add assert message to suggestion in lint assertions_on_constants

Work towards fixing: #3575
@CAD97
Copy link
Contributor

CAD97 commented Jan 13, 2024

The implementation now evaluates the constant and emits different help messages based on whether the assertion is always true or false. I think that's sufficient to close this issue.

With #11966 fixed (not linting when the assert! is always constant evaluated), assert!(TRUE) should be replaced with const _: () = assert!(TRUE); (or similar) to ensure constant evaluation, and assert!(FALSE) should (almost1) always be a panic!ing macro instead.

Footnotes

  1. The exception being "fickle" consts that vary based on cfg and/or lib versions, which could validly only want a runtime panic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: Enhancement of lints, like adding more cases or adding help messages good-first-issue These issues are a good way to get started with Clippy L-suggestion Lint: Improving, adding or fixing lint suggestions
Projects
None yet
Development

No branches or pull requests

7 participants