diff --git a/Configurations.md b/Configurations.md index ac5747800b2..98b7ac971c8 100644 --- a/Configurations.md +++ b/Configurations.md @@ -1246,6 +1246,50 @@ Control the case of the letters in hexadecimal literal values - **Possible values**: `Preserve`, `Upper`, `Lower` - **Stable**: No (tracking issue: [#5081](https://github.com/rust-lang/rustfmt/issues/5081)) +## `float_literal_trailing_zero` + +Control the presence of trailing zero in floating-point literal values + +- **Default value**: `Preserve` +- **Possible values**: `Preserve`, `Always`, `IfNoPostfix`, `Never` +- **Stable**: No (tracking issue: [#3187](https://github.com/rust-lang/rustfmt/issues/3187)) + +#### `Preserve` (default): + +Leave the literal as-is. + +#### `Always`: + +Add a trailing zero to the literal: + +```rust +fn main() { + let values = [1.0, 2.0e10, 3.0f32]; +} +``` + +#### `IfNoPostfix`: + +Add a trailing zero by default. If the literal contains an exponent or a suffix, the zero +and the preceding period are removed: + +```rust +fn main() { + let values = [1.0, 2e10, 3f32]; +} +``` + +#### `Never`: + +Remove the trailing zero. If the literal contains an exponent or a suffix, the preceding +period is also removed: + +```rust +fn main() { + let values = [1., 2e10, 3f32]; +} +``` + ## `hide_parse_errors` Do not show parse errors if the parser failed to parse files. diff --git a/src/config/mod.rs b/src/config/mod.rs index 6f41b299e87..a5767bdbf8a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -79,6 +79,8 @@ create_config! { "Skip formatting the bodies of macros invoked with the following names."; hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false, "Format hexadecimal integer literals"; + float_literal_trailing_zero: FloatLiteralTrailingZero, FloatLiteralTrailingZero::Preserve, + false, "Add or remove trailing zero in floating-point literals"; // Single line expressions and items empty_item_single_line: bool, true, false, @@ -637,6 +639,7 @@ format_macro_matchers = false format_macro_bodies = true skip_macro_invocations = [] hex_literal_case = "Preserve" +float_literal_trailing_zero = "Preserve" empty_item_single_line = true struct_lit_single_line = true fn_single_line = false diff --git a/src/config/options.rs b/src/config/options.rs index 3aa1a4de99d..14903d18e03 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -142,6 +142,21 @@ pub enum HexLiteralCase { Lower, } +/// How to treat trailing zeros in floating-point literals. +#[config_type] +pub enum FloatLiteralTrailingZero { + /// Leave the literal as-is. + Preserve, + /// Add a trailing zero to the literal. + Always, + /// Add a trailing zero by default. If the literal contains an exponent or a suffix, the zero + /// and the preceding period are removed. + IfNoPostfix, + /// Remove the trailing zero. If the literal contains an exponent or a suffix, the preceding + /// period is also removed. + Never, +} + #[config_type] pub enum ReportTactic { Always, diff --git a/src/expr.rs b/src/expr.rs index 9a605d6427e..c0d47e4b8ca 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -2,6 +2,8 @@ use std::borrow::Cow; use std::cmp::min; use itertools::Itertools; +use lazy_static::lazy_static; +use regex::Regex; use rustc_ast::token::{Delimiter, Lit, LitKind}; use rustc_ast::{ast, ptr, token}; use rustc_span::{BytePos, Span}; @@ -13,7 +15,9 @@ use crate::comment::{ rewrite_missing_comment, CharClasses, FindUncommented, }; use crate::config::lists::*; -use crate::config::{Config, ControlBraceStyle, HexLiteralCase, IndentStyle, Version}; +use crate::config::{ + Config, ControlBraceStyle, FloatLiteralTrailingZero, HexLiteralCase, IndentStyle, Version, +}; use crate::lists::{ definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, struct_lit_tactic, write_list, ListFormatting, Separator, @@ -37,6 +41,14 @@ use crate::utils::{ use crate::vertical::rewrite_with_alignment; use crate::visitor::FmtVisitor; +lazy_static! { + // This regex may accept invalid float literals (such as `1`, `_` or `2.e3`). That's ok. + // We only use it to parse literals whose validity has already been established. + static ref FLOAT_LITERAL: Regex = + Regex::new(r"^([0-9_]+)(?:\.([0-9_]+)?)?([eE][+-]?[0-9_]+)?$").unwrap(); + static ref ZERO_LITERAL: Regex = Regex::new(r"^[0_]+$").unwrap(); +} + impl Rewrite for ast::Expr { fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { format_expr(self, ExprType::SubExpression, context, shape) @@ -1235,6 +1247,7 @@ pub(crate) fn rewrite_literal( match token_lit.kind { token::LitKind::Str => rewrite_string_lit(context, span, shape), token::LitKind::Integer => rewrite_int_lit(context, token_lit, span, shape), + token::LitKind::Float => rewrite_float_lit(context, token_lit, span, shape), _ => wrap_str( context.snippet(span).to_owned(), context.config.max_width(), @@ -1276,6 +1289,11 @@ fn rewrite_int_lit( shape: Shape, ) -> Option { let symbol = token_lit.symbol.as_str(); + let suffix = token_lit.suffix.as_ref().map(|s| s.as_str()); + + if suffix == Some("f32") || suffix == Some("f64") { + return rewrite_float_lit(context, token_lit, span, shape); + } if let Some(symbol_stripped) = symbol.strip_prefix("0x") { let hex_lit = match context.config.hex_literal_case() { @@ -1285,11 +1303,7 @@ fn rewrite_int_lit( }; if let Some(hex_lit) = hex_lit { return wrap_str( - format!( - "0x{}{}", - hex_lit, - token_lit.suffix.map_or(String::new(), |s| s.to_string()) - ), + format!("0x{}{}", hex_lit, suffix.unwrap_or("")), context.config.max_width(), shape, ); @@ -1303,6 +1317,68 @@ fn rewrite_int_lit( ) } +fn rewrite_float_lit( + context: &RewriteContext<'_>, + token_lit: token::Lit, + span: Span, + shape: Shape, +) -> Option { + if matches!( + context.config.float_literal_trailing_zero(), + FloatLiteralTrailingZero::Preserve + ) { + return wrap_str( + context.snippet(span).to_owned(), + context.config.max_width(), + shape, + ); + } + + let symbol = token_lit.symbol.as_str(); + let suffix = token_lit.suffix.as_ref().map(|s| s.as_str()); + + let caps = FLOAT_LITERAL.captures(symbol).unwrap(); + let integer_part = caps.get(1).unwrap().as_str(); + let fractional_part = caps.get(2).map(|s| s.as_str()); + let exponent = caps.get(3).map(|s| s.as_str()); + + let has_postfix = exponent.is_some() || suffix.is_some(); + let fractional_part_nonzero = fractional_part.map_or(false, |s| !ZERO_LITERAL.is_match(s)); + + let (include_period, include_fractional_part) = + match context.config.float_literal_trailing_zero() { + FloatLiteralTrailingZero::Preserve => unreachable!("handled above"), + FloatLiteralTrailingZero::Always => (true, true), + FloatLiteralTrailingZero::IfNoPostfix => ( + fractional_part_nonzero || !has_postfix, + fractional_part_nonzero || !has_postfix, + ), + FloatLiteralTrailingZero::Never => ( + fractional_part_nonzero || !has_postfix, + fractional_part_nonzero, + ), + }; + + let period = if include_period { "." } else { "" }; + let fractional_part = if include_fractional_part { + fractional_part.unwrap_or("0") + } else { + "" + }; + wrap_str( + format!( + "{}{}{}{}{}", + integer_part, + period, + fractional_part, + exponent.unwrap_or(""), + suffix.unwrap_or(""), + ), + context.config.max_width(), + shape, + ) +} + fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option { if context.inside_macro() { if span_ends_with_comma(context, span) { diff --git a/tests/source/float-lit-trailing-zero-always.rs b/tests/source/float-lit-trailing-zero-always.rs new file mode 100644 index 00000000000..ab9305e5cd4 --- /dev/null +++ b/tests/source/float-lit-trailing-zero-always.rs @@ -0,0 +1,17 @@ +// rustfmt-float_literal_trailing_zero: Always + +fn float_literals() { + let a = 0.; + let b = 0.0; + let c = 100.; + let d = 100.0; + let e = 5e3; + let f = 5.0e3; + let g = 7f32; + let h = 7.0f32; + let i = 9e3f32; + let j = 9.0e3f32; + let k = 1000.00; + let l = 1_000_.; + let m = 1_000_.000_000; +} diff --git a/tests/source/float-lit-trailing-zero-if-no-postfix.rs b/tests/source/float-lit-trailing-zero-if-no-postfix.rs new file mode 100644 index 00000000000..43d3d69f67b --- /dev/null +++ b/tests/source/float-lit-trailing-zero-if-no-postfix.rs @@ -0,0 +1,17 @@ +// rustfmt-float_literal_trailing_zero: IfNoPostfix + +fn float_literals() { + let a = 0.; + let b = 0.0; + let c = 100.; + let d = 100.0; + let e = 5e3; + let f = 5.0e3; + let g = 7f32; + let h = 7.0f32; + let i = 9e3f32; + let j = 9.0e3f32; + let k = 1000.00; + let l = 1_000_.; + let m = 1_000_.000_000; +} diff --git a/tests/source/float-lit-trailing-zero-never.rs b/tests/source/float-lit-trailing-zero-never.rs new file mode 100644 index 00000000000..9f455b31f13 --- /dev/null +++ b/tests/source/float-lit-trailing-zero-never.rs @@ -0,0 +1,17 @@ +// rustfmt-float_literal_trailing_zero: Never + +fn float_literals() { + let a = 0.; + let b = 0.0; + let c = 100.; + let d = 100.0; + let e = 5e3; + let f = 5.0e3; + let g = 7f32; + let h = 7.0f32; + let i = 9e3f32; + let j = 9.0e3f32; + let k = 1000.00; + let l = 1_000_.; + let m = 1_000_.000_000; +} diff --git a/tests/target/float-lit-trailing-zero-always.rs b/tests/target/float-lit-trailing-zero-always.rs new file mode 100644 index 00000000000..042433d34ac --- /dev/null +++ b/tests/target/float-lit-trailing-zero-always.rs @@ -0,0 +1,17 @@ +// rustfmt-float_literal_trailing_zero: Always + +fn float_literals() { + let a = 0.0; + let b = 0.0; + let c = 100.0; + let d = 100.0; + let e = 5.0e3; + let f = 5.0e3; + let g = 7.0f32; + let h = 7.0f32; + let i = 9.0e3f32; + let j = 9.0e3f32; + let k = 1000.00; + let l = 1_000_.0; + let m = 1_000_.000_000; +} diff --git a/tests/target/float-lit-trailing-zero-if-no-postfix.rs b/tests/target/float-lit-trailing-zero-if-no-postfix.rs new file mode 100644 index 00000000000..4efec39e382 --- /dev/null +++ b/tests/target/float-lit-trailing-zero-if-no-postfix.rs @@ -0,0 +1,17 @@ +// rustfmt-float_literal_trailing_zero: IfNoPostfix + +fn float_literals() { + let a = 0.0; + let b = 0.0; + let c = 100.0; + let d = 100.0; + let e = 5e3; + let f = 5e3; + let g = 7f32; + let h = 7f32; + let i = 9e3f32; + let j = 9e3f32; + let k = 1000.00; + let l = 1_000_.0; + let m = 1_000_.000_000; +} diff --git a/tests/target/float-lit-trailing-zero-never.rs b/tests/target/float-lit-trailing-zero-never.rs new file mode 100644 index 00000000000..4f28b7ac5c2 --- /dev/null +++ b/tests/target/float-lit-trailing-zero-never.rs @@ -0,0 +1,17 @@ +// rustfmt-float_literal_trailing_zero: Never + +fn float_literals() { + let a = 0.; + let b = 0.; + let c = 100.; + let d = 100.; + let e = 5e3; + let f = 5e3; + let g = 7f32; + let h = 7f32; + let i = 9e3f32; + let j = 9e3f32; + let k = 1000.; + let l = 1_000_.; + let m = 1_000_.; +}