Skip to content

Commit

Permalink
Add option to control trailing zero in floating-point literals
Browse files Browse the repository at this point in the history
  • Loading branch information
amatveiakin committed Jan 26, 2024
1 parent cedb7b5 commit 2289dab
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 6 deletions.
44 changes: 44 additions & 0 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,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`

This option is deprecated and has been renamed to `show_parse_errors` to avoid confusion around the double negative default of `hide_parse_errors=false`.
Expand Down
3 changes: 3 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -645,6 +647,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
Expand Down
15 changes: 15 additions & 0 deletions src/config/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,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,
Expand Down
88 changes: 82 additions & 6 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ForLoopKind};
use rustc_span::{BytePos, Span};
Expand All @@ -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,
Expand All @@ -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<String> {
format_expr(self, ExprType::SubExpression, context, shape)
Expand Down Expand Up @@ -1242,6 +1254,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(),
Expand Down Expand Up @@ -1283,6 +1296,11 @@ fn rewrite_int_lit(
shape: Shape,
) -> Option<String> {
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() {
Expand All @@ -1292,11 +1310,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,
);
Expand All @@ -1310,6 +1324,68 @@ fn rewrite_int_lit(
)
}

fn rewrite_float_lit(
context: &RewriteContext<'_>,
token_lit: token::Lit,
span: Span,
shape: Shape,
) -> Option<String> {
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<SeparatorTactic> {
if context.inside_macro() {
if span_ends_with_comma(context, span) {
Expand Down
17 changes: 17 additions & 0 deletions tests/source/float-lit-trailing-zero-always.rs
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions tests/source/float-lit-trailing-zero-if-no-postfix.rs
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions tests/source/float-lit-trailing-zero-never.rs
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions tests/target/float-lit-trailing-zero-always.rs
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions tests/target/float-lit-trailing-zero-if-no-postfix.rs
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions tests/target/float-lit-trailing-zero-never.rs
Original file line number Diff line number Diff line change
@@ -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_.;
}

0 comments on commit 2289dab

Please sign in to comment.