Rollup merge of #154086 - aytey:fix-float-range-spacing, r=Kivooeo

Insert space after float literal ending with `.` in pretty printer

The pretty printer was collapsing unsuffixed float literals (like `0.`) with adjacent dot tokens, producing ambiguous or invalid output:

- `0. ..45.` (range) became `0...45.`
- `0. ..=360.` (inclusive range) became `0...=360.`
- `0. .to_string()` (method call) became `0..to_string()`

The fix inserts a space after an unsuffixed float literal ending with `.` when it appears as the start expression of a range operator or the receiver of a method call or field access, preventing the trailing dot from merging with the following `.`, `..`, or `..=` token. This is skipped when the expression is already parenthesized, since the closing `)` naturally provides separation.

## Example

**before** (`rustc 1.96.0-nightly (3b1b0ef4d 2026-03-11)`):

```rust
#![feature(prelude_import)]
#![no_std]
extern crate std;
#[prelude_import]
use ::std::prelude::rust_2015::*;
//@ pp-exact

fn main() {
    let _ = 0...45.;
    let _ = 0...=360.;
    let _ = 0...;
    let _ = 0..to_string();
}
```

**after** (this branch):

```rust
#![feature(prelude_import)]
#![no_std]
extern crate std;
#[prelude_import]
use ::std::prelude::rust_2015::*;
//@ pp-exact

fn main() {
    let _ = 0. ..45.;
    let _ = 0. ..=360.;
    let _ = 0. ..;
    let _ = 0. .to_string();
}
```

Notice `0...45.` (ambiguous — looks like deprecated `...` inclusive range) becomes `0. ..45.` (clear: float `0.` followed by range `..`). Similarly `0..to_string()` (parsed as range) becomes `0. .to_string()` (method call on float).
This commit is contained in:
Stuart Cook
2026-03-20 15:33:09 +11:00
committed by GitHub
2 changed files with 45 additions and 11 deletions
@@ -260,12 +260,15 @@ fn print_expr_method_call(
//
// loop { break x; }.method();
//
self.print_expr_cond_paren(
receiver,
receiver.precedence() < ExprPrecedence::Unambiguous,
fixup.leftmost_subexpression_with_dot(),
);
let needs_paren = receiver.precedence() < ExprPrecedence::Unambiguous;
self.print_expr_cond_paren(receiver, needs_paren, fixup.leftmost_subexpression_with_dot());
// If the receiver is an unsuffixed float literal like `0.`, insert
// a space so the `.` of the method call doesn't merge with the
// trailing dot: `0. .method()` instead of `0..method()`.
if !needs_paren && expr_ends_with_dot(receiver) {
self.word(" ");
}
self.word(".");
self.print_ident(segment.ident);
if let Some(args) = &segment.args {
@@ -658,11 +661,15 @@ pub(super) fn print_expr_outer_attr_style(
);
}
ast::ExprKind::Field(expr, ident) => {
let needs_paren = expr.precedence() < ExprPrecedence::Unambiguous;
self.print_expr_cond_paren(
expr,
expr.precedence() < ExprPrecedence::Unambiguous,
needs_paren,
fixup.leftmost_subexpression_with_dot(),
);
if !needs_paren && expr_ends_with_dot(expr) {
self.word(" ");
}
self.word(".");
self.print_ident(*ident);
}
@@ -685,11 +692,15 @@ pub(super) fn print_expr_outer_attr_style(
let fake_prec = ExprPrecedence::LOr;
if let Some(e) = start {
let start_fixup = fixup.leftmost_subexpression_with_operator(true);
self.print_expr_cond_paren(
e,
start_fixup.precedence(e) < fake_prec,
start_fixup,
);
let needs_paren = start_fixup.precedence(e) < fake_prec;
self.print_expr_cond_paren(e, needs_paren, start_fixup);
// If the start expression is a float literal ending with
// `.`, we need a space before `..` or `..=` so that the
// dots don't merge. E.g. `0. ..45.` must not become
// `0...45.`.
if !needs_paren && expr_ends_with_dot(e) {
self.word(" ");
}
}
match limits {
ast::RangeLimits::HalfOpen => self.word(".."),
@@ -1025,3 +1036,18 @@ fn reconstruct_format_args_template_string(pieces: &[FormatArgsPiece]) -> String
template.push('"');
template
}
/// Returns `true` if the printed representation of this expression ends with
/// a `.` character — specifically, an unsuffixed float literal like `0.` or
/// `45.`. This is used to insert whitespace before range operators (`..`,
/// `..=`) so that the dots don't merge (e.g. `0. ..45.` instead of `0...45.`).
fn expr_ends_with_dot(expr: &ast::Expr) -> bool {
match &expr.kind {
ast::ExprKind::Lit(token_lit) => {
token_lit.kind == token::Float
&& token_lit.suffix.is_none()
&& token_lit.symbol.as_str().ends_with('.')
}
_ => false,
}
}
+8
View File
@@ -0,0 +1,8 @@
//@ pp-exact
fn main() {
let _ = 0. ..45.;
let _ = 0. ..=360.;
let _ = 0. ..;
let _ = 0. .to_string();
}