Skip to content

Fix collapsible_else_if FP on conditionally compiled stmt #14906

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6529,6 +6529,7 @@ Released 2018-09-13
[`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability
[`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
[`lint-commented-code`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-code
[`lint-commented-else-if`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-else-if
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold
[`matches-for-let-else`]: https://doc.rust-lang.org/clippy/lint_configuration.html#matches-for-let-else
[`max-fn-params-bools`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-fn-params-bools
Expand Down
11 changes: 11 additions & 0 deletions book/src/lint_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,17 @@ that would be collapsed.
* [`collapsible_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if)


## `lint-commented-else-if`
Whether collapsible `else if` chains are linted if they contain comments inside the parts
that would be collapsed.

**Default Value:** `false`

---
**Affected lints:**
* [`collapsible_else_if`](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if)


## `literal-representation-threshold`
The lower bound for linting decimal literals

Expand Down
4 changes: 4 additions & 0 deletions clippy_config/src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,10 @@ define_Conf! {
/// that would be collapsed.
#[lints(collapsible_if)]
lint_commented_code: bool = false,
/// Whether collapsible `else if` chains are linted if they contain comments inside the parts
/// that would be collapsed.
#[lints(collapsible_else_if)]
lint_commented_else_if: bool = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should reuse the same option, lint_commented_code, there is no good reason to separate them.

/// Whether to suggest reordering constructor fields when initializers are present.
/// DEPRECATED CONFIGURATION: lint-inconsistent-struct-field-initializers
///
Expand Down
35 changes: 21 additions & 14 deletions clippy_lints/src/collapsible_if.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability};
use clippy_utils::span_contains_non_whitespace;
use rustc_ast::BinOpKind;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::{BytePos, Span};

declare_clippy_lint! {
/// ### What it does
Expand Down Expand Up @@ -80,22 +81,24 @@ declare_clippy_lint! {
pub struct CollapsibleIf {
msrv: Msrv,
lint_commented_code: bool,
lint_commented_else_if: bool,
}

impl CollapsibleIf {
pub fn new(conf: &'static Conf) -> Self {
Self {
msrv: conf.msrv,
lint_commented_code: conf.lint_commented_code,
lint_commented_else_if: conf.lint_commented_else_if,
}
}

fn check_collapsible_else_if(cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
if !block_starts_with_comment(cx, else_block)
&& let Some(else_) = expr_block(else_block)
fn check_collapsible_else_if(&self, cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
if let Some(else_) = expr_block(else_block)
&& cx.tcx.hir_attrs(else_.hir_id).is_empty()
&& !else_.span.from_expansion()
&& let ExprKind::If(..) = else_.kind
&& !block_starts_with_significant_tokens(cx, else_block, else_, self.lint_commented_else_if)
{
// Prevent "elseif"
// Check that the "else" is followed by whitespace
Expand Down Expand Up @@ -130,7 +133,7 @@ impl CollapsibleIf {
&& self.eligible_condition(cx, check_inner)
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
&& !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code)
{
span_lint_and_then(
cx,
Expand All @@ -141,7 +144,7 @@ impl CollapsibleIf {
let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span();
let then_closing_bracket = {
let end = then.span.shrink_to_hi();
end.with_lo(end.lo() - rustc_span::BytePos(1))
end.with_lo(end.lo() - BytePos(1))
.with_leading_whitespace(cx)
.into_span()
};
Expand Down Expand Up @@ -179,7 +182,7 @@ impl LateLintPass<'_> for CollapsibleIf {
if let Some(else_) = else_
&& let ExprKind::Block(else_, None) = else_.kind
{
Self::check_collapsible_else_if(cx, then.span, else_);
self.check_collapsible_else_if(cx, then.span, else_);
} else if else_.is_none()
&& self.eligible_condition(cx, cond)
&& let ExprKind::Block(then, None) = then.kind
Expand All @@ -190,12 +193,16 @@ impl LateLintPass<'_> for CollapsibleIf {
}
}

fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
// We trim all opening braces and whitespaces and then check if the next string is a comment.
let trimmed_block_text = snippet_block(cx, block.span, "..", None)
.trim_start_matches(|c: char| c.is_whitespace() || c == '{')
.to_owned();
trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
// Check that nothing significant can be found but whitespaces between the initial `{` of `block`
// and the beginning of `stop_at`.
fn block_starts_with_significant_tokens(
cx: &LateContext<'_>,
block: &Block<'_>,
stop_at: &Expr<'_>,
lint_commented_code: bool,
) -> bool {
let span = block.span.split_at(1).1.until(stop_at.span);
span_contains_non_whitespace(cx, span, lint_commented_code)
}

/// If `block` is a block with either one expression or a statement containing an expression,
Expand Down
15 changes: 14 additions & 1 deletion clippy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{Ident, Symbol, kw};
use rustc_span::{InnerSpan, Span};
use source::walk_span_to_context;
use source::{SpanRangeExt, walk_span_to_context};
use visitors::{Visitable, for_each_unconsumed_temporary};

use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
Expand Down Expand Up @@ -2788,6 +2788,19 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
});
}

/// Checks whether a given span has any significant token. A significant token is a non-whitespace
/// token, including comments unless `skip_comments` is set.
/// This is useful to determine if there are any actual code tokens in the span that are omitted in
/// the late pass, such as platform-specific code.
pub fn span_contains_non_whitespace(cx: &impl source::HasSession, span: Span, skip_comments: bool) -> bool {
matches!(span.get_source_text(cx), Some(snippet) if tokenize_with_text(&snippet).any(|(token, _, _)|
match token {
TokenKind::Whitespace => false,
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => !skip_comments,
_ => true,
}
))
}
/// Returns all the comments a given span contains
///
/// Comments are returned wrapped with their relevant delimiters
Expand Down
1 change: 1 addition & 0 deletions tests/ui-toml/collapsible_if/clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
lint-commented-code = true
lint-commented-else-if = true
35 changes: 35 additions & 0 deletions tests/ui-toml/collapsible_if/collapsible_else_if.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![allow(clippy::eq_op, clippy::nonminimal_bool)]

#[rustfmt::skip]
#[warn(clippy::collapsible_if)]
fn main() {
let (x, y) = ("hello", "world");

if x == "hello" {
todo!()
} else if y == "world" {
println!("Hello world!");
}
//~^^^^^^ collapsible_else_if

if x == "hello" {
todo!()
} else if y == "world" {
println!("Hello world!");
}
//~^^^^^ collapsible_else_if

if x == "hello" {
todo!()
} else if y == "world" {
println!("Hello world!");
}
//~^^^^^^ collapsible_else_if

if x == "hello" {
todo!()
} else if y == "world" {
println!("Hello world!");
}
//~^^^^^ collapsible_else_if
}
45 changes: 45 additions & 0 deletions tests/ui-toml/collapsible_if/collapsible_else_if.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#![allow(clippy::eq_op, clippy::nonminimal_bool)]

#[rustfmt::skip]
#[warn(clippy::collapsible_if)]
fn main() {
let (x, y) = ("hello", "world");

if x == "hello" {
todo!()
} else {
// Comment must be kept
if y == "world" {
println!("Hello world!");
}
}
//~^^^^^^ collapsible_else_if

if x == "hello" {
todo!()
} else { // Inner comment
if y == "world" {
println!("Hello world!");
}
}
//~^^^^^ collapsible_else_if

if x == "hello" {
todo!()
} else {
/* Inner comment */
if y == "world" {
println!("Hello world!");
}
}
//~^^^^^^ collapsible_else_if

if x == "hello" {
todo!()
} else { /* Inner comment */
if y == "world" {
println!("Hello world!");
}
}
//~^^^^^ collapsible_else_if
}
78 changes: 78 additions & 0 deletions tests/ui-toml/collapsible_if/collapsible_else_if.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
error: this `else { if .. }` block can be collapsed
--> tests/ui-toml/collapsible_if/collapsible_else_if.rs:10:12
|
LL | } else {
| ____________^
LL | | // Comment must be kept
LL | | if y == "world" {
LL | | println!("Hello world!");
LL | | }
LL | | }
| |_____^
|
= note: `-D clippy::collapsible-else-if` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::collapsible_else_if)]`
help: collapse nested if block
|
LL ~ } else if y == "world" {
LL + println!("Hello world!");
LL + }
|

error: this `else { if .. }` block can be collapsed
--> tests/ui-toml/collapsible_if/collapsible_else_if.rs:20:12
|
LL | } else { // Inner comment
| ____________^
LL | | if y == "world" {
LL | | println!("Hello world!");
LL | | }
LL | | }
| |_____^
|
help: collapse nested if block
|
LL ~ } else if y == "world" {
LL + println!("Hello world!");
LL + }
|

error: this `else { if .. }` block can be collapsed
--> tests/ui-toml/collapsible_if/collapsible_else_if.rs:29:12
|
LL | } else {
| ____________^
LL | | /* Inner comment */
LL | | if y == "world" {
LL | | println!("Hello world!");
LL | | }
LL | | }
| |_____^
|
help: collapse nested if block
|
LL ~ } else if y == "world" {
LL + println!("Hello world!");
LL + }
|

error: this `else { if .. }` block can be collapsed
--> tests/ui-toml/collapsible_if/collapsible_else_if.rs:39:12
|
LL | } else { /* Inner comment */
| ____________^
LL | | if y == "world" {
LL | | println!("Hello world!");
LL | | }
LL | | }
| |_____^
|
help: collapse nested if block
|
LL ~ } else if y == "world" {
LL + println!("Hello world!");
LL + }
|

error: aborting due to 4 previous errors

3 changes: 3 additions & 0 deletions tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
ignore-interior-mutability
large-error-threshold
lint-commented-code
lint-commented-else-if
literal-representation-threshold
matches-for-let-else
max-fn-params-bools
Expand Down Expand Up @@ -144,6 +145,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
ignore-interior-mutability
large-error-threshold
lint-commented-code
lint-commented-else-if
literal-representation-threshold
matches-for-let-else
max-fn-params-bools
Expand Down Expand Up @@ -238,6 +240,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
ignore-interior-mutability
large-error-threshold
lint-commented-code
lint-commented-else-if
literal-representation-threshold
matches-for-let-else
max-fn-params-bools
Expand Down
18 changes: 18 additions & 0 deletions tests/ui/collapsible_else_if.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,21 @@ fn issue_7318() {
}else if false {}
//~^^^ collapsible_else_if
}

fn issue14799() {
use std::ops::ControlFlow;

let c: ControlFlow<_, ()> = ControlFlow::Break(Some(42));
if let ControlFlow::Break(Some(_)) = c {
todo!();
} else {
#[cfg(target_os = "freebsd")]
todo!();

if let ControlFlow::Break(None) = c {
todo!();
} else {
todo!();
}
}
}
18 changes: 18 additions & 0 deletions tests/ui/collapsible_else_if.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,21 @@ fn issue_7318() {
}
//~^^^ collapsible_else_if
}

fn issue14799() {
use std::ops::ControlFlow;

let c: ControlFlow<_, ()> = ControlFlow::Break(Some(42));
if let ControlFlow::Break(Some(_)) = c {
todo!();
} else {
#[cfg(target_os = "freebsd")]
todo!();

if let ControlFlow::Break(None) = c {
todo!();
} else {
todo!();
}
}
}
Loading