Skip to content

Warn about C-style octal literals #131309

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 1 commit 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
4 changes: 4 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,10 @@ lint_invalid_reference_casting_note_book = for more information, visit <https://

lint_invalid_reference_casting_note_ty_has_interior_mutability = even for types with interior mutability, the only legal way to obtain a mutable pointer from a shared reference is through `UnsafeCell::get`

lint_leading_zeros_in_decimal_literals = this is a decimal constant
.suggestion_remove_zeros = if you meant to use a decimal constant, remove leading zeros to avoid confusion
.suggestion_prefix_octal = if you meant to use an octal constant, prefix it with `0o` instead

lint_legacy_derive_helpers = derive helper attribute is used before it is introduced
.label = the attribute is introduced here

Expand Down
73 changes: 72 additions & 1 deletion compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_ast::visit::{FnCtxt, FnKind};
use rustc_ast::{self as ast, *};
use rustc_ast_pretty::pprust::expr_to_string;
use rustc_data_structures::packed::Pu128;
use rustc_errors::{Applicability, LintDiagnostic};
use rustc_feature::{AttributeGate, BuiltinAttribute, GateIssue, Stability, deprecated_attributes};
use rustc_hir as hir;
Expand Down Expand Up @@ -58,7 +59,7 @@ use crate::lints::{
BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasBounds,
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub,
BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment,
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel, LeadingZeros,
};
use crate::nonstandard_style::{MethodLateContext, method_context};
use crate::{
Expand Down Expand Up @@ -3140,3 +3141,73 @@ impl EarlyLintPass for SpecialModuleName {
}
}
}

declare_lint! {
/// The `leading_zeros_in_decimal_literals` lint
/// detects decimal integral literals with leading zeros.
///
/// ### Example
///
/// ```rust,no_run
/// fn is_executable(unix_mode: u32) -> bool {
/// unix_mode & 0111 != 0
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
/// In some languages (including the infamous C language and most of its family),
/// a leading zero marks an octal constant. In Rust however, a `0o` prefix is used instead.
/// Thus, a leading zero can be confusing for both the writer and a reader.
///
/// In Rust:
/// ```rust,no_run
/// fn main() {
/// let a = 0123;
/// println!("{}", a);
/// }
/// ```
///
/// prints `123`, while in C:
///
/// ```c
/// #include <stdio.h>
///
/// int main() {
/// int a = 0123;
/// printf("%d\n", a);
/// }
/// ```
///
/// prints `83` (as `83 == 0o123` while `123 == 0o173`).
pub LEADING_ZEROS_IN_DECIMAL_LITERALS,
Warn,
"leading `0` in decimal integer literals",
}

declare_lint_pass!(LeadingZerosInDecimals => [LEADING_ZEROS_IN_DECIMAL_LITERALS]);

impl EarlyLintPass for LeadingZerosInDecimals {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let ExprKind::Lit(literal) = expr.kind
&& let Ok(LitKind::Int(Pu128(10..), _)) = LitKind::from_token_lit(literal)
&& let s = literal.symbol.as_str()
&& let Some(tail) = s.strip_prefix('0')
&& !tail.starts_with(['b', 'o', 'x'])
&& let nonzero_digits = tail.trim_start_matches(['0', '_'])
&& !nonzero_digits.contains(['8', '9'])
{
let lit_span = expr.span;
let zeros_offset = s.len() - nonzero_digits.len();
cx.emit_span_lint(
LEADING_ZEROS_IN_DECIMAL_LITERALS,
lit_span,
LeadingZeros {
remove_zeros: lit_span.with_hi(lit_span.lo() + BytePos(zeros_offset as u32)),
prefix_octal: lit_span.with_hi(lit_span.lo() + BytePos(1)),
},
);
}
}
}
1 change: 1 addition & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ early_lint_methods!(
Expr2024: Expr2024,
Precedence: Precedence,
DoubleNegations: DoubleNegations,
LeadingZerosInDecimals: LeadingZerosInDecimals,
]
]
);
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3156,3 +3156,12 @@ pub(crate) struct ReservedMultihash {
#[suggestion(code = " ", applicability = "machine-applicable")]
pub suggestion: Span,
}

#[derive(LintDiagnostic)]
#[diag(lint_leading_zeros_in_decimal_literals)]
pub(crate) struct LeadingZeros {
#[suggestion(lint_suggestion_remove_zeros, code = "", applicability = "maybe-incorrect")]
pub remove_zeros: Span,
#[suggestion(lint_suggestion_prefix_octal, code = "0o", applicability = "maybe-incorrect")]
pub prefix_octal: Span,
}
2 changes: 2 additions & 0 deletions library/coretests/tests/time.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg_attr(not(bootstrap), allow(leading_zeros_in_decimal_literals))]

use core::time::Duration;

#[test]
Expand Down
1 change: 0 additions & 1 deletion src/tools/clippy/clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::misc_early::UNNEEDED_FIELD_PATTERN_INFO,
crate::misc_early::UNNEEDED_WILDCARD_PATTERN_INFO,
crate::misc_early::UNSEPARATED_LITERAL_SUFFIX_INFO,
crate::misc_early::ZERO_PREFIXED_LITERAL_INFO,
crate::mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER_INFO,
crate::missing_assert_message::MISSING_ASSERT_MESSAGE_INFO,
crate::missing_asserts_for_indexing::MISSING_ASSERTS_FOR_INDEXING_INFO,
Expand Down
2 changes: 2 additions & 0 deletions src/tools/clippy/clippy_lints/src/deprecated_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,7 @@ declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
("clippy::vtable_address_comparisons", "ambiguous_wide_pointer_comparisons"),
#[clippy::version = ""]
("clippy::reverse_range_loop", "clippy::reversed_empty_ranges"),
#[clippy::version = "CURRENT_RUSTC_VERSION"]
("clippy::zero_prefixed_literal", "leading_zeros_in_decimal_literals"),
// end renamed lints. used by `cargo dev rename_lint`
]}
47 changes: 1 addition & 46 deletions src/tools/clippy/clippy_lints/src/misc_early/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ mod redundant_at_rest_pattern;
mod redundant_pattern;
mod unneeded_field_pattern;
mod unneeded_wildcard_pattern;
mod zero_prefixed_literal;

use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet_opt;
Expand Down Expand Up @@ -167,45 +166,6 @@ declare_clippy_lint! {
"literals whose suffix is separated by an underscore"
}

declare_clippy_lint! {
/// ### What it does
/// Warns if an integral constant literal starts with `0`.
///
/// ### Why is this bad?
/// In some languages (including the infamous C language
/// and most of its
/// family), this marks an octal constant. In Rust however, this is a decimal
/// constant. This could
/// be confusing for both the writer and a reader of the constant.
///
/// ### Example
///
/// In Rust:
/// ```no_run
/// fn main() {
/// let a = 0123;
/// println!("{}", a);
/// }
/// ```
///
/// prints `123`, while in C:
///
/// ```c
/// #include <stdio.h>
///
/// int main() {
/// int a = 0123;
/// printf("%d\n", a);
/// }
/// ```
///
/// prints `83` (as `83 == 0o123` while `123 == 0o173`).
#[clippy::version = "pre 1.29.0"]
pub ZERO_PREFIXED_LITERAL,
complexity,
"integer literals starting with `0`"
}

declare_clippy_lint! {
/// ### What it does
/// Warns if a generic shadows a built-in type.
Expand Down Expand Up @@ -334,7 +294,6 @@ declare_lint_pass!(MiscEarlyLints => [
MIXED_CASE_HEX_LITERALS,
UNSEPARATED_LITERAL_SUFFIX,
SEPARATED_LITERAL_SUFFIX,
ZERO_PREFIXED_LITERAL,
BUILTIN_TYPE_SHADOW,
REDUNDANT_PATTERN,
UNNEEDED_WILDCARD_PATTERN,
Expand Down Expand Up @@ -409,7 +368,7 @@ impl MiscEarlyLints {
};

let lit_kind = LitKind::from_token_lit(lit);
if let Ok(LitKind::Int(value, lit_int_type)) = lit_kind {
if let Ok(LitKind::Int(_value, lit_int_type)) = lit_kind {
let suffix = match lit_int_type {
LitIntType::Signed(ty) => ty.name_str(),
LitIntType::Unsigned(ty) => ty.name_str(),
Expand All @@ -418,10 +377,6 @@ impl MiscEarlyLints {
literal_suffix::check(cx, span, &lit_snip, suffix, "integer");
if lit_snip.starts_with("0x") {
mixed_case_hex_literals::check(cx, span, suffix, &lit_snip);
} else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
// nothing to do
} else if value != 0 && lit_snip.starts_with('0') {
zero_prefixed_literal::check(cx, span, &lit_snip);
}
} else if let Ok(LitKind::Float(_, LitFloatType::Suffixed(float_ty))) = lit_kind {
let suffix = float_ty.name_str();
Expand Down

This file was deleted.

14 changes: 3 additions & 11 deletions src/tools/clippy/tests/ui/literals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
// does not test any rustfixable lints

#![warn(clippy::mixed_case_hex_literals)]
#![warn(clippy::zero_prefixed_literal)]
#![warn(clippy::unseparated_literal_suffix)]
#![warn(clippy::separated_literal_suffix)]
#![allow(dead_code, overflowing_literals)]
#![allow(dead_code, overflowing_literals, leading_zeros_in_decimal_literals)]

fn main() {
let ok1 = 0xABCD;
Expand Down Expand Up @@ -36,14 +35,12 @@ fn main() {

let fail_multi_zero = 000_123usize;
//~^ unseparated_literal_suffix
//~| zero_prefixed_literal

let ok9 = 0;
let ok10 = 0_i64;
//~^ separated_literal_suffix

let fail8 = 0123;
//~^ zero_prefixed_literal
let ok10andhalf = 0123;

let ok11 = 0o123;
let ok12 = 0b10_1010;
Expand Down Expand Up @@ -75,13 +72,8 @@ fn main() {
}

fn issue9651() {
// lint but octal form is not possible here
// octal form is not possible here
let _ = 08;
//~^ zero_prefixed_literal

let _ = 09;
//~^ zero_prefixed_literal

let _ = 089;
//~^ zero_prefixed_literal
}
Loading
Loading