Skip to content

Commit 2289dab

Browse files
committed
Add option to control trailing zero in floating-point literals
1 parent cedb7b5 commit 2289dab

10 files changed

+246
-6
lines changed

Configurations.md

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

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

12611305
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`.

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,
@@ -645,6 +647,7 @@ format_macro_matchers = false
645647
format_macro_bodies = true
646648
skip_macro_invocations = []
647649
hex_literal_case = "Preserve"
650+
float_literal_trailing_zero = "Preserve"
648651
empty_item_single_line = true
649652
struct_lit_single_line = true
650653
fn_single_line = false

src/config/options.rs

+15
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,21 @@ pub enum HexLiteralCase {
144144
Lower,
145145
}
146146

147+
/// How to treat trailing zeros in floating-point literals.
148+
#[config_type]
149+
pub enum FloatLiteralTrailingZero {
150+
/// Leave the literal as-is.
151+
Preserve,
152+
/// Add a trailing zero to the literal.
153+
Always,
154+
/// Add a trailing zero by default. If the literal contains an exponent or a suffix, the zero
155+
/// and the preceding period are removed.
156+
IfNoPostfix,
157+
/// Remove the trailing zero. If the literal contains an exponent or a suffix, the preceding
158+
/// period is also removed.
159+
Never,
160+
}
161+
147162
#[config_type]
148163
pub enum ReportTactic {
149164
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, ForLoopKind};
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)
@@ -1242,6 +1254,7 @@ pub(crate) fn rewrite_literal(
12421254
match token_lit.kind {
12431255
token::LitKind::Str => rewrite_string_lit(context, span, shape),
12441256
token::LitKind::Integer => rewrite_int_lit(context, token_lit, span, shape),
1257+
token::LitKind::Float => rewrite_float_lit(context, token_lit, span, shape),
12451258
_ => wrap_str(
12461259
context.snippet(span).to_owned(),
12471260
context.config.max_width(),
@@ -1283,6 +1296,11 @@ fn rewrite_int_lit(
12831296
shape: Shape,
12841297
) -> Option<String> {
12851298
let symbol = token_lit.symbol.as_str();
1299+
let suffix = token_lit.suffix.as_ref().map(|s| s.as_str());
1300+
1301+
if suffix == Some("f32") || suffix == Some("f64") {
1302+
return rewrite_float_lit(context, token_lit, span, shape);
1303+
}
12861304

12871305
if let Some(symbol_stripped) = symbol.strip_prefix("0x") {
12881306
let hex_lit = match context.config.hex_literal_case() {
@@ -1292,11 +1310,7 @@ fn rewrite_int_lit(
12921310
};
12931311
if let Some(hex_lit) = hex_lit {
12941312
return wrap_str(
1295-
format!(
1296-
"0x{}{}",
1297-
hex_lit,
1298-
token_lit.suffix.map_or(String::new(), |s| s.to_string())
1299-
),
1313+
format!("0x{}{}", hex_lit, suffix.unwrap_or("")),
13001314
context.config.max_width(),
13011315
shape,
13021316
);
@@ -1310,6 +1324,68 @@ fn rewrite_int_lit(
13101324
)
13111325
}
13121326

1327+
fn rewrite_float_lit(
1328+
context: &RewriteContext<'_>,
1329+
token_lit: token::Lit,
1330+
span: Span,
1331+
shape: Shape,
1332+
) -> Option<String> {
1333+
if matches!(
1334+
context.config.float_literal_trailing_zero(),
1335+
FloatLiteralTrailingZero::Preserve
1336+
) {
1337+
return wrap_str(
1338+
context.snippet(span).to_owned(),
1339+
context.config.max_width(),
1340+
shape,
1341+
);
1342+
}
1343+
1344+
let symbol = token_lit.symbol.as_str();
1345+
let suffix = token_lit.suffix.as_ref().map(|s| s.as_str());
1346+
1347+
let caps = FLOAT_LITERAL.captures(symbol).unwrap();
1348+
let integer_part = caps.get(1).unwrap().as_str();
1349+
let fractional_part = caps.get(2).map(|s| s.as_str());
1350+
let exponent = caps.get(3).map(|s| s.as_str());
1351+
1352+
let has_postfix = exponent.is_some() || suffix.is_some();
1353+
let fractional_part_nonzero = fractional_part.map_or(false, |s| !ZERO_LITERAL.is_match(s));
1354+
1355+
let (include_period, include_fractional_part) =
1356+
match context.config.float_literal_trailing_zero() {
1357+
FloatLiteralTrailingZero::Preserve => unreachable!("handled above"),
1358+
FloatLiteralTrailingZero::Always => (true, true),
1359+
FloatLiteralTrailingZero::IfNoPostfix => (
1360+
fractional_part_nonzero || !has_postfix,
1361+
fractional_part_nonzero || !has_postfix,
1362+
),
1363+
FloatLiteralTrailingZero::Never => (
1364+
fractional_part_nonzero || !has_postfix,
1365+
fractional_part_nonzero,
1366+
),
1367+
};
1368+
1369+
let period = if include_period { "." } else { "" };
1370+
let fractional_part = if include_fractional_part {
1371+
fractional_part.unwrap_or("0")
1372+
} else {
1373+
""
1374+
};
1375+
wrap_str(
1376+
format!(
1377+
"{}{}{}{}{}",
1378+
integer_part,
1379+
period,
1380+
fractional_part,
1381+
exponent.unwrap_or(""),
1382+
suffix.unwrap_or(""),
1383+
),
1384+
context.config.max_width(),
1385+
shape,
1386+
)
1387+
}
1388+
13131389
fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option<SeparatorTactic> {
13141390
if context.inside_macro() {
13151391
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)