Skip to content

Commit b7cb5be

Browse files
committed
Add option to control trailing zero in floating-point literals
1 parent d698bf4 commit b7cb5be

10 files changed

+246
-6
lines changed

Configurations.md

+44
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,50 @@ Control the case of the letters in hexadecimal literal values
12461246
- **Possible values**: `Preserve`, `Upper`, `Lower`
12471247
- **Stable**: No (tracking issue: [#5081](https://github.com/rust-lang/rustfmt/issues/5081))
12481248

1249+
## `float_literal_trailing_zero`
1250+
1251+
Control the presence of trailing zero in floating-point literal values
1252+
1253+
- **Default value**: `Preserve`
1254+
- **Possible values**: `Preserve`, `Always`, `IfNoPostfix`, `Never`
1255+
- **Stable**: No (tracking issue: [#3187](https://github.com/rust-lang/rustfmt/issues/3187))
1256+
1257+
#### `Preserve` (default):
1258+
1259+
Leave the literal as-is.
1260+
1261+
#### `Always`:
1262+
1263+
Add a trailing zero to the literal:
1264+
1265+
```rust
1266+
fn main() {
1267+
let values = [1.0, 2.0e10, 3.0f32];
1268+
}
1269+
```
1270+
1271+
#### `IfNoPostfix`:
1272+
1273+
Add a trailing zero by default. If the literal contains an exponent or a suffix, the zero
1274+
and the preceding period are removed:
1275+
1276+
```rust
1277+
fn main() {
1278+
let values = [1.0, 2e10, 3f32];
1279+
}
1280+
```
1281+
1282+
#### `Never`:
1283+
1284+
Remove the trailing zero. If the literal contains an exponent or a suffix, the preceding
1285+
period is also removed:
1286+
1287+
```rust
1288+
fn main() {
1289+
let values = [1., 2e10, 3f32];
1290+
}
1291+
```
1292+
12491293
## `hide_parse_errors`
12501294

12511295
Do not show parse errors if the parser failed to parse files.

src/config/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ create_config! {
7979
"Skip formatting the bodies of macros invoked with the following names.";
8080
hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false,
8181
"Format hexadecimal integer literals";
82+
float_literal_trailing_zero: FloatLiteralTrailingZero, FloatLiteralTrailingZero::Preserve,
83+
false, "Add or remove trailing zero in floating-point literals";
8284

8385
// Single line expressions and items
8486
empty_item_single_line: bool, true, false,
@@ -637,6 +639,7 @@ format_macro_matchers = false
637639
format_macro_bodies = true
638640
skip_macro_invocations = []
639641
hex_literal_case = "Preserve"
642+
float_literal_trailing_zero = "Preserve"
640643
empty_item_single_line = true
641644
struct_lit_single_line = true
642645
fn_single_line = false

src/config/options.rs

+15
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,21 @@ pub enum HexLiteralCase {
142142
Lower,
143143
}
144144

145+
/// How to treat trailing zeros in floating-point literals.
146+
#[config_type]
147+
pub enum FloatLiteralTrailingZero {
148+
/// Leave the literal as-is.
149+
Preserve,
150+
/// Add a trailing zero to the literal.
151+
Always,
152+
/// Add a trailing zero by default. If the literal contains an exponent or a suffix, the zero
153+
/// and the preceding period are removed.
154+
IfNoPostfix,
155+
/// Remove the trailing zero. If the literal contains an exponent or a suffix, the preceding
156+
/// period is also removed.
157+
Never,
158+
}
159+
145160
#[config_type]
146161
pub enum ReportTactic {
147162
Always,

src/expr.rs

+82-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use std::borrow::Cow;
22
use std::cmp::min;
33

44
use itertools::Itertools;
5+
use lazy_static::lazy_static;
6+
use regex::Regex;
57
use rustc_ast::token::{Delimiter, Lit, LitKind};
68
use rustc_ast::{ast, ptr, token};
79
use rustc_span::{BytePos, Span};
@@ -13,7 +15,9 @@ use crate::comment::{
1315
rewrite_missing_comment, CharClasses, FindUncommented,
1416
};
1517
use crate::config::lists::*;
16-
use crate::config::{Config, ControlBraceStyle, HexLiteralCase, IndentStyle, Version};
18+
use crate::config::{
19+
Config, ControlBraceStyle, FloatLiteralTrailingZero, HexLiteralCase, IndentStyle, Version,
20+
};
1721
use crate::lists::{
1822
definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape,
1923
struct_lit_tactic, write_list, ListFormatting, Separator,
@@ -37,6 +41,14 @@ use crate::utils::{
3741
use crate::vertical::rewrite_with_alignment;
3842
use crate::visitor::FmtVisitor;
3943

44+
lazy_static! {
45+
// This regex may accept invalid float literals (such as `1`, `_` or `2.e3`). That's ok.
46+
// We only use it to parse literals whose validity has already been established.
47+
static ref FLOAT_LITERAL: Regex =
48+
Regex::new(r"^([0-9_]+)(?:\.([0-9_]+)?)?([eE][+-]?[0-9_]+)?$").unwrap();
49+
static ref ZERO_LITERAL: Regex = Regex::new(r"^[0_]+$").unwrap();
50+
}
51+
4052
impl Rewrite for ast::Expr {
4153
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
4254
format_expr(self, ExprType::SubExpression, context, shape)
@@ -1235,6 +1247,7 @@ pub(crate) fn rewrite_literal(
12351247
match token_lit.kind {
12361248
token::LitKind::Str => rewrite_string_lit(context, span, shape),
12371249
token::LitKind::Integer => rewrite_int_lit(context, token_lit, span, shape),
1250+
token::LitKind::Float => rewrite_float_lit(context, token_lit, span, shape),
12381251
_ => wrap_str(
12391252
context.snippet(span).to_owned(),
12401253
context.config.max_width(),
@@ -1276,6 +1289,11 @@ fn rewrite_int_lit(
12761289
shape: Shape,
12771290
) -> Option<String> {
12781291
let symbol = token_lit.symbol.as_str();
1292+
let suffix = token_lit.suffix.as_ref().map(|s| s.as_str());
1293+
1294+
if suffix == Some("f32") || suffix == Some("f64") {
1295+
return rewrite_float_lit(context, token_lit, span, shape);
1296+
}
12791297

12801298
if let Some(symbol_stripped) = symbol.strip_prefix("0x") {
12811299
let hex_lit = match context.config.hex_literal_case() {
@@ -1285,11 +1303,7 @@ fn rewrite_int_lit(
12851303
};
12861304
if let Some(hex_lit) = hex_lit {
12871305
return wrap_str(
1288-
format!(
1289-
"0x{}{}",
1290-
hex_lit,
1291-
token_lit.suffix.map_or(String::new(), |s| s.to_string())
1292-
),
1306+
format!("0x{}{}", hex_lit, suffix.unwrap_or("")),
12931307
context.config.max_width(),
12941308
shape,
12951309
);
@@ -1303,6 +1317,68 @@ fn rewrite_int_lit(
13031317
)
13041318
}
13051319

1320+
fn rewrite_float_lit(
1321+
context: &RewriteContext<'_>,
1322+
token_lit: token::Lit,
1323+
span: Span,
1324+
shape: Shape,
1325+
) -> Option<String> {
1326+
if matches!(
1327+
context.config.float_literal_trailing_zero(),
1328+
FloatLiteralTrailingZero::Preserve
1329+
) {
1330+
return wrap_str(
1331+
context.snippet(span).to_owned(),
1332+
context.config.max_width(),
1333+
shape,
1334+
);
1335+
}
1336+
1337+
let symbol = token_lit.symbol.as_str();
1338+
let suffix = token_lit.suffix.as_ref().map(|s| s.as_str());
1339+
1340+
let caps = FLOAT_LITERAL.captures(symbol).unwrap();
1341+
let integer_part = caps.get(1).unwrap().as_str();
1342+
let fractional_part = caps.get(2).map(|s| s.as_str());
1343+
let exponent = caps.get(3).map(|s| s.as_str());
1344+
1345+
let has_postfix = exponent.is_some() || suffix.is_some();
1346+
let fractional_part_nonzero = fractional_part.map_or(false, |s| !ZERO_LITERAL.is_match(s));
1347+
1348+
let (include_period, include_fractional_part) =
1349+
match context.config.float_literal_trailing_zero() {
1350+
FloatLiteralTrailingZero::Preserve => unreachable!("handled above"),
1351+
FloatLiteralTrailingZero::Always => (true, true),
1352+
FloatLiteralTrailingZero::IfNoPostfix => (
1353+
fractional_part_nonzero || !has_postfix,
1354+
fractional_part_nonzero || !has_postfix,
1355+
),
1356+
FloatLiteralTrailingZero::Never => (
1357+
fractional_part_nonzero || !has_postfix,
1358+
fractional_part_nonzero,
1359+
),
1360+
};
1361+
1362+
let period = if include_period { "." } else { "" };
1363+
let fractional_part = if include_fractional_part {
1364+
fractional_part.unwrap_or("0")
1365+
} else {
1366+
""
1367+
};
1368+
wrap_str(
1369+
format!(
1370+
"{}{}{}{}{}",
1371+
integer_part,
1372+
period,
1373+
fractional_part,
1374+
exponent.unwrap_or(""),
1375+
suffix.unwrap_or(""),
1376+
),
1377+
context.config.max_width(),
1378+
shape,
1379+
)
1380+
}
1381+
13061382
fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option<SeparatorTactic> {
13071383
if context.inside_macro() {
13081384
if span_ends_with_comma(context, span) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// rustfmt-float_literal_trailing_zero: Always
2+
3+
fn float_literals() {
4+
let a = 0.;
5+
let b = 0.0;
6+
let c = 100.;
7+
let d = 100.0;
8+
let e = 5e3;
9+
let f = 5.0e3;
10+
let g = 7f32;
11+
let h = 7.0f32;
12+
let i = 9e3f32;
13+
let j = 9.0e3f32;
14+
let k = 1000.00;
15+
let l = 1_000_.;
16+
let m = 1_000_.000_000;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// rustfmt-float_literal_trailing_zero: IfNoPostfix
2+
3+
fn float_literals() {
4+
let a = 0.;
5+
let b = 0.0;
6+
let c = 100.;
7+
let d = 100.0;
8+
let e = 5e3;
9+
let f = 5.0e3;
10+
let g = 7f32;
11+
let h = 7.0f32;
12+
let i = 9e3f32;
13+
let j = 9.0e3f32;
14+
let k = 1000.00;
15+
let l = 1_000_.;
16+
let m = 1_000_.000_000;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// rustfmt-float_literal_trailing_zero: Never
2+
3+
fn float_literals() {
4+
let a = 0.;
5+
let b = 0.0;
6+
let c = 100.;
7+
let d = 100.0;
8+
let e = 5e3;
9+
let f = 5.0e3;
10+
let g = 7f32;
11+
let h = 7.0f32;
12+
let i = 9e3f32;
13+
let j = 9.0e3f32;
14+
let k = 1000.00;
15+
let l = 1_000_.;
16+
let m = 1_000_.000_000;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// rustfmt-float_literal_trailing_zero: Always
2+
3+
fn float_literals() {
4+
let a = 0.0;
5+
let b = 0.0;
6+
let c = 100.0;
7+
let d = 100.0;
8+
let e = 5.0e3;
9+
let f = 5.0e3;
10+
let g = 7.0f32;
11+
let h = 7.0f32;
12+
let i = 9.0e3f32;
13+
let j = 9.0e3f32;
14+
let k = 1000.00;
15+
let l = 1_000_.0;
16+
let m = 1_000_.000_000;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// rustfmt-float_literal_trailing_zero: IfNoPostfix
2+
3+
fn float_literals() {
4+
let a = 0.0;
5+
let b = 0.0;
6+
let c = 100.0;
7+
let d = 100.0;
8+
let e = 5e3;
9+
let f = 5e3;
10+
let g = 7f32;
11+
let h = 7f32;
12+
let i = 9e3f32;
13+
let j = 9e3f32;
14+
let k = 1000.00;
15+
let l = 1_000_.0;
16+
let m = 1_000_.000_000;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// rustfmt-float_literal_trailing_zero: Never
2+
3+
fn float_literals() {
4+
let a = 0.;
5+
let b = 0.;
6+
let c = 100.;
7+
let d = 100.;
8+
let e = 5e3;
9+
let f = 5e3;
10+
let g = 7f32;
11+
let h = 7f32;
12+
let i = 9e3f32;
13+
let j = 9e3f32;
14+
let k = 1000.;
15+
let l = 1_000_.;
16+
let m = 1_000_.;
17+
}

0 commit comments

Comments
 (0)