New lint: unnecessary_semicolon (#14032)

This lint detects and removes the unnecessary semicolon after a `match`
or `if` statement returning `()`. It seems to be quite a common
"mistake", given the number of hits (88) we had in the Clippy sources
themselves.

The lint doesn't bother about loops, as `rustfmt` already removes the
extra semicolon. It doesn't handle blocks either, as an extra block
level, followed or not by a semicolon, is likely intentional.

I propose to put the lint in `pedantic`, as putting it in `style` seems
quite hazardous given the number of hits.

Note: there exists a `redundant-semicolon` lint in the compiler, but it
is an early lint and cannot check that the expression evaluates to `()`,
so it ignores the cases we're handling here.

----

changelog: [`unnecessary_semicolon`]: new lint
This commit is contained in:
llogiq
2025-01-20 17:39:37 +00:00
committed by GitHub
83 changed files with 236 additions and 88 deletions
+1
View File
@@ -6174,6 +6174,7 @@ Released 2018-09-13
[`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment
[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
[`unnecessary_semicolon`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_semicolon
[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
[`unnecessary_struct_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_struct_initialization
[`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned
+1 -1
View File
@@ -62,7 +62,7 @@ fn check_and_get_rustc_dir(rustc_path: &str) -> Result<PathBuf, ()> {
eprintln!("error: unable to get the absolute path of rustc ({err})");
return Err(());
},
};
}
}
let path = path.join("compiler");
+1 -1
View File
@@ -842,7 +842,7 @@ fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
Ok(file) => drop(file),
Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false,
Err(e) => panic_file(e, new_name, "create"),
};
}
match fs::rename(old_name, new_name) {
Ok(()) => true,
Err(e) => {
+1 -1
View File
@@ -257,7 +257,7 @@ fn build_sugg<'tcx>(
// The receiver may have been a value type, so we need to add an `&` to
// be sure the argument to clone_from will be a reference.
arg_sugg = arg_sugg.addr();
};
}
format!("{receiver_sugg}.clone_from({arg_sugg})")
},
@@ -60,7 +60,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item_span: Span, attrs: &[Attribute])
}
outer_attr_kind.insert(kind);
},
};
}
}
}
+1 -1
View File
@@ -84,6 +84,6 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
diag
.note("`usize` and `isize` may be as small as 16 bits on some platforms")
.note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types");
};
}
});
}
+2 -2
View File
@@ -205,7 +205,7 @@ fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
// - uncertain if there are any uncertain values (because they could be negative or positive),
Sign::Uncertain => return Sign::Uncertain,
Sign::ZeroOrPositive => (),
};
}
}
// A mul/div is:
@@ -236,7 +236,7 @@ fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
// - uncertain if there are any uncertain values (because they could be negative or positive),
Sign::Uncertain => return Sign::Uncertain,
Sign::ZeroOrPositive => positive_count += 1,
};
}
}
// A sum is:
+1 -1
View File
@@ -273,7 +273,7 @@ fn get_types_from_cast<'a>(
},
_ => {},
}
};
}
None
}
+1
View File
@@ -757,6 +757,7 @@
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
crate::unnecessary_semicolon::UNNECESSARY_SEMICOLON_INFO,
crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO,
crate::unnecessary_wraps::UNNECESSARY_WRAPS_INFO,
crate::unneeded_struct_pattern::UNNEEDED_STRUCT_PATTERN_INFO,
@@ -80,6 +80,6 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tc
String::new(),
Applicability::MachineApplicable,
);
};
}
}
}
+1 -1
View File
@@ -331,7 +331,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
deref_count += 1;
},
None => break None,
};
}
};
let use_node = use_cx.use_node(cx);
+1 -1
View File
@@ -70,7 +70,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
var.span,
"C-like enum variant discriminant is not portable to 32-bit targets",
);
};
}
}
}
}
@@ -183,7 +183,7 @@ fn emit_lint(&self) {
.collect()
};
self.emit_sugg(spans, msg, help);
};
}
}
}
@@ -45,7 +45,7 @@ pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body:
for param in generics.params {
if param.is_impl_trait() {
report(cx, param, generics);
};
}
}
}
}
+1 -1
View File
@@ -160,7 +160,7 @@ fn check_needless_must_use(
&& !is_must_use_ty(cx, future_ty)
{
return;
};
}
}
span_lint_and_help(
+1 -1
View File
@@ -120,7 +120,7 @@ fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, B
let ecx = ConstEvalCtxt::new(cx);
if let Some(Constant::Int(c)) = ecx.eval(r) {
return Some((c, op.node, l));
};
}
if let Some(Constant::Int(c)) = ecx.eval(l) {
return Some((c, invert_op(op.node)?, r));
}
+1 -1
View File
@@ -350,7 +350,7 @@ fn check_with_condition<'tcx>(
if cx.typeck_results().expr_ty(cond_left).is_signed() {
} else {
print_lint_and_sugg(cx, var_name, expr);
};
}
}
},
ExprKind::Path(QPath::TypeRelative(_, name)) => {
+1 -1
View File
@@ -65,6 +65,6 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) {
expr.span,
"iteration over unordered hash-based type",
);
};
}
}
}
+1 -1
View File
@@ -41,6 +41,6 @@ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
Some(ty.span.with_lo(local.pat.span.hi())),
"remove the explicit type `_` declaration",
);
};
}
}
}
+2
View File
@@ -372,6 +372,7 @@
mod unnecessary_map_on_constructor;
mod unnecessary_owned_empty_strings;
mod unnecessary_self_imports;
mod unnecessary_semicolon;
mod unnecessary_struct_initialization;
mod unnecessary_wraps;
mod unneeded_struct_pattern;
@@ -972,5 +973,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern));
store.register_late_pass(|_| Box::new(unnecessary_semicolon::UnnecessarySemicolon));
// add lints here, do not remove this comment, it's used in `new_lint`
}
+1 -1
View File
@@ -784,7 +784,7 @@ fn report_elidable_lifetimes(
|diag| {
if !include_suggestions {
return;
};
}
if let Some(suggestions) = elision_suggestions(cx, generics, elidable_lts, usages) {
diag.multipart_suggestion("elide the lifetimes", suggestions, Applicability::MachineApplicable);
+1 -1
View File
@@ -251,7 +251,7 @@ fn check_lit(&self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
);
if !consistent {
return Err(WarningType::InconsistentDigitGrouping);
};
}
}
Ok(())
+2 -2
View File
@@ -106,7 +106,7 @@ pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'t
emit_manual_let_else(cx, stmt.span, match_expr, &ident_map, pat_arm.pat, diverging_arm.body);
},
}
};
}
}
}
@@ -295,7 +295,7 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo
PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..)
) {
return;
};
}
let ty = typeck_results.pat_ty(pat);
// Option and Result are allowed, everything else isn't.
if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) {
+1 -1
View File
@@ -85,7 +85,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
}
},
_ => return,
};
}
let mut app = Applicability::MachineApplicable;
let rem_of = snippet_with_context(cx, rem2_lhs.span, ctxt, "_", &mut app).0;
+1 -1
View File
@@ -86,7 +86,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let target_res = cx.qpath_res(target_path, target_arg.hir_id);
if target_res == Res::Err {
return;
};
}
if let Res::Local(hir_id) = target_res
&& let Some(used_mutably) = mutated_variables(then, cx)
+4 -4
View File
@@ -34,7 +34,7 @@ fn get_cond_expr<'tcx>(
needs_negated: is_none_expr(cx, then_expr), /* if the `then_expr` resolves to `None`, need to negate the
* cond */
});
};
}
None
}
@@ -45,7 +45,7 @@ fn peels_blocks_incl_unsafe_opt<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>>
if block.stmts.is_empty() {
return block.expr;
}
};
}
None
}
@@ -68,14 +68,14 @@ fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr:
&& is_res_lang_ctor(cx, path_res(cx, callee), OptionSome)
&& path_to_local_id(arg, target);
}
};
}
false
}
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) {
return is_res_lang_ctor(cx, path_res(cx, inner_expr), OptionNone);
};
}
false
}
+1 -1
View File
@@ -109,7 +109,7 @@ pub(super) fn check_with<'tcx, F>(
}
},
None => return None,
};
}
let mut app = Applicability::MachineApplicable;
@@ -117,7 +117,7 @@ fn find_matches_sugg<'a, 'b, I>(
if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
ex_new = ex_inner;
}
};
}
span_lint_and_sugg(
cx,
MATCH_LIKE_MATCHES_MACRO,
+1 -1
View File
@@ -170,7 +170,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
);
});
},
};
}
}
enum CommonPrefixSearcher<'a> {
+1 -1
View File
@@ -46,7 +46,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine
err_ty = ty;
} else {
return;
};
}
span_lint_and_then(
cx,
+1 -1
View File
@@ -13,7 +13,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arms: &[Arm<'_>]) {
&& has_non_exhaustive_attr(cx.tcx, *adt_def)
{
return;
};
}
for arm in arms {
if let PatKind::Or(fields) = arm.pat.kind {
// look for multiple fields in this arm that contains at least one Wild pattern
+1 -1
View File
@@ -62,5 +62,5 @@ pub(super) fn check<'tcx>(
),
applicability,
);
};
}
}
@@ -32,5 +32,5 @@ pub(super) fn check<'tcx>(
),
applicability,
);
};
}
}
+1 -1
View File
@@ -46,5 +46,5 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E
format!("{receiver}.as_bytes().get({n}).copied()"),
applicability,
);
};
}
}
@@ -32,7 +32,7 @@ pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span,
// &T where T: Copy
ty::Ref(_, ty, _) if is_copy(cx, *ty) => {},
_ => return,
};
}
span_lint_and_sugg(
cx,
CLONED_INSTEAD_OF_COPIED,
+1 -1
View File
@@ -37,7 +37,7 @@ pub(super) fn check(
"expect_err".to_string(),
Applicability::MachineApplicable,
);
};
}
}
/// Given a `Result<T, E>` type, return its data (`T`).
+1 -1
View File
@@ -58,7 +58,7 @@ fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
if ty.is_str() && can_be_static_str(cx, arg) {
return false;
}
};
}
true
}
+1 -1
View File
@@ -25,5 +25,5 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
"into_iter()".to_string(),
Applicability::MaybeIncorrect,
);
};
}
}
@@ -37,7 +37,7 @@ pub(super) fn check(
let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
} else {
let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);
};
}
span_lint_and_sugg(
cx,
@@ -87,7 +87,7 @@ fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Ex
// skip lint
return true;
}
};
}
// the lint should not be executed if no violation happens
let snippet = if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind
+2 -2
View File
@@ -214,7 +214,7 @@ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
);
},
);
};
}
if let StmtKind::Semi(expr) = stmt.kind
&& let ExprKind::Binary(ref binop, a, b) = expr.kind
&& (binop.node == BinOpKind::And || binop.node == BinOpKind::Or)
@@ -236,7 +236,7 @@ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
);
},
);
};
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
@@ -66,7 +66,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
}) => impl_params.push((path.segments[0].ident.to_string(), path.span)),
GenericArg::Type(_) => return,
_ => (),
};
}
}
// find the type that the Impl is for
+1 -1
View File
@@ -220,7 +220,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
| hir::ItemKind::GlobalAsm(..)
| hir::ItemKind::Impl { .. }
| hir::ItemKind::Use(..) => note_prev_span_then_ret!(self.prev_span, it.span),
};
}
let (article, desc) = cx.tcx.article_and_description(it.owner_id.to_def_id());
+1 -1
View File
@@ -135,7 +135,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
| hir::ItemKind::ForeignMod { .. }
| hir::ItemKind::Impl { .. }
| hir::ItemKind::Use(..) => {},
};
}
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+2 -2
View File
@@ -56,10 +56,10 @@ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::Assign(target, source, _) = &expr.kind {
if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(target).kind {
span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively");
};
}
if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(source).kind {
span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively");
}
};
}
}
}
@@ -171,7 +171,7 @@ fn collect_unsafe_exprs<'tcx>(
},
_ => {},
};
}
Continue::<(), _>(Descend::Yes)
});
+1 -1
View File
@@ -91,7 +91,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
ty::Uint(t) if t != UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, msg),
ty::Int(t) if t != IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, msg),
_ => span_lint(cx, MUTEX_ATOMIC, expr.span, msg),
};
}
}
}
}
+1 -1
View File
@@ -336,7 +336,7 @@ fn check<'tcx>(
);
},
_ => {},
};
}
Some(())
}
@@ -73,7 +73,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
}
},
_ => {},
};
}
}
}
@@ -104,7 +104,7 @@ fn has_specific_allowed_type_and_operation<'tcx>(
if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
return false;
};
}
let int_type = substs.type_at(0);
let unsigned_int_types = [
@@ -214,7 +214,7 @@ fn manage_bin_ops<'tcx>(
| hir::BinOpKind::Sub
) {
return;
};
}
let (mut actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
let (mut actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
actual_lhs = expr_or_init(cx, actual_lhs);
+1 -1
View File
@@ -104,7 +104,7 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool)
} else {
expr_snip = arg_snip.to_string();
eq_impl = without_deref;
};
}
let span;
let hint;
@@ -127,7 +127,7 @@ pub(super) fn check<'tcx>(
None,
note,
);
};
}
}
}
@@ -49,5 +49,5 @@ macro_rules! lint_double_comparison {
lint_double_comparison!(==);
},
_ => (),
};
}
}
+1 -1
View File
@@ -120,7 +120,7 @@ fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let ty::Array(arr_ty, _) = value {
return matches!(arr_ty.kind(), ty::Float(_));
};
}
matches!(value, ty::Float(_))
}
@@ -30,7 +30,7 @@ pub(super) fn check<'tcx>(
} else {
check_non_const_operands(cx, e, lhs);
}
};
}
}
fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+1 -1
View File
@@ -21,6 +21,6 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, right:
"any number modulo -1 will panic/overflow or result in 0",
);
}
};
}
}
}
+1 -1
View File
@@ -53,6 +53,6 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
);
}
}
};
}
}
}
+1 -1
View File
@@ -208,7 +208,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
// avoid clippy::double_parens
if !is_in_fn_call_arg {
hint = hint.maybe_par();
};
}
diag.span_suggestion(full_expr.span, "try doing something like", hint, applicability);
}
@@ -215,6 +215,6 @@ fn check_local<'tcx>(&mut self, cx: &LateContext<'tcx>, local: &'tcx rustc_hir::
},
_ => (),
}
};
}
}
}
+3 -3
View File
@@ -97,7 +97,7 @@ fn get_pointee_ty_and_count_expr<'tcx>(
&& let Some(pointee_ty) = cx.typeck_results().node_args(func.hir_id).types().next()
{
return Some((pointee_ty, count));
};
}
if let ExprKind::MethodCall(method_path, ptr_self, [.., count], _) = expr.kind
// Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods
&& let method_ident = method_path.ident.as_str()
@@ -108,7 +108,7 @@ fn get_pointee_ty_and_count_expr<'tcx>(
cx.typeck_results().expr_ty(ptr_self).kind()
{
return Some((*pointee_ty, count));
};
}
None
}
@@ -130,6 +130,6 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
&& pointee_ty == ty_used_for_size_of
{
span_lint_and_help(cx, SIZE_OF_IN_ELEMENT_COUNT, count_expr.span, LINT_MSG, None, HELP_MSG);
};
}
}
}
@@ -191,7 +191,7 @@ fn lint_initialization<'tcx>(
InitializationType::Extend(e) | InitializationType::Resize(e) => {
Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization");
},
};
}
}
fn emit_lint(cx: &LateContext<'_>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &'static str) {
+1 -1
View File
@@ -296,7 +296,7 @@ fn check_xor_swap<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
{
let span = s1.span.to(s3.span);
generate_swap_warning(block, cx, lhs0, rhs0, rhs1, rhs2, span, true);
};
}
}
}
@@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
return false;
};
}
let int_ty = substs.type_at(0);
if from_ty != int_ty {
+1 -1
View File
@@ -119,7 +119,7 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &
&& let LitKind::Int(val, _) = lit.node
{
return (val == i as u128).then_some(lhs);
};
}
None
})
+1 -1
View File
@@ -71,7 +71,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m
Applicability::Unspecified,
);
return true;
};
}
false
},
_ => false,
+63
View File
@@ -0,0 +1,63 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, MatchSource, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for the presence of a semicolon at the end of
/// a `match` or `if` statement evaluating to `()`.
///
/// ### Why is this bad?
/// The semicolon is not needed, and may be removed to
/// avoid confusion and visual clutter.
///
/// ### Example
/// ```no_run
/// # let a: u32 = 42;
/// if a > 10 {
/// println!("a is greater than 10");
/// };
/// ```
/// Use instead:
/// ```no_run
/// # let a: u32 = 42;
/// if a > 10 {
/// println!("a is greater than 10");
/// }
/// ```
#[clippy::version = "1.86.0"]
pub UNNECESSARY_SEMICOLON,
pedantic,
"unnecessary semicolon after expression returning `()`"
}
declare_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]);
impl LateLintPass<'_> for UnnecessarySemicolon {
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
// rustfmt already takes care of removing semicolons at the end
// of loops.
if let StmtKind::Semi(expr) = stmt.kind
&& !stmt.span.from_expansion()
&& !expr.span.from_expansion()
&& matches!(
expr.kind,
ExprKind::If(..) | ExprKind::Match(_, _, MatchSource::Normal | MatchSource::Postfix)
)
&& cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit
{
let semi_span = expr.span.shrink_to_hi().to(stmt.span.shrink_to_hi());
span_lint_and_sugg(
cx,
UNNECESSARY_SEMICOLON,
semi_span,
"unnecessary semicolon",
"remove",
String::new(),
Applicability::MachineApplicable,
);
}
}
}
+1 -1
View File
@@ -182,7 +182,7 @@ fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
emit_lint(cx, expr.span, expr.hir_id, op, &[]);
},
_ => {},
};
}
}
fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option<IoOp> {
@@ -69,6 +69,6 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
),
applicability,
);
};
}
}
}
+1 -1
View File
@@ -69,7 +69,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
};
if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) {
return;
};
}
// the parent callsite of this `vec!` expression, or span to the borrowed one such as `&vec!`
let callsite = expr.span.parent_callsite().unwrap_or(expr.span);
+1 -1
View File
@@ -475,7 +475,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -
Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
_ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
};
};
}
},
ExprKind::Path(QPath::Resolved(_, path))
if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
+2 -2
View File
@@ -815,7 +815,7 @@ fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'
e = ep;
},
_ => break e,
};
}
};
result.reverse();
(result, root)
@@ -2045,7 +2045,7 @@ pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'t
{
return Some(expr);
}
};
}
None
}
+1 -1
View File
@@ -251,7 +251,7 @@ pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
// This has no argument
if name == "panic_cold_explicit" {
return Some(Self::Empty);
};
}
let [arg, rest @ ..] = args else {
return None;
+1 -1
View File
@@ -125,7 +125,7 @@ pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Opti
integer = &digits[..exp_start];
} else {
fraction = Some(&digits[integer.len() + 1..exp_start]);
};
}
exponent = Some((&digits[exp_start..=i], &digits[i + 1..]));
break;
},
+1 -1
View File
@@ -118,7 +118,7 @@ fn contains_ty_adt_constructor_opaque_inner<'tcx>(
if contains_ty_adt_constructor_opaque_inner(cx, ty, needle, seen) {
return true;
}
};
}
},
_ => (),
}
+1 -1
View File
@@ -55,7 +55,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
&& let Some(self_ty_def_id) = adt_def_id(self_ty(cx, method_def_id))
{
receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id);
};
}
let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false);
let rhs = if type_is_inferable_from_arguments(cx, expr) {
meet(
+1 -1
View File
@@ -107,7 +107,7 @@ pub fn new() -> Self {
} else {
std::thread::available_parallelism().map_or(1, NonZero::get)
};
};
}
for lint_name in &mut config.lint_filter {
*lint_name = format!(
+2 -2
View File
@@ -145,7 +145,7 @@ fn run_clippy_lints(
assert_eq!(status.code(), Some(0));
return Vec::new();
};
}
if !config.fix {
cmd.arg("--message-format=json");
@@ -313,7 +313,7 @@ fn lintcheck(config: LintcheckConfig) {
filter
})
.collect_into(&mut lint_level_args);
};
}
let crates: Vec<Crate> = crates
.into_iter()
+1 -1
View File
@@ -223,7 +223,7 @@ pub fn main() {
if !has_sysroot_arg(args) {
args.extend(vec!["--sysroot".into(), sys_root]);
}
};
}
};
// make "clippy-driver --rustc" work like a subcommand that passes further args to "rustc"
+1 -1
View File
@@ -60,7 +60,7 @@ fn explore_directory(dir: &Path) -> Vec<String> {
}
},
_ => {},
};
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
#![warn(clippy::unnecessary_semicolon)]
#![feature(postfix_match)]
fn no_lint(mut x: u32) -> Option<u32> {
Some(())?;
{
let y = 3;
dbg!(x + y)
};
{
let (mut a, mut b) = (10, 20);
(a, b) = (b + 1, a + 1);
}
Some(0)
}
fn main() {
let mut a = 3;
if a == 2 {
println!("This is weird");
}
//~^ ERROR: unnecessary semicolon
a.match {
3 => println!("three"),
_ => println!("not three"),
}
//~^ ERROR: unnecessary semicolon
}
+32
View File
@@ -0,0 +1,32 @@
#![warn(clippy::unnecessary_semicolon)]
#![feature(postfix_match)]
fn no_lint(mut x: u32) -> Option<u32> {
Some(())?;
{
let y = 3;
dbg!(x + y)
};
{
let (mut a, mut b) = (10, 20);
(a, b) = (b + 1, a + 1);
}
Some(0)
}
fn main() {
let mut a = 3;
if a == 2 {
println!("This is weird");
};
//~^ ERROR: unnecessary semicolon
a.match {
3 => println!("three"),
_ => println!("not three"),
};
//~^ ERROR: unnecessary semicolon
}
+17
View File
@@ -0,0 +1,17 @@
error: unnecessary semicolon
--> tests/ui/unnecessary_semicolon.rs:24:6
|
LL | };
| ^ help: remove
|
= note: `-D clippy::unnecessary-semicolon` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_semicolon)]`
error: unnecessary semicolon
--> tests/ui/unnecessary_semicolon.rs:30:6
|
LL | };
| ^ help: remove
error: aborting due to 2 previous errors
+1 -1
View File
@@ -88,5 +88,5 @@ fn check_that_clippy_has_the_same_major_version_as_rustc() {
_ => {
panic!("Failed to parse rustc version: {vsplit:?}");
},
};
}
}