Skip to main content

rustfmt_nightly/
expr.rs

1use std::borrow::Cow;
2use std::cmp::min;
3
4use itertools::Itertools;
5use rustc_ast::token::{Delimiter, Lit, LitKind};
6use rustc_ast::{ForLoopKind, MatchKind, ast, token};
7use rustc_span::{BytePos, Span};
8use tracing::debug;
9
10use crate::chains::rewrite_chain;
11use crate::closures;
12use crate::comment::{
13    CharClasses, FindUncommented, combine_strs_with_missing_comments, contains_comment,
14    recover_comment_removed, rewrite_comment, rewrite_missing_comment,
15};
16use crate::config::{Config, ControlBraceStyle, HexLiteralCase, IndentStyle, StyleEdition};
17use crate::config::{FloatLiteralTrailingZero, lists::*};
18use crate::lists::{
19    ListFormatting, Separator, definitive_tactic, itemize_list, shape_for_tactic,
20    struct_lit_formatting, struct_lit_shape, struct_lit_tactic, write_list,
21};
22use crate::macros::{MacroPosition, rewrite_macro};
23use crate::matches::rewrite_match;
24use crate::overflow::{self, IntoOverflowableItem, OverflowableItem};
25use crate::pairs::{PairParts, rewrite_all_pairs, rewrite_pair};
26use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
27use crate::shape::{Indent, Shape};
28use crate::source_map::{LineRangeUtils, SpanUtils};
29use crate::spanned::Spanned;
30use crate::stmt;
31use crate::string::{StringFormat, rewrite_string};
32use crate::types::{PathContext, rewrite_path};
33use crate::utils::{
34    colon_spaces, contains_skip, count_newlines, filtered_str_fits, first_line_ends_with,
35    inner_attributes, last_line_extendable, last_line_width, mk_sp, outer_attributes,
36    semicolon_for_expr, unicode_str_width, wrap_str,
37};
38use crate::vertical::rewrite_with_alignment;
39use crate::visitor::FmtVisitor;
40
41impl Rewrite for ast::Expr {
42    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
43        self.rewrite_result(context, shape).ok()
44    }
45
46    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
47        format_expr(self, ExprType::SubExpression, context, shape)
48    }
49}
50
51#[derive(Copy, Clone, PartialEq)]
52pub(crate) enum ExprType {
53    Statement,
54    SubExpression,
55}
56
57pub(crate) fn lit_ends_in_dot(lit: &Lit, context: &RewriteContext<'_>) -> bool {
58    match lit.kind {
59        LitKind::Float => float_lit_ends_in_dot(
60            lit.symbol.as_str(),
61            lit.suffix.as_ref().map(|s| s.as_str()),
62            context.config.float_literal_trailing_zero(),
63        ),
64        _ => false,
65    }
66}
67
68pub(crate) fn float_lit_ends_in_dot(
69    symbol: &str,
70    suffix: Option<&str>,
71    float_literal_trailing_zero: FloatLiteralTrailingZero,
72) -> bool {
73    match float_literal_trailing_zero {
74        FloatLiteralTrailingZero::Preserve => symbol.ends_with('.') && suffix.is_none(),
75        FloatLiteralTrailingZero::IfNoPostfix | FloatLiteralTrailingZero::Always => false,
76        FloatLiteralTrailingZero::Never => {
77            let float_parts = parse_float_symbol(symbol).unwrap();
78            let has_postfix = float_parts.exponent.is_some() || suffix.is_some();
79            let fractional_part_zero = float_parts.is_fractional_part_zero();
80            !has_postfix && fractional_part_zero
81        }
82    }
83}
84
85pub(crate) fn format_expr(
86    expr: &ast::Expr,
87    expr_type: ExprType,
88    context: &RewriteContext<'_>,
89    shape: Shape,
90) -> RewriteResult {
91    skip_out_of_file_lines_range_err!(context, expr.span);
92
93    if contains_skip(&*expr.attrs) {
94        return Ok(context.snippet(expr.span()).to_owned());
95    }
96    let shape = if expr_type == ExprType::Statement && semicolon_for_expr(context, expr) {
97        shape.sub_width(1, expr.span)?
98    } else {
99        shape
100    };
101
102    let expr_rw = match expr.kind {
103        ast::ExprKind::Array(ref expr_vec) => rewrite_array(
104            "",
105            expr_vec.iter(),
106            expr.span,
107            context,
108            shape,
109            choose_separator_tactic(context, expr.span),
110            None,
111        ),
112        ast::ExprKind::Lit(token_lit) => {
113            if let Ok(expr_rw) = rewrite_literal(context, token_lit, expr.span, shape) {
114                Ok(expr_rw)
115            } else {
116                if let LitKind::StrRaw(_) = token_lit.kind {
117                    Ok(context.snippet(expr.span).trim().into())
118                } else {
119                    Err(RewriteError::Unknown)
120                }
121            }
122        }
123        ast::ExprKind::Call(ref callee, ref args) => {
124            let inner_span = mk_sp(callee.span.hi(), expr.span.hi());
125            let callee_str = callee.rewrite_result(context, shape)?;
126            rewrite_call(context, &callee_str, args, inner_span, shape)
127        }
128        ast::ExprKind::Paren(ref subexpr) => rewrite_paren(context, subexpr, shape, expr.span),
129        ast::ExprKind::Binary(op, ref lhs, ref rhs) => {
130            // FIXME: format comments between operands and operator
131            rewrite_all_pairs(expr, shape, context).or_else(|_| {
132                rewrite_pair(
133                    &**lhs,
134                    &**rhs,
135                    PairParts::infix(&format!(" {} ", context.snippet(op.span))),
136                    context,
137                    shape,
138                    context.config.binop_separator(),
139                )
140            })
141        }
142        ast::ExprKind::Unary(op, ref subexpr) => rewrite_unary_op(context, op, subexpr, shape),
143        ast::ExprKind::Struct(ref struct_expr) => {
144            let ast::StructExpr {
145                qself,
146                fields,
147                path,
148                rest,
149            } = &**struct_expr;
150            rewrite_struct_lit(
151                context,
152                path,
153                qself,
154                fields,
155                rest,
156                &expr.attrs,
157                expr.span,
158                shape,
159            )
160        }
161        ast::ExprKind::Tup(ref items) => {
162            rewrite_tuple(context, items.iter(), expr.span, shape, items.len() == 1)
163        }
164        ast::ExprKind::Let(ref pat, ref expr, _span, _) => rewrite_let(context, shape, pat, expr),
165        ast::ExprKind::If(..)
166        | ast::ExprKind::ForLoop { .. }
167        | ast::ExprKind::Loop(..)
168        | ast::ExprKind::While(..) => to_control_flow(expr, expr_type)
169            .unknown_error()
170            .and_then(|control_flow| control_flow.rewrite_result(context, shape)),
171        ast::ExprKind::ConstBlock(ref anon_const) => {
172            let rewrite = match anon_const.value.kind {
173                ast::ExprKind::Block(ref block, opt_label) => {
174                    // Inner attributes are associated with the `ast::ExprKind::ConstBlock` node,
175                    // not the `ast::Block` node we're about to rewrite. To prevent dropping inner
176                    // attributes call `rewrite_block` directly.
177                    // See https://github.com/rust-lang/rustfmt/issues/6158
178                    rewrite_block(block, Some(&expr.attrs), opt_label, context, shape)?
179                }
180                _ => anon_const.rewrite_result(context, shape)?,
181            };
182            Ok(format!("const {}", rewrite))
183        }
184        ast::ExprKind::Block(ref block, opt_label) => {
185            match expr_type {
186                ExprType::Statement => {
187                    if is_unsafe_block(block) {
188                        rewrite_block(block, Some(&expr.attrs), opt_label, context, shape)
189                    } else if let Some(rw) =
190                        rewrite_empty_block(context, block, Some(&expr.attrs), opt_label, "", shape)
191                    {
192                        // Rewrite block without trying to put it in a single line.
193                        Ok(rw)
194                    } else {
195                        let prefix = block_prefix(context, block, shape)?;
196
197                        rewrite_block_with_visitor(
198                            context,
199                            &prefix,
200                            block,
201                            Some(&expr.attrs),
202                            opt_label,
203                            shape,
204                            true,
205                        )
206                    }
207                }
208                ExprType::SubExpression => {
209                    rewrite_block(block, Some(&expr.attrs), opt_label, context, shape)
210                }
211            }
212        }
213        ast::ExprKind::Match(ref cond, ref arms, kind) => {
214            rewrite_match(context, cond, arms, shape, expr.span, &expr.attrs, kind)
215        }
216        ast::ExprKind::Path(ref qself, ref path) => {
217            rewrite_path(context, PathContext::Expr, qself, path, shape)
218        }
219        ast::ExprKind::Assign(ref lhs, ref rhs, _) => {
220            rewrite_assignment(context, lhs, rhs, None, shape)
221        }
222        ast::ExprKind::AssignOp(ref op, ref lhs, ref rhs) => {
223            rewrite_assignment(context, lhs, rhs, Some(op), shape)
224        }
225        ast::ExprKind::Continue(ref opt_label) => {
226            let id_str = match *opt_label {
227                Some(label) => {
228                    // Ident lose the `r#` prefix in raw labels, so use the original snippet
229                    let label_name = context.snippet(label.ident.span);
230                    format!(" {}", label_name)
231                }
232                None => String::new(),
233            };
234            Ok(format!("continue{id_str}"))
235        }
236        ast::ExprKind::Break(ref opt_label, ref opt_expr) => {
237            let id_str = match *opt_label {
238                Some(label) => {
239                    // Ident lose the `r#` prefix in raw labels, so use the original snippet
240                    let label_name = context.snippet(label.ident.span);
241                    format!(" {}", label_name)
242                }
243                None => String::new(),
244            };
245
246            if let Some(ref expr) = *opt_expr {
247                rewrite_unary_prefix(context, &format!("break{id_str} "), &**expr, shape)
248            } else {
249                Ok(format!("break{id_str}"))
250            }
251        }
252        ast::ExprKind::Yield(ast::YieldKind::Prefix(ref opt_expr)) => {
253            if let Some(ref expr) = *opt_expr {
254                rewrite_unary_prefix(context, "yield ", &**expr, shape)
255            } else {
256                Ok("yield".to_string())
257            }
258        }
259        ast::ExprKind::Closure(ref cl) => closures::rewrite_closure(
260            &cl.binder,
261            cl.constness,
262            cl.capture_clause,
263            &cl.coroutine_kind,
264            cl.movability,
265            &cl.fn_decl,
266            &cl.body,
267            expr.span,
268            context,
269            shape,
270        ),
271        ast::ExprKind::Try(..)
272        | ast::ExprKind::Field(..)
273        | ast::ExprKind::MethodCall(..)
274        | ast::ExprKind::Await(_, _)
275        | ast::ExprKind::Use(_, _)
276        | ast::ExprKind::Yield(ast::YieldKind::Postfix(_)) => rewrite_chain(expr, context, shape),
277        ast::ExprKind::MacCall(ref mac) => {
278            rewrite_macro(mac, context, shape, MacroPosition::Expression).or_else(|_| {
279                wrap_str(
280                    context.snippet(expr.span).to_owned(),
281                    context.config.max_width(),
282                    shape,
283                )
284                .max_width_error(shape.width, expr.span)
285            })
286        }
287        ast::ExprKind::Ret(None) => Ok("return".to_owned()),
288        ast::ExprKind::Ret(Some(ref expr)) => {
289            rewrite_unary_prefix(context, "return ", &**expr, shape)
290        }
291        ast::ExprKind::Become(ref expr) => rewrite_unary_prefix(context, "become ", &**expr, shape),
292        ast::ExprKind::Yeet(None) => Ok("do yeet".to_owned()),
293        ast::ExprKind::Yeet(Some(ref expr)) => {
294            rewrite_unary_prefix(context, "do yeet ", &**expr, shape)
295        }
296        ast::ExprKind::AddrOf(borrow_kind, mutability, ref expr) => {
297            rewrite_expr_addrof(context, borrow_kind, mutability, expr, shape)
298        }
299        ast::ExprKind::Cast(ref expr, ref ty) => rewrite_pair(
300            &**expr,
301            &**ty,
302            PairParts::infix(" as "),
303            context,
304            shape,
305            SeparatorPlace::Front,
306        ),
307        ast::ExprKind::Index(ref expr, ref index, _) => {
308            rewrite_index(&**expr, &**index, context, shape)
309        }
310        ast::ExprKind::Repeat(ref expr, ref repeats) => rewrite_pair(
311            &**expr,
312            &*repeats.value,
313            PairParts::new("[", "; ", "]"),
314            context,
315            shape,
316            SeparatorPlace::Back,
317        ),
318        ast::ExprKind::Range(ref lhs, ref rhs, limits) => {
319            let delim = match limits {
320                ast::RangeLimits::HalfOpen => "..",
321                ast::RangeLimits::Closed => "..=",
322            };
323
324            fn needs_space_before_range(context: &RewriteContext<'_>, lhs: &ast::Expr) -> bool {
325                match lhs.kind {
326                    ast::ExprKind::Lit(token_lit) => lit_ends_in_dot(&token_lit, context),
327                    ast::ExprKind::Unary(_, ref expr) => needs_space_before_range(context, expr),
328                    ast::ExprKind::Binary(_, _, ref rhs_expr) => {
329                        needs_space_before_range(context, rhs_expr)
330                    }
331                    _ => false,
332                }
333            }
334
335            fn needs_space_after_range(rhs: &ast::Expr) -> bool {
336                // Don't format `.. ..` into `....`, which is invalid.
337                //
338                // This check is unnecessary for `lhs`, because a range
339                // starting from another range needs parentheses as `(x ..) ..`
340                // (`x .. ..` is a range from `x` to `..`).
341                matches!(rhs.kind, ast::ExprKind::Range(None, _, _))
342            }
343
344            let default_sp_delim = |lhs: Option<&ast::Expr>, rhs: Option<&ast::Expr>| {
345                let space_if = |b: bool| if b { " " } else { "" };
346
347                format!(
348                    "{}{}{}",
349                    lhs.map_or("", |lhs| space_if(needs_space_before_range(context, lhs))),
350                    delim,
351                    rhs.map_or("", |rhs| space_if(needs_space_after_range(rhs))),
352                )
353            };
354
355            match (lhs.as_ref().map(|x| &**x), rhs.as_ref().map(|x| &**x)) {
356                (Some(lhs), Some(rhs)) => {
357                    let sp_delim = if context.config.spaces_around_ranges() {
358                        format!(" {delim} ")
359                    } else {
360                        default_sp_delim(Some(lhs), Some(rhs))
361                    };
362                    rewrite_pair(
363                        &*lhs,
364                        &*rhs,
365                        PairParts::infix(&sp_delim),
366                        context,
367                        shape,
368                        context.config.binop_separator(),
369                    )
370                }
371                (None, Some(rhs)) => {
372                    let sp_delim = if context.config.spaces_around_ranges() {
373                        format!("{delim} ")
374                    } else {
375                        default_sp_delim(None, Some(rhs))
376                    };
377                    rewrite_unary_prefix(context, &sp_delim, &*rhs, shape)
378                }
379                (Some(lhs), None) => {
380                    let sp_delim = if context.config.spaces_around_ranges() {
381                        format!(" {delim}")
382                    } else {
383                        default_sp_delim(Some(lhs), None)
384                    };
385                    rewrite_unary_suffix(context, &sp_delim, &*lhs, shape)
386                }
387                (None, None) => Ok(delim.to_owned()),
388            }
389        }
390        // We do not format these expressions yet, but they should still
391        // satisfy our width restrictions.
392        // Style Guide RFC for InlineAsm variant pending
393        // https://github.com/rust-dev-tools/fmt-rfcs/issues/152
394        ast::ExprKind::InlineAsm(..) => Ok(context.snippet(expr.span).to_owned()),
395        ast::ExprKind::TryBlock(ref block, None) => {
396            if let rw @ Ok(_) =
397                rewrite_single_line_block(context, "try ", block, Some(&expr.attrs), None, shape)
398            {
399                rw
400            } else {
401                // FIXME: 9 sounds like `"do catch ".len()`, so may predate the rename
402                // 9 = `try `
403                let budget = shape.width.saturating_sub(9);
404                Ok(format!(
405                    "{}{}",
406                    "try ",
407                    rewrite_block(
408                        block,
409                        Some(&expr.attrs),
410                        None,
411                        context,
412                        Shape::legacy(budget, shape.indent)
413                    )?
414                ))
415            }
416        }
417        ast::ExprKind::TryBlock(ref block, Some(ref ty)) => {
418            let keyword = "try bikeshed ";
419            // 2 = " {".len()
420            let ty_shape = shape
421                .shrink_left(keyword.len(), expr.span)
422                .and_then(|shape| shape.sub_width(2, expr.span))?;
423
424            let ty_str = ty.rewrite_result(context, ty_shape)?;
425            let prefix = format!("{keyword}{ty_str} ");
426            if let rw @ Ok(_) =
427                rewrite_single_line_block(context, &prefix, block, Some(&expr.attrs), None, shape)
428            {
429                rw
430            } else {
431                let budget = shape.width.saturating_sub(prefix.len());
432                Ok(format!(
433                    "{prefix}{}",
434                    rewrite_block(
435                        block,
436                        Some(&expr.attrs),
437                        None,
438                        context,
439                        Shape::legacy(budget, shape.indent)
440                    )?
441                ))
442            }
443        }
444        ast::ExprKind::Gen(capture_by, ref block, ref kind, _) => {
445            let mover = if matches!(capture_by, ast::CaptureBy::Value { .. }) {
446                "move "
447            } else {
448                ""
449            };
450            if let rw @ Ok(_) = rewrite_single_line_block(
451                context,
452                format!("{kind} {mover}").as_str(),
453                block,
454                Some(&expr.attrs),
455                None,
456                shape,
457            ) {
458                rw
459            } else {
460                // 6 = `async `
461                let budget = shape.width.saturating_sub(6);
462                Ok(format!(
463                    "{kind} {mover}{}",
464                    rewrite_block(
465                        block,
466                        Some(&expr.attrs),
467                        None,
468                        context,
469                        Shape::legacy(budget, shape.indent)
470                    )?
471                ))
472            }
473        }
474        ast::ExprKind::Underscore => Ok("_".to_owned()),
475        ast::ExprKind::FormatArgs(..)
476        | ast::ExprKind::Type(..)
477        | ast::ExprKind::IncludedBytes(..)
478        | ast::ExprKind::OffsetOf(..)
479        | ast::ExprKind::UnsafeBinderCast(..) => {
480            // These don't normally occur in the AST because macros aren't expanded. However,
481            // rustfmt tries to parse macro arguments when formatting macros, so it's not totally
482            // impossible for rustfmt to come across one of these nodes when formatting a file.
483            // Also, rustfmt might get passed the output from `-Zunpretty=expanded`.
484            Err(RewriteError::Unknown)
485        }
486        ast::ExprKind::Err(_) | ast::ExprKind::Dummy => Err(RewriteError::Unknown),
487    };
488
489    expr_rw
490        .map(|expr_str| recover_comment_removed(expr_str, expr.span, context))
491        .and_then(|expr_str| {
492            let attrs = outer_attributes(&expr.attrs);
493            let attrs_str = attrs.rewrite_result(context, shape)?;
494            let span = mk_sp(
495                attrs.last().map_or(expr.span.lo(), |attr| attr.span.hi()),
496                expr.span.lo(),
497            );
498            combine_strs_with_missing_comments(context, &attrs_str, &expr_str, span, shape, false)
499        })
500}
501
502pub(crate) fn rewrite_array<'a, T: 'a + IntoOverflowableItem<'a>>(
503    name: &'a str,
504    exprs: impl Iterator<Item = &'a T>,
505    span: Span,
506    context: &'a RewriteContext<'_>,
507    shape: Shape,
508    force_separator_tactic: Option<SeparatorTactic>,
509    delim_token: Option<Delimiter>,
510) -> RewriteResult {
511    overflow::rewrite_with_square_brackets(
512        context,
513        name,
514        exprs,
515        shape,
516        span,
517        force_separator_tactic,
518        delim_token,
519    )
520}
521
522fn rewrite_empty_block(
523    context: &RewriteContext<'_>,
524    block: &ast::Block,
525    attrs: Option<&[ast::Attribute]>,
526    label: Option<ast::Label>,
527    prefix: &str,
528    shape: Shape,
529) -> Option<String> {
530    if block_has_statements(block) {
531        return None;
532    }
533
534    let label_str = rewrite_label(context, label);
535    if attrs.map_or(false, |a| !inner_attributes(a).is_empty()) {
536        return None;
537    }
538
539    if !block_contains_comment(context, block) && shape.width >= 2 {
540        return Some(format!("{prefix}{label_str}{{}}"));
541    }
542
543    // If a block contains only a single-line comment, then leave it on one line.
544    let user_str = context.snippet(block.span);
545    let user_str = user_str.trim();
546    if user_str.starts_with('{') && user_str.ends_with('}') {
547        let comment_str = user_str[1..user_str.len() - 1].trim();
548        if block.stmts.is_empty()
549            && !comment_str.contains('\n')
550            && !comment_str.starts_with("//")
551            && comment_str.len() + 4 <= shape.width
552        {
553            return Some(format!("{prefix}{label_str}{{ {comment_str} }}"));
554        }
555    }
556
557    None
558}
559
560fn block_prefix(context: &RewriteContext<'_>, block: &ast::Block, shape: Shape) -> RewriteResult {
561    Ok(match block.rules {
562        ast::BlockCheckMode::Unsafe(..) => {
563            let snippet = context.snippet(block.span);
564            let open_pos = snippet.find_uncommented("{").unknown_error()?;
565            // Extract comment between unsafe and block start.
566            let trimmed = &snippet[6..open_pos].trim();
567
568            if !trimmed.is_empty() {
569                // 9 = "unsafe  {".len(), 7 = "unsafe ".len()
570                let budget = shape
571                    .width
572                    .checked_sub(9)
573                    .max_width_error(shape.width, block.span)?;
574                format!(
575                    "unsafe {} ",
576                    rewrite_comment(
577                        trimmed,
578                        true,
579                        Shape::legacy(budget, shape.indent + 7),
580                        context.config,
581                    )?
582                )
583            } else {
584                "unsafe ".to_owned()
585            }
586        }
587        ast::BlockCheckMode::Default => String::new(),
588    })
589}
590
591fn rewrite_single_line_block(
592    context: &RewriteContext<'_>,
593    prefix: &str,
594    block: &ast::Block,
595    attrs: Option<&[ast::Attribute]>,
596    label: Option<ast::Label>,
597    shape: Shape,
598) -> RewriteResult {
599    if let Some(block_expr) = stmt::Stmt::from_simple_block(context, block, attrs) {
600        let expr_shape = shape.offset_left(last_line_width(prefix), block_expr.span())?;
601        let expr_str = block_expr.rewrite_result(context, expr_shape)?;
602        let label_str = rewrite_label(context, label);
603        let result = format!("{prefix}{label_str}{{ {expr_str} }}");
604        if result.len() <= shape.width && !result.contains('\n') {
605            return Ok(result);
606        }
607    }
608    Err(RewriteError::Unknown)
609}
610
611pub(crate) fn rewrite_block_with_visitor(
612    context: &RewriteContext<'_>,
613    prefix: &str,
614    block: &ast::Block,
615    attrs: Option<&[ast::Attribute]>,
616    label: Option<ast::Label>,
617    shape: Shape,
618    has_braces: bool,
619) -> RewriteResult {
620    if let Some(rw_str) = rewrite_empty_block(context, block, attrs, label, prefix, shape) {
621        return Ok(rw_str);
622    }
623
624    let mut visitor = FmtVisitor::from_context(context);
625    visitor.block_indent = shape.indent;
626    visitor.is_if_else_block = context.is_if_else_block();
627    visitor.is_loop_block = context.is_loop_block();
628    match (block.rules, label) {
629        (ast::BlockCheckMode::Unsafe(..), _) | (ast::BlockCheckMode::Default, Some(_)) => {
630            let snippet = context.snippet(block.span);
631            let open_pos = snippet.find_uncommented("{").unknown_error()?;
632            visitor.last_pos = block.span.lo() + BytePos(open_pos as u32)
633        }
634        (ast::BlockCheckMode::Default, None) => visitor.last_pos = block.span.lo(),
635    }
636
637    let inner_attrs = attrs.map(inner_attributes);
638    let label_str = rewrite_label(context, label);
639    visitor.visit_block(block, inner_attrs.as_deref(), has_braces);
640    let visitor_context = visitor.get_context();
641    context
642        .skipped_range
643        .borrow_mut()
644        .append(&mut visitor_context.skipped_range.borrow_mut());
645    Ok(format!("{}{}{}", prefix, label_str, visitor.buffer))
646}
647
648impl Rewrite for ast::Block {
649    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
650        self.rewrite_result(context, shape).ok()
651    }
652
653    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
654        rewrite_block(self, None, None, context, shape)
655    }
656}
657
658fn rewrite_block(
659    block: &ast::Block,
660    attrs: Option<&[ast::Attribute]>,
661    label: Option<ast::Label>,
662    context: &RewriteContext<'_>,
663    shape: Shape,
664) -> RewriteResult {
665    rewrite_block_inner(block, attrs, label, true, context, shape)
666}
667
668fn rewrite_block_inner(
669    block: &ast::Block,
670    attrs: Option<&[ast::Attribute]>,
671    label: Option<ast::Label>,
672    allow_single_line: bool,
673    context: &RewriteContext<'_>,
674    shape: Shape,
675) -> RewriteResult {
676    let prefix = block_prefix(context, block, shape)?;
677
678    // shape.width is used only for the single line case: either the empty block `{}`,
679    // or an unsafe expression `unsafe { e }`.
680    if let Some(rw_str) = rewrite_empty_block(context, block, attrs, label, &prefix, shape) {
681        return Ok(rw_str);
682    }
683
684    let result_str =
685        rewrite_block_with_visitor(context, &prefix, block, attrs, label, shape, true)?;
686    if allow_single_line && result_str.lines().count() <= 3 {
687        if let rw @ Ok(_) = rewrite_single_line_block(context, &prefix, block, attrs, label, shape)
688        {
689            return rw;
690        }
691    }
692    Ok(result_str)
693}
694
695/// Rewrite the divergent block of a `let-else` statement.
696pub(crate) fn rewrite_let_else_block(
697    block: &ast::Block,
698    allow_single_line: bool,
699    context: &RewriteContext<'_>,
700    shape: Shape,
701) -> RewriteResult {
702    rewrite_block_inner(block, None, None, allow_single_line, context, shape)
703}
704
705// Rewrite condition if the given expression has one.
706pub(crate) fn rewrite_cond(
707    context: &RewriteContext<'_>,
708    expr: &ast::Expr,
709    shape: Shape,
710) -> Option<String> {
711    match expr.kind {
712        ast::ExprKind::Match(ref cond, _, MatchKind::Prefix) => {
713            // `match `cond` {`
714            let cond_shape = match context.config.indent_style() {
715                IndentStyle::Visual => shape.shrink_left_opt(6).and_then(|s| s.sub_width_opt(2))?,
716                IndentStyle::Block => shape.offset_left_opt(8)?,
717            };
718            cond.rewrite(context, cond_shape)
719        }
720        _ => to_control_flow(expr, ExprType::SubExpression).and_then(|control_flow| {
721            let alt_block_sep =
722                String::from("\n") + &shape.indent.block_only().to_string(context.config);
723            control_flow
724                .rewrite_cond(context, shape, &alt_block_sep)
725                .ok()
726                .map(|rw| rw.0)
727        }),
728    }
729}
730
731// Abstraction over control flow expressions
732#[derive(Debug)]
733struct ControlFlow<'a> {
734    cond: Option<&'a ast::Expr>,
735    block: &'a ast::Block,
736    else_block: Option<&'a ast::Expr>,
737    label: Option<ast::Label>,
738    pat: Option<&'a ast::Pat>,
739    keyword: &'a str,
740    matcher: &'a str,
741    connector: &'a str,
742    allow_single_line: bool,
743    // HACK: `true` if this is an `if` expression in an `else if`.
744    nested_if: bool,
745    is_loop: bool,
746    span: Span,
747}
748
749fn extract_pats_and_cond(expr: &ast::Expr) -> (Option<&ast::Pat>, &ast::Expr) {
750    match expr.kind {
751        ast::ExprKind::Let(ref pat, ref cond, _, _) => (Some(pat), cond),
752        _ => (None, expr),
753    }
754}
755
756// FIXME: Refactor this.
757fn to_control_flow(expr: &ast::Expr, expr_type: ExprType) -> Option<ControlFlow<'_>> {
758    match expr.kind {
759        ast::ExprKind::If(ref cond, ref if_block, ref else_block) => {
760            let (pat, cond) = extract_pats_and_cond(cond);
761            Some(ControlFlow::new_if(
762                cond,
763                pat,
764                if_block,
765                else_block.as_ref().map(|e| &**e),
766                expr_type == ExprType::SubExpression,
767                false,
768                expr.span,
769            ))
770        }
771        ast::ExprKind::ForLoop {
772            ref pat,
773            ref iter,
774            ref body,
775            label,
776            kind,
777        } => Some(ControlFlow::new_for(
778            pat, iter, body, label, expr.span, kind,
779        )),
780        ast::ExprKind::Loop(ref block, label, _) => {
781            Some(ControlFlow::new_loop(block, label, expr.span))
782        }
783        ast::ExprKind::While(ref cond, ref block, label) => {
784            let (pat, cond) = extract_pats_and_cond(cond);
785            Some(ControlFlow::new_while(pat, cond, block, label, expr.span))
786        }
787        _ => None,
788    }
789}
790
791fn choose_matcher(pat: Option<&ast::Pat>) -> &'static str {
792    pat.map_or("", |_| "let")
793}
794
795impl<'a> ControlFlow<'a> {
796    fn new_if(
797        cond: &'a ast::Expr,
798        pat: Option<&'a ast::Pat>,
799        block: &'a ast::Block,
800        else_block: Option<&'a ast::Expr>,
801        allow_single_line: bool,
802        nested_if: bool,
803        span: Span,
804    ) -> ControlFlow<'a> {
805        let matcher = choose_matcher(pat);
806        ControlFlow {
807            cond: Some(cond),
808            block,
809            else_block,
810            label: None,
811            pat,
812            keyword: "if",
813            matcher,
814            connector: " =",
815            allow_single_line,
816            nested_if,
817            is_loop: false,
818            span,
819        }
820    }
821
822    fn new_loop(block: &'a ast::Block, label: Option<ast::Label>, span: Span) -> ControlFlow<'a> {
823        ControlFlow {
824            cond: None,
825            block,
826            else_block: None,
827            label,
828            pat: None,
829            keyword: "loop",
830            matcher: "",
831            connector: "",
832            allow_single_line: false,
833            nested_if: false,
834            is_loop: true,
835            span,
836        }
837    }
838
839    fn new_while(
840        pat: Option<&'a ast::Pat>,
841        cond: &'a ast::Expr,
842        block: &'a ast::Block,
843        label: Option<ast::Label>,
844        span: Span,
845    ) -> ControlFlow<'a> {
846        let matcher = choose_matcher(pat);
847        ControlFlow {
848            cond: Some(cond),
849            block,
850            else_block: None,
851            label,
852            pat,
853            keyword: "while",
854            matcher,
855            connector: " =",
856            allow_single_line: false,
857            nested_if: false,
858            is_loop: true,
859            span,
860        }
861    }
862
863    fn new_for(
864        pat: &'a ast::Pat,
865        cond: &'a ast::Expr,
866        block: &'a ast::Block,
867        label: Option<ast::Label>,
868        span: Span,
869        kind: ForLoopKind,
870    ) -> ControlFlow<'a> {
871        ControlFlow {
872            cond: Some(cond),
873            block,
874            else_block: None,
875            label,
876            pat: Some(pat),
877            keyword: match kind {
878                ForLoopKind::For => "for",
879                ForLoopKind::ForAwait => "for await",
880            },
881            matcher: "",
882            connector: " in",
883            allow_single_line: false,
884            nested_if: false,
885            is_loop: true,
886            span,
887        }
888    }
889
890    fn rewrite_single_line(
891        &self,
892        pat_expr_str: &str,
893        context: &RewriteContext<'_>,
894        width: usize,
895    ) -> Option<String> {
896        assert!(self.allow_single_line);
897        let else_block = self.else_block?;
898        let fixed_cost = self.keyword.len() + "  {  } else {  }".len();
899
900        if let ast::ExprKind::Block(ref else_node, _) = else_block.kind {
901            let (if_expr, else_expr) = match (
902                stmt::Stmt::from_simple_block(context, self.block, None),
903                stmt::Stmt::from_simple_block(context, else_node, None),
904                pat_expr_str.contains('\n'),
905            ) {
906                (Some(if_expr), Some(else_expr), false) => (if_expr, else_expr),
907                _ => return None,
908            };
909
910            let new_width = width.checked_sub(pat_expr_str.len() + fixed_cost)?;
911            let if_str = if_expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?;
912
913            let new_width = new_width.checked_sub(if_str.len())?;
914            let else_str = else_expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?;
915
916            if if_str.contains('\n') || else_str.contains('\n') {
917                return None;
918            }
919
920            let result = format!(
921                "{} {} {{ {} }} else {{ {} }}",
922                self.keyword, pat_expr_str, if_str, else_str
923            );
924
925            if result.len() <= width {
926                return Some(result);
927            }
928        }
929
930        None
931    }
932}
933
934/// Returns `true` if the last line of pat_str has leading whitespace and it is wider than the
935/// shape's indent.
936fn last_line_offsetted(start_column: usize, pat_str: &str) -> bool {
937    let mut leading_whitespaces = 0;
938    for c in pat_str.chars().rev() {
939        match c {
940            '\n' => break,
941            _ if c.is_whitespace() => leading_whitespaces += 1,
942            _ => leading_whitespaces = 0,
943        }
944    }
945    leading_whitespaces > start_column
946}
947
948impl<'a> ControlFlow<'a> {
949    fn rewrite_pat_expr(
950        &self,
951        context: &RewriteContext<'_>,
952        expr: &ast::Expr,
953        shape: Shape,
954        offset: usize,
955    ) -> RewriteResult {
956        debug!("rewrite_pat_expr {:?} {:?} {:?}", shape, self.pat, expr);
957
958        let cond_shape = shape.offset_left(offset, expr.span)?;
959        if let Some(pat) = self.pat {
960            let matcher = if self.matcher.is_empty() {
961                self.matcher.to_owned()
962            } else {
963                format!("{} ", self.matcher)
964            };
965            let pat_shape = cond_shape
966                .offset_left(matcher.len(), pat.span)?
967                .sub_width(self.connector.len(), pat.span)?;
968            let pat_string = pat.rewrite_result(context, pat_shape)?;
969            let comments_lo = context
970                .snippet_provider
971                .span_after(self.span.with_lo(pat.span.hi()), self.connector.trim());
972            let comments_span = mk_sp(comments_lo, expr.span.lo());
973            return rewrite_assign_rhs_with_comments(
974                context,
975                &format!("{}{}{}", matcher, pat_string, self.connector),
976                expr,
977                cond_shape,
978                &RhsAssignKind::Expr(&expr.kind, expr.span),
979                RhsTactics::Default,
980                comments_span,
981                true,
982            );
983        }
984
985        let expr_rw = expr.rewrite_result(context, cond_shape);
986        // The expression may (partially) fit on the current line.
987        // We do not allow splitting between `if` and condition.
988        if self.keyword == "if" || expr_rw.is_ok() {
989            return expr_rw;
990        }
991
992        // The expression won't fit on the current line, jump to next.
993        let nested_shape = shape
994            .block_indent(context.config.tab_spaces())
995            .with_max_width(context.config);
996        let nested_indent_str = nested_shape.indent.to_string_with_newline(context.config);
997        expr.rewrite_result(context, nested_shape)
998            .map(|expr_rw| format!("{}{}", nested_indent_str, expr_rw))
999    }
1000
1001    fn rewrite_cond(
1002        &self,
1003        context: &RewriteContext<'_>,
1004        shape: Shape,
1005        alt_block_sep: &str,
1006    ) -> Result<(String, usize), RewriteError> {
1007        // Do not take the rhs overhead from the upper expressions into account
1008        // when rewriting pattern.
1009        let new_width = context.budget(shape.used_width());
1010        let fresh_shape = Shape {
1011            width: new_width,
1012            ..shape
1013        };
1014        let constr_shape = if self.nested_if {
1015            // We are part of an if-elseif-else chain. Our constraints are tightened.
1016            // 7 = "} else " .len()
1017            fresh_shape.offset_left(7, self.span)?
1018        } else {
1019            fresh_shape
1020        };
1021
1022        let label_string = rewrite_label(context, self.label);
1023        // 1 = space after keyword.
1024        let offset = self.keyword.len() + label_string.len() + 1;
1025
1026        let pat_expr_string = match self.cond {
1027            Some(cond) => self.rewrite_pat_expr(context, cond, constr_shape, offset)?,
1028            None => String::new(),
1029        };
1030
1031        let brace_overhead =
1032            if context.config.control_brace_style() != ControlBraceStyle::AlwaysNextLine {
1033                // 2 = ` {`
1034                2
1035            } else {
1036                0
1037            };
1038        let one_line_budget = context
1039            .config
1040            .max_width()
1041            .saturating_sub(constr_shape.used_width() + offset + brace_overhead);
1042        let force_newline_brace = (pat_expr_string.contains('\n')
1043            || pat_expr_string.len() > one_line_budget)
1044            && (!last_line_extendable(&pat_expr_string)
1045                || last_line_offsetted(shape.used_width(), &pat_expr_string));
1046
1047        // Try to format if-else on single line.
1048        if self.allow_single_line && context.config.single_line_if_else_max_width() > 0 {
1049            let trial = self.rewrite_single_line(&pat_expr_string, context, shape.width);
1050
1051            if let Some(cond_str) = trial {
1052                if cond_str.len() <= context.config.single_line_if_else_max_width() {
1053                    return Ok((cond_str, 0));
1054                }
1055            }
1056        }
1057
1058        let cond_span = if let Some(cond) = self.cond {
1059            cond.span
1060        } else {
1061            mk_sp(self.block.span.lo(), self.block.span.lo())
1062        };
1063
1064        // `for event in event`
1065        // Do not include label in the span.
1066        let lo = self
1067            .label
1068            .map_or(self.span.lo(), |label| label.ident.span.hi());
1069        let between_kwd_cond = mk_sp(
1070            context
1071                .snippet_provider
1072                .span_after(mk_sp(lo, self.span.hi()), self.keyword.trim()),
1073            if self.pat.is_none() {
1074                cond_span.lo()
1075            } else if self.matcher.is_empty() {
1076                self.pat.unwrap().span.lo()
1077            } else {
1078                context
1079                    .snippet_provider
1080                    .span_before(self.span, self.matcher.trim())
1081            },
1082        );
1083
1084        let between_kwd_cond_comment = extract_comment(between_kwd_cond, context, shape);
1085
1086        let after_cond_comment =
1087            extract_comment(mk_sp(cond_span.hi(), self.block.span.lo()), context, shape);
1088
1089        let block_sep = if self.cond.is_none() && between_kwd_cond_comment.is_some() {
1090            ""
1091        } else if context.config.control_brace_style() == ControlBraceStyle::AlwaysNextLine
1092            || force_newline_brace
1093        {
1094            alt_block_sep
1095        } else {
1096            " "
1097        };
1098
1099        let used_width = if pat_expr_string.contains('\n') {
1100            last_line_width(&pat_expr_string)
1101        } else {
1102            // 2 = spaces after keyword and condition.
1103            label_string.len() + self.keyword.len() + pat_expr_string.len() + 2
1104        };
1105
1106        Ok((
1107            format!(
1108                "{}{}{}{}{}",
1109                label_string,
1110                self.keyword,
1111                between_kwd_cond_comment.as_ref().map_or(
1112                    if pat_expr_string.is_empty() || pat_expr_string.starts_with('\n') {
1113                        ""
1114                    } else {
1115                        " "
1116                    },
1117                    |s| &**s,
1118                ),
1119                pat_expr_string,
1120                after_cond_comment.as_ref().map_or(block_sep, |s| &**s)
1121            ),
1122            used_width,
1123        ))
1124    }
1125}
1126
1127/// Rewrite the `else` keyword with surrounding comments.
1128///
1129/// force_newline_else: whether or not to rewrite the `else` keyword on a newline.
1130/// is_last: true if this is an `else` and `false` if this is an `else if` block.
1131/// context: rewrite context
1132/// span: Span between the end of the last expression and the start of the else block,
1133///       which contains the `else` keyword
1134/// shape: Shape
1135pub(crate) fn rewrite_else_kw_with_comments(
1136    force_newline_else: bool,
1137    is_last: bool,
1138    context: &RewriteContext<'_>,
1139    span: Span,
1140    shape: Shape,
1141) -> String {
1142    let else_kw_lo = context.snippet_provider.span_before(span, "else");
1143    let before_else_kw = mk_sp(span.lo(), else_kw_lo);
1144    let before_else_kw_comment = extract_comment(before_else_kw, context, shape);
1145
1146    let else_kw_hi = context.snippet_provider.span_after(span, "else");
1147    let after_else_kw = mk_sp(else_kw_hi, span.hi());
1148    let after_else_kw_comment = extract_comment(after_else_kw, context, shape);
1149
1150    let newline_sep = &shape.indent.to_string_with_newline(context.config);
1151    let before_sep = match context.config.control_brace_style() {
1152        _ if force_newline_else => newline_sep.as_ref(),
1153        ControlBraceStyle::AlwaysNextLine | ControlBraceStyle::ClosingNextLine => {
1154            newline_sep.as_ref()
1155        }
1156        ControlBraceStyle::AlwaysSameLine => " ",
1157    };
1158    let after_sep = match context.config.control_brace_style() {
1159        ControlBraceStyle::AlwaysNextLine if is_last => newline_sep.as_ref(),
1160        _ => " ",
1161    };
1162
1163    format!(
1164        "{}else{}",
1165        before_else_kw_comment.as_ref().map_or(before_sep, |s| &**s),
1166        after_else_kw_comment.as_ref().map_or(after_sep, |s| &**s),
1167    )
1168}
1169
1170impl<'a> Rewrite for ControlFlow<'a> {
1171    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
1172        self.rewrite_result(context, shape).ok()
1173    }
1174
1175    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
1176        debug!("ControlFlow::rewrite {:?} {:?}", self, shape);
1177
1178        let alt_block_sep = &shape.indent.to_string_with_newline(context.config);
1179        let (cond_str, used_width) = self.rewrite_cond(context, shape, alt_block_sep)?;
1180        // If `used_width` is 0, it indicates that whole control flow is written in a single line.
1181        if used_width == 0 {
1182            return Ok(cond_str);
1183        }
1184
1185        let block_width = shape.width.saturating_sub(used_width);
1186        // This is used only for the empty block case: `{}`. So, we use 1 if we know
1187        // we should avoid the single line case.
1188        let block_width = if self.else_block.is_some() || self.nested_if {
1189            min(1, block_width)
1190        } else {
1191            block_width
1192        };
1193        let block_shape = Shape {
1194            width: block_width,
1195            ..shape
1196        };
1197        let block_str = {
1198            let old_val = context.is_if_else_block.replace(self.else_block.is_some());
1199            let old_is_loop = context.is_loop_block.replace(self.is_loop);
1200            let result =
1201                rewrite_block_with_visitor(context, "", self.block, None, None, block_shape, true);
1202            context.is_loop_block.replace(old_is_loop);
1203            context.is_if_else_block.replace(old_val);
1204            result?
1205        };
1206
1207        let mut result = format!("{cond_str}{block_str}");
1208
1209        if let Some(else_block) = self.else_block {
1210            let shape = Shape::indented(shape.indent, context.config);
1211            let mut last_in_chain = false;
1212            let rewrite = match else_block.kind {
1213                // If the else expression is another if-else expression, prevent it
1214                // from being formatted on a single line.
1215                // Note how we're passing the original shape, as the
1216                // cost of "else" should not cascade.
1217                ast::ExprKind::If(ref cond, ref if_block, ref next_else_block) => {
1218                    let (pats, cond) = extract_pats_and_cond(cond);
1219                    ControlFlow::new_if(
1220                        cond,
1221                        pats,
1222                        if_block,
1223                        next_else_block.as_ref().map(|e| &**e),
1224                        false,
1225                        true,
1226                        mk_sp(else_block.span.lo(), self.span.hi()),
1227                    )
1228                    .rewrite_result(context, shape)
1229                }
1230                _ => {
1231                    last_in_chain = true;
1232                    // When rewriting a block, the width is only used for single line
1233                    // blocks, passing 1 lets us avoid that.
1234                    let else_shape = Shape {
1235                        width: min(1, shape.width),
1236                        ..shape
1237                    };
1238                    format_expr(else_block, ExprType::Statement, context, else_shape)
1239                }
1240            };
1241
1242            let else_kw = rewrite_else_kw_with_comments(
1243                false,
1244                last_in_chain,
1245                context,
1246                self.block.span.between(else_block.span),
1247                shape,
1248            );
1249            result.push_str(&else_kw);
1250            result.push_str(&rewrite?);
1251        }
1252
1253        Ok(result)
1254    }
1255}
1256
1257fn rewrite_label(context: &RewriteContext<'_>, opt_label: Option<ast::Label>) -> Cow<'static, str> {
1258    match opt_label {
1259        Some(label) => Cow::from(format!("{}: ", context.snippet(label.ident.span))),
1260        None => Cow::from(""),
1261    }
1262}
1263
1264fn extract_comment(span: Span, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
1265    match rewrite_missing_comment(span, shape, context) {
1266        Ok(ref comment) if !comment.is_empty() => Some(format!(
1267            "{indent}{comment}{indent}",
1268            indent = shape.indent.to_string_with_newline(context.config)
1269        )),
1270        _ => None,
1271    }
1272}
1273
1274pub(crate) fn block_contains_comment(context: &RewriteContext<'_>, block: &ast::Block) -> bool {
1275    contains_comment(context.snippet(block.span))
1276}
1277
1278// Checks that a block contains no statements, an expression and no comments or
1279// attributes.
1280// FIXME: incorrectly returns false when comment is contained completely within
1281// the expression.
1282pub(crate) fn is_simple_block(
1283    context: &RewriteContext<'_>,
1284    block: &ast::Block,
1285    attrs: Option<&[ast::Attribute]>,
1286) -> bool {
1287    block.stmts.len() == 1
1288        && stmt_is_expr(&block.stmts[0])
1289        && !block_contains_comment(context, block)
1290        && attrs.map_or(true, |a| a.is_empty())
1291}
1292
1293/// Checks whether a block contains at most one statement or expression, and no
1294/// comments or attributes.
1295pub(crate) fn is_simple_block_stmt(
1296    context: &RewriteContext<'_>,
1297    block: &ast::Block,
1298    attrs: Option<&[ast::Attribute]>,
1299) -> bool {
1300    block.stmts.len() <= 1
1301        && !block_contains_comment(context, block)
1302        && attrs.map_or(true, |a| a.is_empty())
1303}
1304
1305fn block_has_statements(block: &ast::Block) -> bool {
1306    block
1307        .stmts
1308        .iter()
1309        .any(|stmt| !matches!(stmt.kind, ast::StmtKind::Empty))
1310}
1311
1312/// Checks whether a block contains no statements, expressions, comments, or
1313/// inner attributes.
1314pub(crate) fn is_empty_block(
1315    context: &RewriteContext<'_>,
1316    block: &ast::Block,
1317    attrs: Option<&[ast::Attribute]>,
1318) -> bool {
1319    !block_has_statements(block)
1320        && !block_contains_comment(context, block)
1321        && attrs.map_or(true, |a| inner_attributes(a).is_empty())
1322}
1323
1324pub(crate) fn stmt_is_expr(stmt: &ast::Stmt) -> bool {
1325    matches!(stmt.kind, ast::StmtKind::Expr(..))
1326}
1327
1328pub(crate) fn is_unsafe_block(block: &ast::Block) -> bool {
1329    matches!(block.rules, ast::BlockCheckMode::Unsafe(..))
1330}
1331
1332pub(crate) fn rewrite_literal(
1333    context: &RewriteContext<'_>,
1334    token_lit: token::Lit,
1335    span: Span,
1336    shape: Shape,
1337) -> RewriteResult {
1338    match token_lit.kind {
1339        token::LitKind::Str => rewrite_string_lit(context, span, shape),
1340        token::LitKind::Integer => rewrite_int_lit(context, token_lit, span, shape),
1341        token::LitKind::Float => rewrite_float_lit(context, token_lit, span, shape),
1342        _ => wrap_str(
1343            context.snippet(span).to_owned(),
1344            context.config.max_width(),
1345            shape,
1346        )
1347        .max_width_error(shape.width, span),
1348    }
1349}
1350
1351fn rewrite_string_lit(context: &RewriteContext<'_>, span: Span, shape: Shape) -> RewriteResult {
1352    let string_lit = context.snippet(span);
1353
1354    if !context.config.format_strings() {
1355        if string_lit
1356            .lines()
1357            .dropping_back(1)
1358            .all(|line| line.ends_with('\\'))
1359            && context.config.style_edition() >= StyleEdition::Edition2024
1360        {
1361            return Ok(string_lit.to_owned());
1362        } else {
1363            return wrap_str(string_lit.to_owned(), context.config.max_width(), shape)
1364                .max_width_error(shape.width, span);
1365        }
1366    }
1367
1368    // Remove the quote characters.
1369    let str_lit = &string_lit[1..string_lit.len() - 1];
1370
1371    rewrite_string(
1372        str_lit,
1373        &StringFormat::new(shape.visual_indent(0), context.config),
1374        shape.width.saturating_sub(2),
1375    )
1376    .max_width_error(shape.width, span)
1377}
1378
1379fn rewrite_int_lit(
1380    context: &RewriteContext<'_>,
1381    token_lit: token::Lit,
1382    span: Span,
1383    shape: Shape,
1384) -> RewriteResult {
1385    if token_lit.is_semantic_float() {
1386        return rewrite_float_lit(context, token_lit, span, shape);
1387    }
1388
1389    let symbol = token_lit.symbol.as_str();
1390
1391    if let Some(symbol_stripped) = symbol.strip_prefix("0x") {
1392        let hex_lit = match context.config.hex_literal_case() {
1393            HexLiteralCase::Preserve => None,
1394            HexLiteralCase::Upper => Some(symbol_stripped.to_ascii_uppercase()),
1395            HexLiteralCase::Lower => Some(symbol_stripped.to_ascii_lowercase()),
1396        };
1397        if let Some(hex_lit) = hex_lit {
1398            return wrap_str(
1399                format!(
1400                    "0x{}{}",
1401                    hex_lit,
1402                    token_lit.suffix.as_ref().map_or("", |s| s.as_str())
1403                ),
1404                context.config.max_width(),
1405                shape,
1406            )
1407            .max_width_error(shape.width, span);
1408        }
1409    }
1410
1411    wrap_str(
1412        context.snippet(span).to_owned(),
1413        context.config.max_width(),
1414        shape,
1415    )
1416    .max_width_error(shape.width, span)
1417}
1418
1419fn rewrite_float_lit(
1420    context: &RewriteContext<'_>,
1421    token_lit: token::Lit,
1422    span: Span,
1423    shape: Shape,
1424) -> RewriteResult {
1425    if matches!(
1426        context.config.float_literal_trailing_zero(),
1427        FloatLiteralTrailingZero::Preserve
1428    ) {
1429        return wrap_str(
1430            context.snippet(span).to_owned(),
1431            context.config.max_width(),
1432            shape,
1433        )
1434        .max_width_error(shape.width, span);
1435    }
1436
1437    let symbol = token_lit.symbol.as_str();
1438    let suffix = token_lit.suffix.as_ref().map(|s| s.as_str());
1439
1440    let float_parts = parse_float_symbol(symbol).unwrap();
1441    let FloatSymbolParts {
1442        integer_part,
1443        fractional_part,
1444        exponent,
1445    } = float_parts;
1446
1447    let has_postfix = exponent.is_some() || suffix.is_some();
1448    let fractional_part_nonzero = !float_parts.is_fractional_part_zero();
1449
1450    let (include_period, include_fractional_part) =
1451        match context.config.float_literal_trailing_zero() {
1452            FloatLiteralTrailingZero::Preserve => unreachable!("handled above"),
1453            FloatLiteralTrailingZero::Always => (true, true),
1454            FloatLiteralTrailingZero::IfNoPostfix => (
1455                fractional_part_nonzero || !has_postfix,
1456                fractional_part_nonzero || !has_postfix,
1457            ),
1458            FloatLiteralTrailingZero::Never => (
1459                fractional_part_nonzero || !has_postfix,
1460                fractional_part_nonzero,
1461            ),
1462        };
1463
1464    let period = if include_period { "." } else { "" };
1465    let fractional_part = if include_fractional_part {
1466        fractional_part.unwrap_or("0")
1467    } else {
1468        ""
1469    };
1470    wrap_str(
1471        format!(
1472            "{}{}{}{}{}",
1473            integer_part,
1474            period,
1475            fractional_part,
1476            exponent.unwrap_or(""),
1477            suffix.unwrap_or(""),
1478        ),
1479        context.config.max_width(),
1480        shape,
1481    )
1482    .max_width_error(shape.width, span)
1483}
1484
1485fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option<SeparatorTactic> {
1486    if context.inside_macro() {
1487        if span_ends_with_comma(context, span) {
1488            Some(SeparatorTactic::Always)
1489        } else {
1490            Some(SeparatorTactic::Never)
1491        }
1492    } else {
1493        None
1494    }
1495}
1496
1497pub(crate) fn rewrite_call(
1498    context: &RewriteContext<'_>,
1499    callee: &str,
1500    args: &[Box<ast::Expr>],
1501    span: Span,
1502    shape: Shape,
1503) -> RewriteResult {
1504    overflow::rewrite_with_parens(
1505        context,
1506        callee,
1507        args.iter(),
1508        shape,
1509        span,
1510        context.config.fn_call_width(),
1511        choose_separator_tactic(context, span),
1512    )
1513}
1514
1515pub(crate) fn is_simple_expr(expr: &ast::Expr) -> bool {
1516    match expr.kind {
1517        ast::ExprKind::Lit(..) => true,
1518        ast::ExprKind::Path(ref qself, ref path) => qself.is_none() && path.segments.len() <= 1,
1519        ast::ExprKind::AddrOf(_, _, ref expr)
1520        | ast::ExprKind::Cast(ref expr, _)
1521        | ast::ExprKind::Field(ref expr, _)
1522        | ast::ExprKind::Try(ref expr)
1523        | ast::ExprKind::Unary(_, ref expr) => is_simple_expr(expr),
1524        ast::ExprKind::Index(ref lhs, ref rhs, _) => is_simple_expr(lhs) && is_simple_expr(rhs),
1525        ast::ExprKind::Repeat(ref lhs, ref rhs) => {
1526            is_simple_expr(lhs) && is_simple_expr(&*rhs.value)
1527        }
1528        _ => false,
1529    }
1530}
1531
1532pub(crate) fn is_every_expr_simple(lists: &[OverflowableItem<'_>]) -> bool {
1533    lists.iter().all(OverflowableItem::is_simple)
1534}
1535
1536pub(crate) fn can_be_overflowed_expr(
1537    context: &RewriteContext<'_>,
1538    expr: &ast::Expr,
1539    args_len: usize,
1540) -> bool {
1541    match expr.kind {
1542        _ if !expr.attrs.is_empty() => false,
1543        ast::ExprKind::Match(..) => {
1544            (context.use_block_indent() && args_len == 1)
1545                || (context.config.indent_style() == IndentStyle::Visual && args_len > 1)
1546                || context.config.overflow_delimited_expr()
1547        }
1548        ast::ExprKind::If(..)
1549        | ast::ExprKind::ForLoop { .. }
1550        | ast::ExprKind::Loop(..)
1551        | ast::ExprKind::While(..) => {
1552            context.config.combine_control_expr() && context.use_block_indent() && args_len == 1
1553        }
1554
1555        // Handle always block-like expressions
1556        ast::ExprKind::Gen(..) | ast::ExprKind::Block(..) | ast::ExprKind::Closure(..) => true,
1557
1558        // Handle `[]` and `{}`-like expressions
1559        ast::ExprKind::Array(..) | ast::ExprKind::Struct(..) => {
1560            context.config.overflow_delimited_expr()
1561                || (context.use_block_indent() && args_len == 1)
1562        }
1563        ast::ExprKind::MacCall(ref mac) => {
1564            match (mac.args.delim, context.config.overflow_delimited_expr()) {
1565                (Delimiter::Bracket, true) | (Delimiter::Brace, true) => true,
1566                _ => context.use_block_indent() && args_len == 1,
1567            }
1568        }
1569
1570        // Handle parenthetical expressions
1571        ast::ExprKind::Call(..) | ast::ExprKind::MethodCall(..) | ast::ExprKind::Tup(..) => {
1572            context.use_block_indent() && args_len == 1
1573        }
1574
1575        // Handle unary-like expressions
1576        ast::ExprKind::AddrOf(_, _, ref expr)
1577        | ast::ExprKind::Try(ref expr)
1578        | ast::ExprKind::Unary(_, ref expr)
1579        | ast::ExprKind::Cast(ref expr, _) => can_be_overflowed_expr(context, expr, args_len),
1580        _ => false,
1581    }
1582}
1583
1584pub(crate) fn is_nested_call(expr: &ast::Expr) -> bool {
1585    match expr.kind {
1586        ast::ExprKind::Call(..) | ast::ExprKind::MacCall(..) => true,
1587        ast::ExprKind::AddrOf(_, _, ref expr)
1588        | ast::ExprKind::Try(ref expr)
1589        | ast::ExprKind::Unary(_, ref expr)
1590        | ast::ExprKind::Cast(ref expr, _) => is_nested_call(expr),
1591        _ => false,
1592    }
1593}
1594
1595/// Returns `true` if a function call or a method call represented by the given span ends with a
1596/// trailing comma. This function is used when rewriting macro, as adding or removing a trailing
1597/// comma from macro can potentially break the code.
1598pub(crate) fn span_ends_with_comma(context: &RewriteContext<'_>, span: Span) -> bool {
1599    let mut result: bool = Default::default();
1600    let mut prev_char: char = Default::default();
1601    let closing_delimiters = &[')', '}', ']'];
1602
1603    for (kind, c) in CharClasses::new(context.snippet(span).chars()) {
1604        match c {
1605            _ if kind.is_comment() || c.is_whitespace() => continue,
1606            c if closing_delimiters.contains(&c) => {
1607                result &= !closing_delimiters.contains(&prev_char);
1608            }
1609            ',' => result = true,
1610            _ => result = false,
1611        }
1612        prev_char = c;
1613    }
1614
1615    result
1616}
1617
1618pub(crate) fn rewrite_paren(
1619    context: &RewriteContext<'_>,
1620    mut subexpr: &ast::Expr,
1621    shape: Shape,
1622    mut span: Span,
1623) -> RewriteResult {
1624    debug!("rewrite_paren, shape: {:?}", shape);
1625
1626    // Extract comments within parens.
1627    let mut pre_span;
1628    let mut post_span;
1629    let mut pre_comment;
1630    let mut post_comment;
1631    let remove_nested_parens = context.config.remove_nested_parens();
1632    loop {
1633        // 1 = "(" or ")"
1634        pre_span = mk_sp(span.lo() + BytePos(1), subexpr.span().lo());
1635        post_span = mk_sp(subexpr.span.hi(), span.hi() - BytePos(1));
1636        pre_comment = rewrite_missing_comment(pre_span, shape, context)?;
1637        post_comment = rewrite_missing_comment(post_span, shape, context)?;
1638
1639        // Remove nested parens if there are no comments.
1640        if let ast::ExprKind::Paren(ref subsubexpr) = subexpr.kind {
1641            if remove_nested_parens && pre_comment.is_empty() && post_comment.is_empty() {
1642                span = subexpr.span;
1643                subexpr = subsubexpr;
1644                continue;
1645            }
1646        }
1647
1648        break;
1649    }
1650
1651    // 1 = `(` and `)`
1652    let sub_shape = shape.offset_left(1, span)?.sub_width(1, span)?;
1653    let subexpr_str = subexpr.rewrite_result(context, sub_shape)?;
1654    let fits_single_line = !pre_comment.contains("//") && !post_comment.contains("//");
1655    if fits_single_line {
1656        Ok(format!("({pre_comment}{subexpr_str}{post_comment})"))
1657    } else {
1658        rewrite_paren_in_multi_line(context, subexpr, shape, pre_span, post_span)
1659    }
1660}
1661
1662fn rewrite_paren_in_multi_line(
1663    context: &RewriteContext<'_>,
1664    subexpr: &ast::Expr,
1665    shape: Shape,
1666    pre_span: Span,
1667    post_span: Span,
1668) -> RewriteResult {
1669    let nested_indent = shape.indent.block_indent(context.config);
1670    let nested_shape = Shape::indented(nested_indent, context.config);
1671    let pre_comment = rewrite_missing_comment(pre_span, nested_shape, context)?;
1672    let post_comment = rewrite_missing_comment(post_span, nested_shape, context)?;
1673    let subexpr_str = subexpr.rewrite_result(context, nested_shape)?;
1674
1675    let mut result = String::with_capacity(subexpr_str.len() * 2);
1676    result.push('(');
1677    if !pre_comment.is_empty() {
1678        result.push_str(&nested_indent.to_string_with_newline(context.config));
1679        result.push_str(&pre_comment);
1680    }
1681    result.push_str(&nested_indent.to_string_with_newline(context.config));
1682    result.push_str(&subexpr_str);
1683    if !post_comment.is_empty() {
1684        result.push_str(&nested_indent.to_string_with_newline(context.config));
1685        result.push_str(&post_comment);
1686    }
1687    result.push_str(&shape.indent.to_string_with_newline(context.config));
1688    result.push(')');
1689
1690    Ok(result)
1691}
1692
1693fn rewrite_index(
1694    expr: &ast::Expr,
1695    index: &ast::Expr,
1696    context: &RewriteContext<'_>,
1697    shape: Shape,
1698) -> RewriteResult {
1699    let expr_str = expr.rewrite_result(context, shape)?;
1700
1701    let offset = last_line_width(&expr_str) + 1;
1702    let rhs_overhead = shape.rhs_overhead(context.config);
1703    let index_shape = if expr_str.contains('\n') {
1704        Shape::legacy(context.config.max_width(), shape.indent)
1705            .offset_left(offset, index.span())
1706            .and_then(|shape| shape.sub_width(1 + rhs_overhead, index.span()))
1707    } else {
1708        match context.config.indent_style() {
1709            IndentStyle::Block => shape
1710                .offset_left(offset, index.span())
1711                .and_then(|shape| shape.sub_width(1, index.span())),
1712            IndentStyle::Visual => shape
1713                .visual_indent(offset)
1714                .sub_width(offset + 1, index.span()),
1715        }
1716    };
1717    let orig_index_rw = index_shape
1718        .map_err(RewriteError::from)
1719        .and_then(|s| index.rewrite_result(context, s));
1720
1721    // Return if index fits in a single line.
1722    match orig_index_rw {
1723        Ok(ref index_str) if !index_str.contains('\n') => {
1724            return Ok(format!("{expr_str}[{index_str}]"));
1725        }
1726        _ => (),
1727    }
1728
1729    // Try putting index on the next line and see if it fits in a single line.
1730    let indent = shape.indent.block_indent(context.config);
1731    let index_shape = Shape::indented(indent, context.config)
1732        .offset_left(1, index.span())?
1733        .sub_width(1 + rhs_overhead, index.span())?;
1734    let new_index_rw = index.rewrite_result(context, index_shape);
1735    match (orig_index_rw, new_index_rw) {
1736        (_, Ok(ref new_index_str)) if !new_index_str.contains('\n') => Ok(format!(
1737            "{}{}[{}]",
1738            expr_str,
1739            indent.to_string_with_newline(context.config),
1740            new_index_str,
1741        )),
1742        (Err(_), Ok(ref new_index_str)) => Ok(format!(
1743            "{}{}[{}]",
1744            expr_str,
1745            indent.to_string_with_newline(context.config),
1746            new_index_str,
1747        )),
1748        (Ok(ref index_str), _) => Ok(format!("{expr_str}[{index_str}]")),
1749        // When both orig_index_rw and new_index_rw result in errors, we currently propagate the
1750        // error from the second attempt since it is more generous with width constraints.
1751        // This decision is somewhat arbitrary and is open to change.
1752        (Err(_), Err(new_index_rw_err)) => Err(new_index_rw_err),
1753    }
1754}
1755
1756fn struct_lit_can_be_aligned(fields: &[ast::ExprField], has_base: bool) -> bool {
1757    !has_base && fields.iter().all(|field| !field.is_shorthand)
1758}
1759
1760fn rewrite_struct_lit<'a>(
1761    context: &RewriteContext<'_>,
1762    path: &ast::Path,
1763    qself: &Option<Box<ast::QSelf>>,
1764    fields: &'a [ast::ExprField],
1765    struct_rest: &ast::StructRest,
1766    attrs: &[ast::Attribute],
1767    span: Span,
1768    shape: Shape,
1769) -> RewriteResult {
1770    debug!("rewrite_struct_lit: shape {:?}", shape);
1771
1772    enum StructLitField<'a> {
1773        Regular(&'a ast::ExprField),
1774        Base(&'a ast::Expr),
1775        Rest(Span),
1776    }
1777
1778    // 2 = " {".len()
1779    let path_shape = shape.sub_width(2, span)?;
1780    let path_str = rewrite_path(context, PathContext::Expr, qself, path, path_shape)?;
1781
1782    let has_base_or_rest = match struct_rest {
1783        ast::StructRest::None if fields.is_empty() => return Ok(format!("{path_str} {{}}")),
1784        ast::StructRest::Rest(_) if fields.is_empty() => {
1785            return Ok(format!("{path_str} {{ .. }}"));
1786        }
1787        ast::StructRest::Rest(_) | ast::StructRest::Base(_) => true,
1788        _ => false,
1789    };
1790
1791    // Foo { a: Foo } - indent is +3, width is -5.
1792    let (h_shape, v_shape) = struct_lit_shape(shape, context, path_str.len() + 3, 2, span)?;
1793
1794    let one_line_width = h_shape.map_or(0, |shape| shape.width);
1795    let body_lo = context.snippet_provider.span_after(span, "{");
1796    let fields_str = if struct_lit_can_be_aligned(fields, has_base_or_rest)
1797        && context.config.struct_field_align_threshold() > 0
1798    {
1799        rewrite_with_alignment(
1800            fields,
1801            context,
1802            v_shape,
1803            mk_sp(body_lo, span.hi()),
1804            one_line_width,
1805        )
1806        .unknown_error()?
1807    } else {
1808        let field_iter = fields.iter().map(StructLitField::Regular).chain(
1809            match struct_rest {
1810                ast::StructRest::Base(expr) => Some(StructLitField::Base(&**expr)),
1811                ast::StructRest::Rest(span) => Some(StructLitField::Rest(*span)),
1812                ast::StructRest::None => None,
1813            }
1814            .into_iter(),
1815        );
1816
1817        let span_lo = |item: &StructLitField<'_>| match *item {
1818            StructLitField::Regular(field) => field.span().lo(),
1819            StructLitField::Base(expr) => {
1820                let last_field_hi = fields.last().map_or(span.lo(), |field| field.span.hi());
1821                let snippet = context.snippet(mk_sp(last_field_hi, expr.span.lo()));
1822                let pos = snippet.find_uncommented("..").unwrap();
1823                last_field_hi + BytePos(pos as u32)
1824            }
1825            StructLitField::Rest(span) => span.lo(),
1826        };
1827        let span_hi = |item: &StructLitField<'_>| match *item {
1828            StructLitField::Regular(field) => field.span().hi(),
1829            StructLitField::Base(expr) => expr.span.hi(),
1830            StructLitField::Rest(span) => span.hi(),
1831        };
1832        let rewrite = |item: &StructLitField<'_>| match *item {
1833            StructLitField::Regular(field) => {
1834                // The 1 taken from the v_budget is for the comma.
1835                rewrite_field(context, field, v_shape.sub_width(1, span)?, 0)
1836            }
1837            StructLitField::Base(expr) => {
1838                // 2 = ..
1839                expr.rewrite_result(context, v_shape.offset_left(2, span)?)
1840                    .map(|s| format!("..{}", s))
1841            }
1842            StructLitField::Rest(_) => Ok("..".to_owned()),
1843        };
1844
1845        let items = itemize_list(
1846            context.snippet_provider,
1847            field_iter,
1848            "}",
1849            ",",
1850            span_lo,
1851            span_hi,
1852            rewrite,
1853            body_lo,
1854            span.hi(),
1855            false,
1856        );
1857        let item_vec = items.collect::<Vec<_>>();
1858
1859        let tactic = struct_lit_tactic(h_shape, context, &item_vec);
1860        let nested_shape = shape_for_tactic(tactic, h_shape, v_shape);
1861
1862        let ends_with_comma = span_ends_with_comma(context, span);
1863        let force_no_trailing_comma = context.inside_macro() && !ends_with_comma;
1864
1865        let fmt = struct_lit_formatting(
1866            nested_shape,
1867            tactic,
1868            context,
1869            force_no_trailing_comma || has_base_or_rest || !context.use_block_indent(),
1870        );
1871
1872        write_list(&item_vec, &fmt)?
1873    };
1874
1875    let fields_str =
1876        wrap_struct_field(context, attrs, &fields_str, shape, v_shape, one_line_width)?;
1877    Ok(format!("{path_str} {{{fields_str}}}"))
1878
1879    // FIXME if context.config.indent_style() == Visual, but we run out
1880    // of space, we should fall back to BlockIndent.
1881}
1882
1883pub(crate) fn wrap_struct_field(
1884    context: &RewriteContext<'_>,
1885    attrs: &[ast::Attribute],
1886    fields_str: &str,
1887    shape: Shape,
1888    nested_shape: Shape,
1889    one_line_width: usize,
1890) -> RewriteResult {
1891    let should_vertical = context.config.indent_style() == IndentStyle::Block
1892        && (fields_str.contains('\n')
1893            || !context.config.struct_lit_single_line()
1894            || fields_str.len() > one_line_width);
1895
1896    let inner_attrs = &inner_attributes(attrs);
1897    if inner_attrs.is_empty() {
1898        if should_vertical {
1899            Ok(format!(
1900                "{}{}{}",
1901                nested_shape.indent.to_string_with_newline(context.config),
1902                fields_str,
1903                shape.indent.to_string_with_newline(context.config)
1904            ))
1905        } else {
1906            // One liner or visual indent.
1907            Ok(format!(" {fields_str} "))
1908        }
1909    } else {
1910        Ok(format!(
1911            "{}{}{}{}{}",
1912            nested_shape.indent.to_string_with_newline(context.config),
1913            inner_attrs.rewrite_result(context, shape)?,
1914            nested_shape.indent.to_string_with_newline(context.config),
1915            fields_str,
1916            shape.indent.to_string_with_newline(context.config)
1917        ))
1918    }
1919}
1920
1921pub(crate) fn struct_lit_field_separator(config: &Config) -> &str {
1922    colon_spaces(config)
1923}
1924
1925pub(crate) fn rewrite_field(
1926    context: &RewriteContext<'_>,
1927    field: &ast::ExprField,
1928    shape: Shape,
1929    prefix_max_width: usize,
1930) -> RewriteResult {
1931    if contains_skip(&field.attrs) {
1932        return Ok(context.snippet(field.span()).to_owned());
1933    }
1934    let mut attrs_str = field.attrs.rewrite_result(context, shape)?;
1935    if !attrs_str.is_empty() {
1936        attrs_str.push_str(&shape.indent.to_string_with_newline(context.config));
1937    };
1938    let name = context.snippet(field.ident.span);
1939    if field.is_shorthand {
1940        Ok(attrs_str + name)
1941    } else {
1942        let mut separator = String::from(struct_lit_field_separator(context.config));
1943        for _ in 0..prefix_max_width.saturating_sub(name.len()) {
1944            separator.push(' ');
1945        }
1946        let overhead = name.len() + separator.len();
1947        let expr_shape = shape.offset_left(overhead, field.span)?;
1948        let expr = field.expr.rewrite_result(context, expr_shape);
1949        let is_lit = matches!(field.expr.kind, ast::ExprKind::Lit(_));
1950        match expr {
1951            Ok(ref e)
1952                if !is_lit && e.as_str() == name && context.config.use_field_init_shorthand() =>
1953            {
1954                Ok(attrs_str + name)
1955            }
1956            Ok(e) => Ok(format!("{attrs_str}{name}{separator}{e}")),
1957            Err(_) => {
1958                let expr_offset = shape.indent.block_indent(context.config);
1959                let expr = field
1960                    .expr
1961                    .rewrite_result(context, Shape::indented(expr_offset, context.config));
1962                expr.map(|s| {
1963                    format!(
1964                        "{}{}:\n{}{}",
1965                        attrs_str,
1966                        name,
1967                        expr_offset.to_string(context.config),
1968                        s
1969                    )
1970                })
1971            }
1972        }
1973    }
1974}
1975
1976fn rewrite_tuple_in_visual_indent_style<'a, T: 'a + IntoOverflowableItem<'a>>(
1977    context: &RewriteContext<'_>,
1978    mut items: impl Iterator<Item = &'a T>,
1979    span: Span,
1980    shape: Shape,
1981    is_singleton_tuple: bool,
1982) -> RewriteResult {
1983    // In case of length 1, need a trailing comma
1984    debug!("rewrite_tuple_in_visual_indent_style {:?}", shape);
1985    if is_singleton_tuple {
1986        // 3 = "(" + ",)"
1987        let nested_shape = shape.sub_width(3, span)?.visual_indent(1);
1988        return items
1989            .next()
1990            .unwrap()
1991            .rewrite_result(context, nested_shape)
1992            .map(|s| format!("({},)", s));
1993    }
1994
1995    let list_lo = context.snippet_provider.span_after(span, "(");
1996    let nested_shape = shape.sub_width(2, span)?.visual_indent(1);
1997    let items = itemize_list(
1998        context.snippet_provider,
1999        items,
2000        ")",
2001        ",",
2002        |item| item.span().lo(),
2003        |item| item.span().hi(),
2004        |item| item.rewrite_result(context, nested_shape),
2005        list_lo,
2006        span.hi() - BytePos(1),
2007        false,
2008    );
2009    let item_vec: Vec<_> = items.collect();
2010    let tactic = definitive_tactic(
2011        &item_vec,
2012        ListTactic::HorizontalVertical,
2013        Separator::Comma,
2014        nested_shape.width,
2015    );
2016    let fmt = ListFormatting::new(nested_shape, context.config)
2017        .tactic(tactic)
2018        .ends_with_newline(false);
2019    let list_str = write_list(&item_vec, &fmt)?;
2020
2021    Ok(format!("({list_str})"))
2022}
2023
2024fn rewrite_let(
2025    context: &RewriteContext<'_>,
2026    shape: Shape,
2027    pat: &ast::Pat,
2028    expr: &ast::Expr,
2029) -> RewriteResult {
2030    let mut result = "let ".to_owned();
2031
2032    // TODO(ytmimi) comments could appear between `let` and the `pat`
2033
2034    // 4 = "let ".len()
2035    let mut pat_shape = shape.offset_left(4, pat.span)?;
2036    if context.config.style_edition() >= StyleEdition::Edition2027 {
2037        // 2 for the length of " ="
2038        pat_shape = pat_shape.sub_width(2, pat.span)?;
2039    }
2040    let pat_str = pat.rewrite_result(context, pat_shape)?;
2041    result.push_str(&pat_str);
2042
2043    // TODO(ytmimi) comments could appear between `pat` and `=`
2044    result.push_str(" =");
2045
2046    let comments_lo = context
2047        .snippet_provider
2048        .span_after(expr.span.with_lo(pat.span.hi()), "=");
2049    let comments_span = mk_sp(comments_lo, expr.span.lo());
2050    rewrite_assign_rhs_with_comments(
2051        context,
2052        result,
2053        expr,
2054        shape,
2055        &RhsAssignKind::Expr(&expr.kind, expr.span),
2056        RhsTactics::Default,
2057        comments_span,
2058        true,
2059    )
2060}
2061
2062pub(crate) fn rewrite_tuple<'a, T: 'a + IntoOverflowableItem<'a>>(
2063    context: &'a RewriteContext<'_>,
2064    items: impl Iterator<Item = &'a T>,
2065    span: Span,
2066    shape: Shape,
2067    is_singleton_tuple: bool,
2068) -> RewriteResult {
2069    debug!("rewrite_tuple {:?}", shape);
2070    if context.use_block_indent() {
2071        // We use the same rule as function calls for rewriting tuples.
2072        let force_tactic = if context.inside_macro() {
2073            if span_ends_with_comma(context, span) {
2074                Some(SeparatorTactic::Always)
2075            } else {
2076                Some(SeparatorTactic::Never)
2077            }
2078        } else if is_singleton_tuple {
2079            Some(SeparatorTactic::Always)
2080        } else {
2081            None
2082        };
2083        overflow::rewrite_with_parens(
2084            context,
2085            "",
2086            items,
2087            shape,
2088            span,
2089            context.config.fn_call_width(),
2090            force_tactic,
2091        )
2092    } else {
2093        rewrite_tuple_in_visual_indent_style(context, items, span, shape, is_singleton_tuple)
2094    }
2095}
2096
2097pub(crate) fn rewrite_unary_prefix<R: Rewrite + Spanned>(
2098    context: &RewriteContext<'_>,
2099    prefix: &str,
2100    rewrite: &R,
2101    shape: Shape,
2102) -> RewriteResult {
2103    let shape = shape.offset_left(prefix.len(), rewrite.span())?;
2104    rewrite
2105        .rewrite_result(context, shape)
2106        .map(|r| format!("{}{}", prefix, r))
2107}
2108
2109// FIXME: this is probably not correct for multi-line Rewrites. we should
2110// subtract suffix.len() from the last line budget, not the first!
2111pub(crate) fn rewrite_unary_suffix<R: Rewrite + Spanned>(
2112    context: &RewriteContext<'_>,
2113    suffix: &str,
2114    rewrite: &R,
2115    shape: Shape,
2116) -> RewriteResult {
2117    let shape = shape.sub_width(suffix.len(), rewrite.span())?;
2118    rewrite.rewrite_result(context, shape).map(|mut r| {
2119        r.push_str(suffix);
2120        r
2121    })
2122}
2123
2124fn rewrite_unary_op(
2125    context: &RewriteContext<'_>,
2126    op: ast::UnOp,
2127    expr: &ast::Expr,
2128    shape: Shape,
2129) -> RewriteResult {
2130    // For some reason, an UnOp is not spanned like BinOp!
2131    rewrite_unary_prefix(context, op.as_str(), expr, shape)
2132}
2133
2134pub(crate) enum RhsAssignKind<'ast> {
2135    Expr(&'ast ast::ExprKind, #[allow(dead_code)] Span),
2136    Bounds,
2137    Ty,
2138}
2139
2140impl<'ast> RhsAssignKind<'ast> {
2141    // TODO(calebcartwright)
2142    // Preemptive addition for handling RHS with chains, not yet utilized.
2143    // It may make more sense to construct the chain first and then check
2144    // whether there are actually chain elements.
2145    #[allow(dead_code)]
2146    fn is_chain(&self) -> bool {
2147        match self {
2148            RhsAssignKind::Expr(kind, _) => {
2149                matches!(
2150                    kind,
2151                    ast::ExprKind::Try(..)
2152                        | ast::ExprKind::Field(..)
2153                        | ast::ExprKind::MethodCall(..)
2154                        | ast::ExprKind::Await(_, _)
2155                )
2156            }
2157            _ => false,
2158        }
2159    }
2160}
2161
2162fn rewrite_assignment(
2163    context: &RewriteContext<'_>,
2164    lhs: &ast::Expr,
2165    rhs: &ast::Expr,
2166    op: Option<&ast::AssignOp>,
2167    shape: Shape,
2168) -> RewriteResult {
2169    let operator_str = match op {
2170        Some(op) => context.snippet(op.span),
2171        None => "=",
2172    };
2173
2174    // 1 = space between lhs and operator.
2175    let lhs_shape = shape.sub_width(operator_str.len() + 1, lhs.span())?;
2176    let lhs_str = format!(
2177        "{} {}",
2178        lhs.rewrite_result(context, lhs_shape)?,
2179        operator_str
2180    );
2181
2182    rewrite_assign_rhs(
2183        context,
2184        lhs_str,
2185        rhs,
2186        &RhsAssignKind::Expr(&rhs.kind, rhs.span),
2187        shape,
2188    )
2189}
2190
2191/// Controls where to put the rhs.
2192#[derive(Debug, Copy, Clone, PartialEq, Eq)]
2193pub(crate) enum RhsTactics {
2194    /// Use heuristics.
2195    Default,
2196    /// Put the rhs on the next line if it uses multiple line, without extra indentation.
2197    ForceNextLineWithoutIndent,
2198    /// Allow overflowing max width if neither `Default` nor `ForceNextLineWithoutIndent`
2199    /// did not work.
2200    AllowOverflow,
2201}
2202
2203// The left hand side must contain everything up to, and including, the
2204// assignment operator.
2205pub(crate) fn rewrite_assign_rhs<S: Into<String>, R: Rewrite>(
2206    context: &RewriteContext<'_>,
2207    lhs: S,
2208    ex: &R,
2209    rhs_kind: &RhsAssignKind<'_>,
2210    shape: Shape,
2211) -> RewriteResult {
2212    rewrite_assign_rhs_with(context, lhs, ex, shape, rhs_kind, RhsTactics::Default)
2213}
2214
2215pub(crate) fn rewrite_assign_rhs_expr<R: Rewrite>(
2216    context: &RewriteContext<'_>,
2217    lhs: &str,
2218    ex: &R,
2219    shape: Shape,
2220    rhs_kind: &RhsAssignKind<'_>,
2221    rhs_tactics: RhsTactics,
2222) -> RewriteResult {
2223    let last_line_width = last_line_width(lhs).saturating_sub(if lhs.contains('\n') {
2224        shape.indent.width()
2225    } else {
2226        0
2227    });
2228    // 1 = space between operator and rhs.
2229    let orig_shape = shape.offset_left_opt(last_line_width + 1).unwrap_or(Shape {
2230        width: 0,
2231        offset: shape.offset + last_line_width + 1,
2232        ..shape
2233    });
2234    let has_rhs_comment = if let Some(offset) = lhs.find_last_uncommented("=") {
2235        lhs.trim_end().len() > offset + 1
2236    } else {
2237        false
2238    };
2239
2240    choose_rhs(
2241        context,
2242        ex,
2243        orig_shape,
2244        ex.rewrite_result(context, orig_shape),
2245        rhs_kind,
2246        rhs_tactics,
2247        has_rhs_comment,
2248    )
2249}
2250
2251pub(crate) fn rewrite_assign_rhs_with<S: Into<String>, R: Rewrite>(
2252    context: &RewriteContext<'_>,
2253    lhs: S,
2254    ex: &R,
2255    shape: Shape,
2256    rhs_kind: &RhsAssignKind<'_>,
2257    rhs_tactics: RhsTactics,
2258) -> RewriteResult {
2259    let lhs = lhs.into();
2260    let rhs = rewrite_assign_rhs_expr(context, &lhs, ex, shape, rhs_kind, rhs_tactics)?;
2261    Ok(lhs + &rhs)
2262}
2263
2264pub(crate) fn rewrite_assign_rhs_with_comments<S: Into<String>, R: Rewrite + Spanned>(
2265    context: &RewriteContext<'_>,
2266    lhs: S,
2267    ex: &R,
2268    shape: Shape,
2269    rhs_kind: &RhsAssignKind<'_>,
2270    rhs_tactics: RhsTactics,
2271    between_span: Span,
2272    allow_extend: bool,
2273) -> RewriteResult {
2274    let lhs = lhs.into();
2275    let contains_comment = contains_comment(context.snippet(between_span));
2276    let shape = if contains_comment {
2277        shape.block_left(
2278            context.config.tab_spaces(),
2279            between_span.with_hi(ex.span().hi()),
2280        )?
2281    } else {
2282        shape
2283    };
2284    let rhs = rewrite_assign_rhs_expr(context, &lhs, ex, shape, rhs_kind, rhs_tactics)?;
2285    if contains_comment {
2286        let rhs = rhs.trim_start();
2287        combine_strs_with_missing_comments(context, &lhs, rhs, between_span, shape, allow_extend)
2288    } else {
2289        Ok(lhs + &rhs)
2290    }
2291}
2292
2293fn choose_rhs<R: Rewrite>(
2294    context: &RewriteContext<'_>,
2295    expr: &R,
2296    shape: Shape,
2297    orig_rhs: RewriteResult,
2298    _rhs_kind: &RhsAssignKind<'_>,
2299    rhs_tactics: RhsTactics,
2300    has_rhs_comment: bool,
2301) -> RewriteResult {
2302    match orig_rhs {
2303        Ok(ref new_str) if new_str.is_empty() => Ok(String::new()),
2304        Ok(ref new_str) if !new_str.contains('\n') && unicode_str_width(new_str) <= shape.width => {
2305            Ok(format!(" {new_str}"))
2306        }
2307        _ => {
2308            // Expression did not fit on the same line as the identifier.
2309            // Try splitting the line and see if that works better.
2310            let new_shape = shape_from_rhs_tactic(context, shape, rhs_tactics)
2311                // TODO(ding-young) Ideally, we can replace unknown_error() with max_width_error(),
2312                // but this requires either implementing the Spanned trait for ast::GenericBounds
2313                // or grabbing the span from the call site.
2314                .unknown_error()?;
2315            let new_rhs = expr.rewrite_result(context, new_shape);
2316            let new_indent_str = &shape
2317                .indent
2318                .block_indent(context.config)
2319                .to_string_with_newline(context.config);
2320            let before_space_str = if has_rhs_comment { "" } else { " " };
2321
2322            match (orig_rhs, new_rhs) {
2323                (Ok(ref orig_rhs), Ok(ref new_rhs))
2324                    if !filtered_str_fits(&new_rhs, context.config.max_width(), new_shape) =>
2325                {
2326                    Ok(format!("{before_space_str}{orig_rhs}"))
2327                }
2328                (Ok(ref orig_rhs), Ok(ref new_rhs))
2329                    if prefer_next_line(orig_rhs, new_rhs, rhs_tactics) =>
2330                {
2331                    Ok(format!("{new_indent_str}{new_rhs}"))
2332                }
2333                (Err(_), Ok(ref new_rhs)) => Ok(format!("{new_indent_str}{new_rhs}")),
2334                (Err(_), Err(_)) if rhs_tactics == RhsTactics::AllowOverflow => {
2335                    let shape = shape.infinite_width();
2336                    expr.rewrite_result(context, shape)
2337                        .map(|s| format!("{}{}", before_space_str, s))
2338                }
2339                // When both orig_rhs and new_rhs result in errors, we currently propagate
2340                // the error from the second attempt since it is more generous with
2341                // width constraints. This decision is somewhat arbitrary and is open to change.
2342                (Err(_), Err(new_rhs_err)) => Err(new_rhs_err),
2343                (Ok(orig_rhs), _) => Ok(format!("{before_space_str}{orig_rhs}")),
2344            }
2345        }
2346    }
2347}
2348
2349fn shape_from_rhs_tactic(
2350    context: &RewriteContext<'_>,
2351    shape: Shape,
2352    rhs_tactic: RhsTactics,
2353) -> Option<Shape> {
2354    match rhs_tactic {
2355        RhsTactics::ForceNextLineWithoutIndent => shape
2356            .with_max_width(context.config)
2357            .sub_width_opt(shape.indent.width()),
2358        RhsTactics::Default | RhsTactics::AllowOverflow => {
2359            Shape::indented(shape.indent.block_indent(context.config), context.config)
2360                .sub_width_opt(shape.rhs_overhead(context.config))
2361        }
2362    }
2363}
2364
2365/// Returns true if formatting next_line_rhs is better on a new line when compared to the
2366/// original's line formatting.
2367///
2368/// It is considered better if:
2369/// 1. the tactic is ForceNextLineWithoutIndent
2370/// 2. next_line_rhs doesn't have newlines
2371/// 3. the original line has more newlines than next_line_rhs
2372/// 4. the original formatting of the first line ends with `(`, `{`, or `[` and next_line_rhs
2373///    doesn't
2374pub(crate) fn prefer_next_line(
2375    orig_rhs: &str,
2376    next_line_rhs: &str,
2377    rhs_tactics: RhsTactics,
2378) -> bool {
2379    rhs_tactics == RhsTactics::ForceNextLineWithoutIndent
2380        || !next_line_rhs.contains('\n')
2381        || count_newlines(orig_rhs) > count_newlines(next_line_rhs) + 1
2382        || first_line_ends_with(orig_rhs, '(') && !first_line_ends_with(next_line_rhs, '(')
2383        || first_line_ends_with(orig_rhs, '{') && !first_line_ends_with(next_line_rhs, '{')
2384        || first_line_ends_with(orig_rhs, '[') && !first_line_ends_with(next_line_rhs, '[')
2385}
2386
2387fn rewrite_expr_addrof(
2388    context: &RewriteContext<'_>,
2389    borrow_kind: ast::BorrowKind,
2390    mutability: ast::Mutability,
2391    expr: &ast::Expr,
2392    shape: Shape,
2393) -> RewriteResult {
2394    let operator_str = match (mutability, borrow_kind) {
2395        (ast::Mutability::Not, ast::BorrowKind::Ref) => "&",
2396        (ast::Mutability::Not, ast::BorrowKind::Pin) => "&pin const ",
2397        (ast::Mutability::Not, ast::BorrowKind::Raw) => "&raw const ",
2398        (ast::Mutability::Mut, ast::BorrowKind::Ref) => "&mut ",
2399        (ast::Mutability::Mut, ast::BorrowKind::Pin) => "&pin mut ",
2400        (ast::Mutability::Mut, ast::BorrowKind::Raw) => "&raw mut ",
2401    };
2402    rewrite_unary_prefix(context, operator_str, expr, shape)
2403}
2404
2405pub(crate) fn is_method_call(expr: &ast::Expr) -> bool {
2406    match expr.kind {
2407        ast::ExprKind::MethodCall(..) => true,
2408        ast::ExprKind::AddrOf(_, _, ref expr)
2409        | ast::ExprKind::Cast(ref expr, _)
2410        | ast::ExprKind::Try(ref expr)
2411        | ast::ExprKind::Unary(_, ref expr) => is_method_call(expr),
2412        _ => false,
2413    }
2414}
2415
2416/// Indicates the parts of a float literal specified as a string.
2417struct FloatSymbolParts<'a> {
2418    /// The integer part, e.g. `123` in `123.456e789`.
2419    /// Always non-empty, because in Rust `.1` is not a valid floating-point literal:
2420    /// <https://doc.rust-lang.org/reference/tokens.html#floating-point-literals>
2421    integer_part: &'a str,
2422    /// The fractional part excluding the decimal point, e.g. `456` in `123.456e789`.
2423    fractional_part: Option<&'a str>,
2424    /// The exponent part including the `e` or `E`, e.g. `e789` in `123.456e789`.
2425    exponent: Option<&'a str>,
2426}
2427
2428impl FloatSymbolParts<'_> {
2429    fn is_fractional_part_zero(&self) -> bool {
2430        let zero_literal_regex = static_regex!(r"^[0_]+$");
2431        self.fractional_part
2432            .is_none_or(|s| zero_literal_regex.is_match(s))
2433    }
2434}
2435
2436/// Parses a float literal. The `symbol` must be a valid floating point literal without a type
2437/// suffix. Otherwise the function may panic or return wrong result.
2438fn parse_float_symbol(symbol: &str) -> Result<FloatSymbolParts<'_>, &'static str> {
2439    // This regex may accept invalid float literals (such as `1`, `_` or `2.e3`). That's ok.
2440    // We only use it to parse literals whose validity has already been established.
2441    let float_literal_regex = static_regex!(r"^([0-9_]+)(?:\.([0-9_]+)?)?([eE][+-]?[0-9_]+)?$");
2442    let caps = float_literal_regex
2443        .captures(symbol)
2444        .ok_or("invalid float literal")?;
2445    Ok(FloatSymbolParts {
2446        integer_part: caps.get(1).ok_or("missing integer part")?.as_str(),
2447        fractional_part: caps.get(2).map(|m| m.as_str()),
2448        exponent: caps.get(3).map(|m| m.as_str()),
2449    })
2450}
2451
2452#[cfg(test)]
2453mod test {
2454    use super::*;
2455
2456    #[test]
2457    fn test_last_line_offsetted() {
2458        let lines = "one\n    two";
2459        assert_eq!(last_line_offsetted(2, lines), true);
2460        assert_eq!(last_line_offsetted(4, lines), false);
2461        assert_eq!(last_line_offsetted(6, lines), false);
2462
2463        let lines = "one    two";
2464        assert_eq!(last_line_offsetted(2, lines), false);
2465        assert_eq!(last_line_offsetted(0, lines), false);
2466
2467        let lines = "\ntwo";
2468        assert_eq!(last_line_offsetted(2, lines), false);
2469        assert_eq!(last_line_offsetted(0, lines), false);
2470
2471        let lines = "one\n    two      three";
2472        assert_eq!(last_line_offsetted(2, lines), true);
2473        let lines = "one\n two      three";
2474        assert_eq!(last_line_offsetted(2, lines), false);
2475    }
2476
2477    #[test]
2478    fn test_parse_float_symbol() {
2479        let parts = parse_float_symbol("123.456e789").unwrap();
2480        assert_eq!(parts.integer_part, "123");
2481        assert_eq!(parts.fractional_part, Some("456"));
2482        assert_eq!(parts.exponent, Some("e789"));
2483
2484        let parts = parse_float_symbol("123.456e+789").unwrap();
2485        assert_eq!(parts.integer_part, "123");
2486        assert_eq!(parts.fractional_part, Some("456"));
2487        assert_eq!(parts.exponent, Some("e+789"));
2488
2489        let parts = parse_float_symbol("123.456e-789").unwrap();
2490        assert_eq!(parts.integer_part, "123");
2491        assert_eq!(parts.fractional_part, Some("456"));
2492        assert_eq!(parts.exponent, Some("e-789"));
2493
2494        let parts = parse_float_symbol("123e789").unwrap();
2495        assert_eq!(parts.integer_part, "123");
2496        assert_eq!(parts.fractional_part, None);
2497        assert_eq!(parts.exponent, Some("e789"));
2498
2499        let parts = parse_float_symbol("123E789").unwrap();
2500        assert_eq!(parts.integer_part, "123");
2501        assert_eq!(parts.fractional_part, None);
2502        assert_eq!(parts.exponent, Some("E789"));
2503
2504        let parts = parse_float_symbol("123.").unwrap();
2505        assert_eq!(parts.integer_part, "123");
2506        assert_eq!(parts.fractional_part, None);
2507        assert_eq!(parts.exponent, None);
2508    }
2509
2510    #[test]
2511    fn test_parse_float_symbol_with_underscores() {
2512        let parts = parse_float_symbol("_123._456e_789").unwrap();
2513        assert_eq!(parts.integer_part, "_123");
2514        assert_eq!(parts.fractional_part, Some("_456"));
2515        assert_eq!(parts.exponent, Some("e_789"));
2516
2517        let parts = parse_float_symbol("123_.456_e789_").unwrap();
2518        assert_eq!(parts.integer_part, "123_");
2519        assert_eq!(parts.fractional_part, Some("456_"));
2520        assert_eq!(parts.exponent, Some("e789_"));
2521
2522        let parts = parse_float_symbol("1_23.4_56e7_89").unwrap();
2523        assert_eq!(parts.integer_part, "1_23");
2524        assert_eq!(parts.fractional_part, Some("4_56"));
2525        assert_eq!(parts.exponent, Some("e7_89"));
2526
2527        let parts = parse_float_symbol("_1_23_._4_56_e_7_89_").unwrap();
2528        assert_eq!(parts.integer_part, "_1_23_");
2529        assert_eq!(parts.fractional_part, Some("_4_56_"));
2530        assert_eq!(parts.exponent, Some("e_7_89_"));
2531    }
2532
2533    #[test]
2534    fn test_float_lit_ends_in_dot() {
2535        type TZ = FloatLiteralTrailingZero;
2536
2537        assert!(float_lit_ends_in_dot("1.", None, TZ::Preserve));
2538        assert!(!float_lit_ends_in_dot("1.0", None, TZ::Preserve));
2539        assert!(!float_lit_ends_in_dot("1.e2", None, TZ::Preserve));
2540        assert!(!float_lit_ends_in_dot("1.0e2", None, TZ::Preserve));
2541        assert!(!float_lit_ends_in_dot("1.", Some("f32"), TZ::Preserve));
2542        assert!(!float_lit_ends_in_dot("1.0", Some("f32"), TZ::Preserve));
2543
2544        assert!(!float_lit_ends_in_dot("1.", None, TZ::Always));
2545        assert!(!float_lit_ends_in_dot("1.0", None, TZ::Always));
2546        assert!(!float_lit_ends_in_dot("1.e2", None, TZ::Always));
2547        assert!(!float_lit_ends_in_dot("1.0e2", None, TZ::Always));
2548        assert!(!float_lit_ends_in_dot("1.", Some("f32"), TZ::Always));
2549        assert!(!float_lit_ends_in_dot("1.0", Some("f32"), TZ::Always));
2550
2551        assert!(!float_lit_ends_in_dot("1.", None, TZ::IfNoPostfix));
2552        assert!(!float_lit_ends_in_dot("1.0", None, TZ::IfNoPostfix));
2553        assert!(!float_lit_ends_in_dot("1.e2", None, TZ::IfNoPostfix));
2554        assert!(!float_lit_ends_in_dot("1.0e2", None, TZ::IfNoPostfix));
2555        assert!(!float_lit_ends_in_dot("1.", Some("f32"), TZ::IfNoPostfix));
2556        assert!(!float_lit_ends_in_dot("1.0", Some("f32"), TZ::IfNoPostfix));
2557
2558        assert!(float_lit_ends_in_dot("1.", None, TZ::Never));
2559        assert!(float_lit_ends_in_dot("1.0", None, TZ::Never));
2560        assert!(!float_lit_ends_in_dot("1.e2", None, TZ::Never));
2561        assert!(!float_lit_ends_in_dot("1.0e2", None, TZ::Never));
2562        assert!(!float_lit_ends_in_dot("1.", Some("f32"), TZ::Never));
2563        assert!(!float_lit_ends_in_dot("1.0", Some("f32"), TZ::Never));
2564    }
2565}