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(..)
1557        | ast::ExprKind::Block(..)
1558        | ast::ExprKind::Closure(..)
1559        | ast::ExprKind::TryBlock(..) => true,
1560
1561        // Handle `[]` and `{}`-like expressions
1562        ast::ExprKind::Array(..) | ast::ExprKind::Struct(..) => {
1563            context.config.overflow_delimited_expr()
1564                || (context.use_block_indent() && args_len == 1)
1565        }
1566        ast::ExprKind::MacCall(ref mac) => {
1567            match (mac.args.delim, context.config.overflow_delimited_expr()) {
1568                (Delimiter::Bracket, true) | (Delimiter::Brace, true) => true,
1569                _ => context.use_block_indent() && args_len == 1,
1570            }
1571        }
1572
1573        // Handle parenthetical expressions
1574        ast::ExprKind::Call(..) | ast::ExprKind::MethodCall(..) | ast::ExprKind::Tup(..) => {
1575            context.use_block_indent() && args_len == 1
1576        }
1577
1578        // Handle unary-like expressions
1579        ast::ExprKind::AddrOf(_, _, ref expr)
1580        | ast::ExprKind::Try(ref expr)
1581        | ast::ExprKind::Unary(_, ref expr)
1582        | ast::ExprKind::Cast(ref expr, _) => can_be_overflowed_expr(context, expr, args_len),
1583        _ => false,
1584    }
1585}
1586
1587pub(crate) fn is_nested_call(expr: &ast::Expr) -> bool {
1588    match expr.kind {
1589        ast::ExprKind::Call(..) | ast::ExprKind::MacCall(..) => true,
1590        ast::ExprKind::AddrOf(_, _, ref expr)
1591        | ast::ExprKind::Try(ref expr)
1592        | ast::ExprKind::Unary(_, ref expr)
1593        | ast::ExprKind::Cast(ref expr, _) => is_nested_call(expr),
1594        _ => false,
1595    }
1596}
1597
1598/// Returns `true` if a function call or a method call represented by the given span ends with a
1599/// trailing comma. This function is used when rewriting macro, as adding or removing a trailing
1600/// comma from macro can potentially break the code.
1601pub(crate) fn span_ends_with_comma(context: &RewriteContext<'_>, span: Span) -> bool {
1602    let mut result: bool = Default::default();
1603    let mut prev_char: char = Default::default();
1604    let closing_delimiters = &[')', '}', ']'];
1605
1606    for (kind, c) in CharClasses::new(context.snippet(span).chars()) {
1607        match c {
1608            _ if kind.is_comment() || c.is_whitespace() => continue,
1609            c if closing_delimiters.contains(&c) => {
1610                result &= !closing_delimiters.contains(&prev_char);
1611            }
1612            ',' => result = true,
1613            _ => result = false,
1614        }
1615        prev_char = c;
1616    }
1617
1618    result
1619}
1620
1621pub(crate) fn rewrite_paren(
1622    context: &RewriteContext<'_>,
1623    mut subexpr: &ast::Expr,
1624    shape: Shape,
1625    mut span: Span,
1626) -> RewriteResult {
1627    debug!("rewrite_paren, shape: {:?}", shape);
1628
1629    // Extract comments within parens.
1630    let mut pre_span;
1631    let mut post_span;
1632    let mut pre_comment;
1633    let mut post_comment;
1634    let remove_nested_parens = context.config.remove_nested_parens();
1635    loop {
1636        // 1 = "(" or ")"
1637        pre_span = mk_sp(span.lo() + BytePos(1), subexpr.span().lo());
1638        post_span = mk_sp(subexpr.span.hi(), span.hi() - BytePos(1));
1639        pre_comment = rewrite_missing_comment(pre_span, shape, context)?;
1640        post_comment = rewrite_missing_comment(post_span, shape, context)?;
1641
1642        // Remove nested parens if there are no comments.
1643        if let ast::ExprKind::Paren(ref subsubexpr) = subexpr.kind {
1644            if remove_nested_parens && pre_comment.is_empty() && post_comment.is_empty() {
1645                span = subexpr.span;
1646                subexpr = subsubexpr;
1647                continue;
1648            }
1649        }
1650
1651        break;
1652    }
1653
1654    // 1 = `(` and `)`
1655    let sub_shape = shape.offset_left(1, span)?.sub_width(1, span)?;
1656    let subexpr_str = subexpr.rewrite_result(context, sub_shape)?;
1657    let fits_single_line = !pre_comment.contains("//") && !post_comment.contains("//");
1658    if fits_single_line {
1659        Ok(format!("({pre_comment}{subexpr_str}{post_comment})"))
1660    } else {
1661        rewrite_paren_in_multi_line(context, subexpr, shape, pre_span, post_span)
1662    }
1663}
1664
1665fn rewrite_paren_in_multi_line(
1666    context: &RewriteContext<'_>,
1667    subexpr: &ast::Expr,
1668    shape: Shape,
1669    pre_span: Span,
1670    post_span: Span,
1671) -> RewriteResult {
1672    let nested_indent = shape.indent.block_indent(context.config);
1673    let nested_shape = Shape::indented(nested_indent, context.config);
1674    let pre_comment = rewrite_missing_comment(pre_span, nested_shape, context)?;
1675    let post_comment = rewrite_missing_comment(post_span, nested_shape, context)?;
1676    let subexpr_str = subexpr.rewrite_result(context, nested_shape)?;
1677
1678    let mut result = String::with_capacity(subexpr_str.len() * 2);
1679    result.push('(');
1680    if !pre_comment.is_empty() {
1681        result.push_str(&nested_indent.to_string_with_newline(context.config));
1682        result.push_str(&pre_comment);
1683    }
1684    result.push_str(&nested_indent.to_string_with_newline(context.config));
1685    result.push_str(&subexpr_str);
1686    if !post_comment.is_empty() {
1687        result.push_str(&nested_indent.to_string_with_newline(context.config));
1688        result.push_str(&post_comment);
1689    }
1690    result.push_str(&shape.indent.to_string_with_newline(context.config));
1691    result.push(')');
1692
1693    Ok(result)
1694}
1695
1696fn rewrite_index(
1697    expr: &ast::Expr,
1698    index: &ast::Expr,
1699    context: &RewriteContext<'_>,
1700    shape: Shape,
1701) -> RewriteResult {
1702    let expr_str = expr.rewrite_result(context, shape)?;
1703
1704    let offset = last_line_width(&expr_str) + 1;
1705    let rhs_overhead = shape.rhs_overhead(context.config);
1706    let index_shape = if expr_str.contains('\n') {
1707        Shape::legacy(context.config.max_width(), shape.indent)
1708            .offset_left(offset, index.span())
1709            .and_then(|shape| shape.sub_width(1 + rhs_overhead, index.span()))
1710    } else {
1711        match context.config.indent_style() {
1712            IndentStyle::Block => shape
1713                .offset_left(offset, index.span())
1714                .and_then(|shape| shape.sub_width(1, index.span())),
1715            IndentStyle::Visual => shape
1716                .visual_indent(offset)
1717                .sub_width(offset + 1, index.span()),
1718        }
1719    };
1720    let orig_index_rw = index_shape
1721        .map_err(RewriteError::from)
1722        .and_then(|s| index.rewrite_result(context, s));
1723
1724    // Return if index fits in a single line.
1725    match orig_index_rw {
1726        Ok(ref index_str) if !index_str.contains('\n') => {
1727            return Ok(format!("{expr_str}[{index_str}]"));
1728        }
1729        _ => (),
1730    }
1731
1732    // Try putting index on the next line and see if it fits in a single line.
1733    let indent = shape.indent.block_indent(context.config);
1734    let index_shape = Shape::indented(indent, context.config)
1735        .offset_left(1, index.span())?
1736        .sub_width(1 + rhs_overhead, index.span())?;
1737    let new_index_rw = index.rewrite_result(context, index_shape);
1738    match (orig_index_rw, new_index_rw) {
1739        (_, Ok(ref new_index_str)) if !new_index_str.contains('\n') => Ok(format!(
1740            "{}{}[{}]",
1741            expr_str,
1742            indent.to_string_with_newline(context.config),
1743            new_index_str,
1744        )),
1745        (Err(_), Ok(ref new_index_str)) => Ok(format!(
1746            "{}{}[{}]",
1747            expr_str,
1748            indent.to_string_with_newline(context.config),
1749            new_index_str,
1750        )),
1751        (Ok(ref index_str), _) => Ok(format!("{expr_str}[{index_str}]")),
1752        // When both orig_index_rw and new_index_rw result in errors, we currently propagate the
1753        // error from the second attempt since it is more generous with width constraints.
1754        // This decision is somewhat arbitrary and is open to change.
1755        (Err(_), Err(new_index_rw_err)) => Err(new_index_rw_err),
1756    }
1757}
1758
1759fn struct_lit_can_be_aligned(fields: &[ast::ExprField], has_base: bool) -> bool {
1760    !has_base && fields.iter().all(|field| !field.is_shorthand)
1761}
1762
1763fn rewrite_struct_lit<'a>(
1764    context: &RewriteContext<'_>,
1765    path: &ast::Path,
1766    qself: &Option<Box<ast::QSelf>>,
1767    fields: &'a [ast::ExprField],
1768    struct_rest: &ast::StructRest,
1769    attrs: &[ast::Attribute],
1770    span: Span,
1771    shape: Shape,
1772) -> RewriteResult {
1773    debug!("rewrite_struct_lit: shape {:?}", shape);
1774
1775    enum StructLitField<'a> {
1776        Regular(&'a ast::ExprField),
1777        Base(&'a ast::Expr),
1778        Rest(Span),
1779    }
1780
1781    // 2 = " {".len()
1782    let path_shape = shape.sub_width(2, span)?;
1783    let path_str = rewrite_path(context, PathContext::Expr, qself, path, path_shape)?;
1784
1785    let has_base_or_rest = match struct_rest {
1786        ast::StructRest::None if fields.is_empty() => return Ok(format!("{path_str} {{}}")),
1787        ast::StructRest::Rest(_) if fields.is_empty() => {
1788            return Ok(format!("{path_str} {{ .. }}"));
1789        }
1790        ast::StructRest::Rest(_) | ast::StructRest::Base(_) => true,
1791        _ => false,
1792    };
1793
1794    // Foo { a: Foo } - indent is +3, width is -5.
1795    let (h_shape, v_shape) = struct_lit_shape(shape, context, path_str.len() + 3, 2, span)?;
1796
1797    let one_line_width = h_shape.map_or(0, |shape| shape.width);
1798    let body_lo = context.snippet_provider.span_after(span, "{");
1799    let fields_str = if struct_lit_can_be_aligned(fields, has_base_or_rest)
1800        && context.config.struct_field_align_threshold() > 0
1801    {
1802        rewrite_with_alignment(
1803            fields,
1804            context,
1805            v_shape,
1806            mk_sp(body_lo, span.hi()),
1807            one_line_width,
1808        )
1809        .unknown_error()?
1810    } else {
1811        let field_iter = fields.iter().map(StructLitField::Regular).chain(
1812            match struct_rest {
1813                ast::StructRest::Base(expr) => Some(StructLitField::Base(&**expr)),
1814                ast::StructRest::Rest(span) => Some(StructLitField::Rest(*span)),
1815                ast::StructRest::None | ast::StructRest::NoneWithError(_) => None,
1816            }
1817            .into_iter(),
1818        );
1819
1820        let span_lo = |item: &StructLitField<'_>| match *item {
1821            StructLitField::Regular(field) => field.span().lo(),
1822            StructLitField::Base(expr) => {
1823                let last_field_hi = fields.last().map_or(span.lo(), |field| field.span.hi());
1824                let snippet = context.snippet(mk_sp(last_field_hi, expr.span.lo()));
1825                let pos = snippet.find_uncommented("..").unwrap();
1826                last_field_hi + BytePos(pos as u32)
1827            }
1828            StructLitField::Rest(span) => span.lo(),
1829        };
1830        let span_hi = |item: &StructLitField<'_>| match *item {
1831            StructLitField::Regular(field) => field.span().hi(),
1832            StructLitField::Base(expr) => expr.span.hi(),
1833            StructLitField::Rest(span) => span.hi(),
1834        };
1835        let rewrite = |item: &StructLitField<'_>| match *item {
1836            StructLitField::Regular(field) => {
1837                // The 1 taken from the v_budget is for the comma.
1838                rewrite_field(context, field, v_shape.sub_width(1, span)?, 0)
1839            }
1840            StructLitField::Base(expr) => {
1841                // 2 = ..
1842                expr.rewrite_result(context, v_shape.offset_left(2, span)?)
1843                    .map(|s| format!("..{}", s))
1844            }
1845            StructLitField::Rest(_) => Ok("..".to_owned()),
1846        };
1847
1848        let items = itemize_list(
1849            context.snippet_provider,
1850            field_iter,
1851            "}",
1852            ",",
1853            span_lo,
1854            span_hi,
1855            rewrite,
1856            body_lo,
1857            span.hi(),
1858            false,
1859        );
1860        let item_vec = items.collect::<Vec<_>>();
1861
1862        let tactic = struct_lit_tactic(h_shape, context, &item_vec);
1863        let nested_shape = shape_for_tactic(tactic, h_shape, v_shape);
1864
1865        let ends_with_comma = span_ends_with_comma(context, span);
1866        let force_no_trailing_comma = context.inside_macro() && !ends_with_comma;
1867
1868        let fmt = struct_lit_formatting(
1869            nested_shape,
1870            tactic,
1871            context,
1872            force_no_trailing_comma || has_base_or_rest || !context.use_block_indent(),
1873        );
1874
1875        write_list(&item_vec, &fmt)?
1876    };
1877
1878    let fields_str =
1879        wrap_struct_field(context, attrs, &fields_str, shape, v_shape, one_line_width)?;
1880    Ok(format!("{path_str} {{{fields_str}}}"))
1881
1882    // FIXME if context.config.indent_style() == Visual, but we run out
1883    // of space, we should fall back to BlockIndent.
1884}
1885
1886pub(crate) fn wrap_struct_field(
1887    context: &RewriteContext<'_>,
1888    attrs: &[ast::Attribute],
1889    fields_str: &str,
1890    shape: Shape,
1891    nested_shape: Shape,
1892    one_line_width: usize,
1893) -> RewriteResult {
1894    let should_vertical = context.config.indent_style() == IndentStyle::Block
1895        && (fields_str.contains('\n')
1896            || !context.config.struct_lit_single_line()
1897            || fields_str.len() > one_line_width);
1898
1899    let inner_attrs = &inner_attributes(attrs);
1900    if inner_attrs.is_empty() {
1901        if should_vertical {
1902            Ok(format!(
1903                "{}{}{}",
1904                nested_shape.indent.to_string_with_newline(context.config),
1905                fields_str,
1906                shape.indent.to_string_with_newline(context.config)
1907            ))
1908        } else {
1909            // One liner or visual indent.
1910            Ok(format!(" {fields_str} "))
1911        }
1912    } else {
1913        Ok(format!(
1914            "{}{}{}{}{}",
1915            nested_shape.indent.to_string_with_newline(context.config),
1916            inner_attrs.rewrite_result(context, shape)?,
1917            nested_shape.indent.to_string_with_newline(context.config),
1918            fields_str,
1919            shape.indent.to_string_with_newline(context.config)
1920        ))
1921    }
1922}
1923
1924pub(crate) fn struct_lit_field_separator(config: &Config) -> &str {
1925    colon_spaces(config)
1926}
1927
1928pub(crate) fn rewrite_field(
1929    context: &RewriteContext<'_>,
1930    field: &ast::ExprField,
1931    shape: Shape,
1932    prefix_max_width: usize,
1933) -> RewriteResult {
1934    if contains_skip(&field.attrs) {
1935        return Ok(context.snippet(field.span()).to_owned());
1936    }
1937    let mut attrs_str = field.attrs.rewrite_result(context, shape)?;
1938    if !attrs_str.is_empty() {
1939        attrs_str.push_str(&shape.indent.to_string_with_newline(context.config));
1940    };
1941    let name = context.snippet(field.ident.span);
1942    if field.is_shorthand {
1943        Ok(attrs_str + name)
1944    } else {
1945        let mut separator = String::from(struct_lit_field_separator(context.config));
1946        for _ in 0..prefix_max_width.saturating_sub(name.len()) {
1947            separator.push(' ');
1948        }
1949        let overhead = name.len() + separator.len();
1950        let expr_shape = shape.offset_left(overhead, field.span)?;
1951        let expr = field.expr.rewrite_result(context, expr_shape);
1952        let is_lit = matches!(field.expr.kind, ast::ExprKind::Lit(_));
1953        match expr {
1954            Ok(ref e)
1955                if !is_lit && e.as_str() == name && context.config.use_field_init_shorthand() =>
1956            {
1957                Ok(attrs_str + name)
1958            }
1959            Ok(e) => Ok(format!("{attrs_str}{name}{separator}{e}")),
1960            Err(_) => {
1961                let expr_offset = shape.indent.block_indent(context.config);
1962                let expr = field
1963                    .expr
1964                    .rewrite_result(context, Shape::indented(expr_offset, context.config));
1965                expr.map(|s| {
1966                    format!(
1967                        "{}{}:\n{}{}",
1968                        attrs_str,
1969                        name,
1970                        expr_offset.to_string(context.config),
1971                        s
1972                    )
1973                })
1974            }
1975        }
1976    }
1977}
1978
1979fn rewrite_tuple_in_visual_indent_style<'a, T: 'a + IntoOverflowableItem<'a>>(
1980    context: &RewriteContext<'_>,
1981    mut items: impl Iterator<Item = &'a T>,
1982    span: Span,
1983    shape: Shape,
1984    is_singleton_tuple: bool,
1985) -> RewriteResult {
1986    // In case of length 1, need a trailing comma
1987    debug!("rewrite_tuple_in_visual_indent_style {:?}", shape);
1988    if is_singleton_tuple {
1989        // 3 = "(" + ",)"
1990        let nested_shape = shape.sub_width(3, span)?.visual_indent(1);
1991        return items
1992            .next()
1993            .unwrap()
1994            .rewrite_result(context, nested_shape)
1995            .map(|s| format!("({},)", s));
1996    }
1997
1998    let list_lo = context.snippet_provider.span_after(span, "(");
1999    let nested_shape = shape.sub_width(2, span)?.visual_indent(1);
2000    let items = itemize_list(
2001        context.snippet_provider,
2002        items,
2003        ")",
2004        ",",
2005        |item| item.span().lo(),
2006        |item| item.span().hi(),
2007        |item| item.rewrite_result(context, nested_shape),
2008        list_lo,
2009        span.hi() - BytePos(1),
2010        false,
2011    );
2012    let item_vec: Vec<_> = items.collect();
2013    let tactic = definitive_tactic(
2014        &item_vec,
2015        ListTactic::HorizontalVertical,
2016        Separator::Comma,
2017        nested_shape.width,
2018    );
2019    let fmt = ListFormatting::new(nested_shape, context.config)
2020        .tactic(tactic)
2021        .ends_with_newline(false);
2022    let list_str = write_list(&item_vec, &fmt)?;
2023
2024    Ok(format!("({list_str})"))
2025}
2026
2027fn rewrite_let(
2028    context: &RewriteContext<'_>,
2029    shape: Shape,
2030    pat: &ast::Pat,
2031    expr: &ast::Expr,
2032) -> RewriteResult {
2033    let mut result = "let ".to_owned();
2034
2035    // TODO(ytmimi) comments could appear between `let` and the `pat`
2036
2037    // 4 = "let ".len()
2038    let mut pat_shape = shape.offset_left(4, pat.span)?;
2039    if context.config.style_edition() >= StyleEdition::Edition2027 {
2040        // 2 for the length of " ="
2041        pat_shape = pat_shape.sub_width(2, pat.span)?;
2042    }
2043    let pat_str = pat.rewrite_result(context, pat_shape)?;
2044    result.push_str(&pat_str);
2045
2046    // TODO(ytmimi) comments could appear between `pat` and `=`
2047    result.push_str(" =");
2048
2049    let comments_lo = context
2050        .snippet_provider
2051        .span_after(expr.span.with_lo(pat.span.hi()), "=");
2052    let comments_span = mk_sp(comments_lo, expr.span.lo());
2053    rewrite_assign_rhs_with_comments(
2054        context,
2055        result,
2056        expr,
2057        shape,
2058        &RhsAssignKind::Expr(&expr.kind, expr.span),
2059        RhsTactics::Default,
2060        comments_span,
2061        true,
2062    )
2063}
2064
2065pub(crate) fn rewrite_tuple<'a, T: 'a + IntoOverflowableItem<'a>>(
2066    context: &'a RewriteContext<'_>,
2067    items: impl Iterator<Item = &'a T>,
2068    span: Span,
2069    shape: Shape,
2070    is_singleton_tuple: bool,
2071) -> RewriteResult {
2072    debug!("rewrite_tuple {:?}", shape);
2073    if context.use_block_indent() {
2074        // We use the same rule as function calls for rewriting tuples.
2075        let force_tactic = if context.inside_macro() {
2076            if span_ends_with_comma(context, span) {
2077                Some(SeparatorTactic::Always)
2078            } else {
2079                Some(SeparatorTactic::Never)
2080            }
2081        } else if is_singleton_tuple {
2082            Some(SeparatorTactic::Always)
2083        } else {
2084            None
2085        };
2086        overflow::rewrite_with_parens(
2087            context,
2088            "",
2089            items,
2090            shape,
2091            span,
2092            context.config.fn_call_width(),
2093            force_tactic,
2094        )
2095    } else {
2096        rewrite_tuple_in_visual_indent_style(context, items, span, shape, is_singleton_tuple)
2097    }
2098}
2099
2100pub(crate) fn rewrite_unary_prefix<R: Rewrite + Spanned>(
2101    context: &RewriteContext<'_>,
2102    prefix: &str,
2103    rewrite: &R,
2104    shape: Shape,
2105) -> RewriteResult {
2106    let shape = shape.offset_left(prefix.len(), rewrite.span())?;
2107    rewrite
2108        .rewrite_result(context, shape)
2109        .map(|r| format!("{}{}", prefix, r))
2110}
2111
2112// FIXME: this is probably not correct for multi-line Rewrites. we should
2113// subtract suffix.len() from the last line budget, not the first!
2114pub(crate) fn rewrite_unary_suffix<R: Rewrite + Spanned>(
2115    context: &RewriteContext<'_>,
2116    suffix: &str,
2117    rewrite: &R,
2118    shape: Shape,
2119) -> RewriteResult {
2120    let shape = shape.sub_width(suffix.len(), rewrite.span())?;
2121    rewrite.rewrite_result(context, shape).map(|mut r| {
2122        r.push_str(suffix);
2123        r
2124    })
2125}
2126
2127fn rewrite_unary_op(
2128    context: &RewriteContext<'_>,
2129    op: ast::UnOp,
2130    expr: &ast::Expr,
2131    shape: Shape,
2132) -> RewriteResult {
2133    // For some reason, an UnOp is not spanned like BinOp!
2134    rewrite_unary_prefix(context, op.as_str(), expr, shape)
2135}
2136
2137pub(crate) enum RhsAssignKind<'ast> {
2138    Expr(&'ast ast::ExprKind, #[allow(dead_code)] Span),
2139    Bounds,
2140    Ty,
2141}
2142
2143impl<'ast> RhsAssignKind<'ast> {
2144    // TODO(calebcartwright)
2145    // Preemptive addition for handling RHS with chains, not yet utilized.
2146    // It may make more sense to construct the chain first and then check
2147    // whether there are actually chain elements.
2148    #[allow(dead_code)]
2149    fn is_chain(&self) -> bool {
2150        match self {
2151            RhsAssignKind::Expr(kind, _) => {
2152                matches!(
2153                    kind,
2154                    ast::ExprKind::Try(..)
2155                        | ast::ExprKind::Field(..)
2156                        | ast::ExprKind::MethodCall(..)
2157                        | ast::ExprKind::Await(_, _)
2158                )
2159            }
2160            _ => false,
2161        }
2162    }
2163}
2164
2165fn rewrite_assignment(
2166    context: &RewriteContext<'_>,
2167    lhs: &ast::Expr,
2168    rhs: &ast::Expr,
2169    op: Option<&ast::AssignOp>,
2170    shape: Shape,
2171) -> RewriteResult {
2172    let operator_str = match op {
2173        Some(op) => context.snippet(op.span),
2174        None => "=",
2175    };
2176
2177    // 1 = space between lhs and operator.
2178    let lhs_shape = shape.sub_width(operator_str.len() + 1, lhs.span())?;
2179    let lhs_str = format!(
2180        "{} {}",
2181        lhs.rewrite_result(context, lhs_shape)?,
2182        operator_str
2183    );
2184
2185    rewrite_assign_rhs(
2186        context,
2187        lhs_str,
2188        rhs,
2189        &RhsAssignKind::Expr(&rhs.kind, rhs.span),
2190        shape,
2191    )
2192}
2193
2194/// Controls where to put the rhs.
2195#[derive(Debug, Copy, Clone, PartialEq, Eq)]
2196pub(crate) enum RhsTactics {
2197    /// Use heuristics.
2198    Default,
2199    /// Put the rhs on the next line if it uses multiple line, without extra indentation.
2200    ForceNextLineWithoutIndent,
2201    /// Allow overflowing max width if neither `Default` nor `ForceNextLineWithoutIndent`
2202    /// did not work.
2203    AllowOverflow,
2204}
2205
2206// The left hand side must contain everything up to, and including, the
2207// assignment operator.
2208pub(crate) fn rewrite_assign_rhs<S: Into<String>, R: Rewrite>(
2209    context: &RewriteContext<'_>,
2210    lhs: S,
2211    ex: &R,
2212    rhs_kind: &RhsAssignKind<'_>,
2213    shape: Shape,
2214) -> RewriteResult {
2215    rewrite_assign_rhs_with(context, lhs, ex, shape, rhs_kind, RhsTactics::Default)
2216}
2217
2218pub(crate) fn rewrite_assign_rhs_expr<R: Rewrite>(
2219    context: &RewriteContext<'_>,
2220    lhs: &str,
2221    ex: &R,
2222    shape: Shape,
2223    rhs_kind: &RhsAssignKind<'_>,
2224    rhs_tactics: RhsTactics,
2225) -> RewriteResult {
2226    let last_line_width = last_line_width(lhs).saturating_sub(if lhs.contains('\n') {
2227        shape.indent.width()
2228    } else {
2229        0
2230    });
2231    // 1 = space between operator and rhs.
2232    let orig_shape = shape.offset_left_opt(last_line_width + 1).unwrap_or(Shape {
2233        width: 0,
2234        offset: shape.offset + last_line_width + 1,
2235        ..shape
2236    });
2237    let has_rhs_comment = if let Some(offset) = lhs.find_last_uncommented("=") {
2238        lhs.trim_end().len() > offset + 1
2239    } else {
2240        false
2241    };
2242
2243    choose_rhs(
2244        context,
2245        ex,
2246        orig_shape,
2247        ex.rewrite_result(context, orig_shape),
2248        rhs_kind,
2249        rhs_tactics,
2250        has_rhs_comment,
2251    )
2252}
2253
2254pub(crate) fn rewrite_assign_rhs_with<S: Into<String>, R: Rewrite>(
2255    context: &RewriteContext<'_>,
2256    lhs: S,
2257    ex: &R,
2258    shape: Shape,
2259    rhs_kind: &RhsAssignKind<'_>,
2260    rhs_tactics: RhsTactics,
2261) -> RewriteResult {
2262    let lhs = lhs.into();
2263    let rhs = rewrite_assign_rhs_expr(context, &lhs, ex, shape, rhs_kind, rhs_tactics)?;
2264    Ok(lhs + &rhs)
2265}
2266
2267pub(crate) fn rewrite_assign_rhs_with_comments<S: Into<String>, R: Rewrite + Spanned>(
2268    context: &RewriteContext<'_>,
2269    lhs: S,
2270    ex: &R,
2271    shape: Shape,
2272    rhs_kind: &RhsAssignKind<'_>,
2273    rhs_tactics: RhsTactics,
2274    between_span: Span,
2275    allow_extend: bool,
2276) -> RewriteResult {
2277    let lhs = lhs.into();
2278    let contains_comment = contains_comment(context.snippet(between_span));
2279    let shape = if contains_comment {
2280        shape.block_left(
2281            context.config.tab_spaces(),
2282            between_span.with_hi(ex.span().hi()),
2283        )?
2284    } else {
2285        shape
2286    };
2287    let rhs = rewrite_assign_rhs_expr(context, &lhs, ex, shape, rhs_kind, rhs_tactics)?;
2288    if contains_comment {
2289        let rhs = rhs.trim_start();
2290        combine_strs_with_missing_comments(context, &lhs, rhs, between_span, shape, allow_extend)
2291    } else {
2292        Ok(lhs + &rhs)
2293    }
2294}
2295
2296fn choose_rhs<R: Rewrite>(
2297    context: &RewriteContext<'_>,
2298    expr: &R,
2299    shape: Shape,
2300    orig_rhs: RewriteResult,
2301    _rhs_kind: &RhsAssignKind<'_>,
2302    rhs_tactics: RhsTactics,
2303    has_rhs_comment: bool,
2304) -> RewriteResult {
2305    match orig_rhs {
2306        Ok(ref new_str) if new_str.is_empty() => Ok(String::new()),
2307        Ok(ref new_str) if !new_str.contains('\n') && unicode_str_width(new_str) <= shape.width => {
2308            Ok(format!(" {new_str}"))
2309        }
2310        _ => {
2311            // Expression did not fit on the same line as the identifier.
2312            // Try splitting the line and see if that works better.
2313            let new_shape = shape_from_rhs_tactic(context, shape, rhs_tactics)
2314                // TODO(ding-young) Ideally, we can replace unknown_error() with max_width_error(),
2315                // but this requires either implementing the Spanned trait for ast::GenericBounds
2316                // or grabbing the span from the call site.
2317                .unknown_error()?;
2318            let new_rhs = expr.rewrite_result(context, new_shape);
2319            let new_indent_str = &shape
2320                .indent
2321                .block_indent(context.config)
2322                .to_string_with_newline(context.config);
2323            let before_space_str = if has_rhs_comment { "" } else { " " };
2324
2325            match (orig_rhs, new_rhs) {
2326                (Ok(ref orig_rhs), Ok(ref new_rhs))
2327                    if !filtered_str_fits(&new_rhs, context.config.max_width(), new_shape) =>
2328                {
2329                    Ok(format!("{before_space_str}{orig_rhs}"))
2330                }
2331                (Ok(ref orig_rhs), Ok(ref new_rhs))
2332                    if prefer_next_line(orig_rhs, new_rhs, rhs_tactics) =>
2333                {
2334                    Ok(format!("{new_indent_str}{new_rhs}"))
2335                }
2336                (Err(_), Ok(ref new_rhs)) => Ok(format!("{new_indent_str}{new_rhs}")),
2337                (Err(_), Err(_)) if rhs_tactics == RhsTactics::AllowOverflow => {
2338                    let shape = shape.infinite_width();
2339                    expr.rewrite_result(context, shape)
2340                        .map(|s| format!("{}{}", before_space_str, s))
2341                }
2342                // When both orig_rhs and new_rhs result in errors, we currently propagate
2343                // the error from the second attempt since it is more generous with
2344                // width constraints. This decision is somewhat arbitrary and is open to change.
2345                (Err(_), Err(new_rhs_err)) => Err(new_rhs_err),
2346                (Ok(orig_rhs), _) => Ok(format!("{before_space_str}{orig_rhs}")),
2347            }
2348        }
2349    }
2350}
2351
2352fn shape_from_rhs_tactic(
2353    context: &RewriteContext<'_>,
2354    shape: Shape,
2355    rhs_tactic: RhsTactics,
2356) -> Option<Shape> {
2357    match rhs_tactic {
2358        RhsTactics::ForceNextLineWithoutIndent => shape
2359            .with_max_width(context.config)
2360            .sub_width_opt(shape.indent.width()),
2361        RhsTactics::Default | RhsTactics::AllowOverflow => {
2362            Shape::indented(shape.indent.block_indent(context.config), context.config)
2363                .sub_width_opt(shape.rhs_overhead(context.config))
2364        }
2365    }
2366}
2367
2368/// Returns true if formatting next_line_rhs is better on a new line when compared to the
2369/// original's line formatting.
2370///
2371/// It is considered better if:
2372/// 1. the tactic is ForceNextLineWithoutIndent
2373/// 2. next_line_rhs doesn't have newlines
2374/// 3. the original line has more newlines than next_line_rhs
2375/// 4. the original formatting of the first line ends with `(`, `{`, or `[` and next_line_rhs
2376///    doesn't
2377pub(crate) fn prefer_next_line(
2378    orig_rhs: &str,
2379    next_line_rhs: &str,
2380    rhs_tactics: RhsTactics,
2381) -> bool {
2382    rhs_tactics == RhsTactics::ForceNextLineWithoutIndent
2383        || !next_line_rhs.contains('\n')
2384        || count_newlines(orig_rhs) > count_newlines(next_line_rhs) + 1
2385        || first_line_ends_with(orig_rhs, '(') && !first_line_ends_with(next_line_rhs, '(')
2386        || first_line_ends_with(orig_rhs, '{') && !first_line_ends_with(next_line_rhs, '{')
2387        || first_line_ends_with(orig_rhs, '[') && !first_line_ends_with(next_line_rhs, '[')
2388}
2389
2390fn rewrite_expr_addrof(
2391    context: &RewriteContext<'_>,
2392    borrow_kind: ast::BorrowKind,
2393    mutability: ast::Mutability,
2394    expr: &ast::Expr,
2395    shape: Shape,
2396) -> RewriteResult {
2397    let operator_str = match (mutability, borrow_kind) {
2398        (ast::Mutability::Not, ast::BorrowKind::Ref) => "&",
2399        (ast::Mutability::Not, ast::BorrowKind::Pin) => "&pin const ",
2400        (ast::Mutability::Not, ast::BorrowKind::Raw) => "&raw const ",
2401        (ast::Mutability::Mut, ast::BorrowKind::Ref) => "&mut ",
2402        (ast::Mutability::Mut, ast::BorrowKind::Pin) => "&pin mut ",
2403        (ast::Mutability::Mut, ast::BorrowKind::Raw) => "&raw mut ",
2404    };
2405    rewrite_unary_prefix(context, operator_str, expr, shape)
2406}
2407
2408pub(crate) fn is_method_call(expr: &ast::Expr) -> bool {
2409    match expr.kind {
2410        ast::ExprKind::MethodCall(..) => true,
2411        ast::ExprKind::AddrOf(_, _, ref expr)
2412        | ast::ExprKind::Cast(ref expr, _)
2413        | ast::ExprKind::Try(ref expr)
2414        | ast::ExprKind::Unary(_, ref expr) => is_method_call(expr),
2415        _ => false,
2416    }
2417}
2418
2419/// Indicates the parts of a float literal specified as a string.
2420struct FloatSymbolParts<'a> {
2421    /// The integer part, e.g. `123` in `123.456e789`.
2422    /// Always non-empty, because in Rust `.1` is not a valid floating-point literal:
2423    /// <https://doc.rust-lang.org/reference/tokens.html#floating-point-literals>
2424    integer_part: &'a str,
2425    /// The fractional part excluding the decimal point, e.g. `456` in `123.456e789`.
2426    fractional_part: Option<&'a str>,
2427    /// The exponent part including the `e` or `E`, e.g. `e789` in `123.456e789`.
2428    exponent: Option<&'a str>,
2429}
2430
2431impl FloatSymbolParts<'_> {
2432    fn is_fractional_part_zero(&self) -> bool {
2433        let zero_literal_regex = static_regex!(r"^[0_]+$");
2434        self.fractional_part
2435            .is_none_or(|s| zero_literal_regex.is_match(s))
2436    }
2437}
2438
2439/// Parses a float literal. The `symbol` must be a valid floating point literal without a type
2440/// suffix. Otherwise the function may panic or return wrong result.
2441fn parse_float_symbol(symbol: &str) -> Result<FloatSymbolParts<'_>, &'static str> {
2442    // This regex may accept invalid float literals (such as `1`, `_` or `2.e3`). That's ok.
2443    // We only use it to parse literals whose validity has already been established.
2444    let float_literal_regex = static_regex!(r"^([0-9_]+)(?:\.([0-9_]+)?)?([eE][+-]?[0-9_]+)?$");
2445    let caps = float_literal_regex
2446        .captures(symbol)
2447        .ok_or("invalid float literal")?;
2448    Ok(FloatSymbolParts {
2449        integer_part: caps.get(1).ok_or("missing integer part")?.as_str(),
2450        fractional_part: caps.get(2).map(|m| m.as_str()),
2451        exponent: caps.get(3).map(|m| m.as_str()),
2452    })
2453}
2454
2455#[cfg(test)]
2456mod test {
2457    use super::*;
2458
2459    #[test]
2460    fn test_last_line_offsetted() {
2461        let lines = "one\n    two";
2462        assert_eq!(last_line_offsetted(2, lines), true);
2463        assert_eq!(last_line_offsetted(4, lines), false);
2464        assert_eq!(last_line_offsetted(6, lines), false);
2465
2466        let lines = "one    two";
2467        assert_eq!(last_line_offsetted(2, lines), false);
2468        assert_eq!(last_line_offsetted(0, lines), false);
2469
2470        let lines = "\ntwo";
2471        assert_eq!(last_line_offsetted(2, lines), false);
2472        assert_eq!(last_line_offsetted(0, lines), false);
2473
2474        let lines = "one\n    two      three";
2475        assert_eq!(last_line_offsetted(2, lines), true);
2476        let lines = "one\n two      three";
2477        assert_eq!(last_line_offsetted(2, lines), false);
2478    }
2479
2480    #[test]
2481    fn test_parse_float_symbol() {
2482        let parts = parse_float_symbol("123.456e789").unwrap();
2483        assert_eq!(parts.integer_part, "123");
2484        assert_eq!(parts.fractional_part, Some("456"));
2485        assert_eq!(parts.exponent, Some("e789"));
2486
2487        let parts = parse_float_symbol("123.456e+789").unwrap();
2488        assert_eq!(parts.integer_part, "123");
2489        assert_eq!(parts.fractional_part, Some("456"));
2490        assert_eq!(parts.exponent, Some("e+789"));
2491
2492        let parts = parse_float_symbol("123.456e-789").unwrap();
2493        assert_eq!(parts.integer_part, "123");
2494        assert_eq!(parts.fractional_part, Some("456"));
2495        assert_eq!(parts.exponent, Some("e-789"));
2496
2497        let parts = parse_float_symbol("123e789").unwrap();
2498        assert_eq!(parts.integer_part, "123");
2499        assert_eq!(parts.fractional_part, None);
2500        assert_eq!(parts.exponent, Some("e789"));
2501
2502        let parts = parse_float_symbol("123E789").unwrap();
2503        assert_eq!(parts.integer_part, "123");
2504        assert_eq!(parts.fractional_part, None);
2505        assert_eq!(parts.exponent, Some("E789"));
2506
2507        let parts = parse_float_symbol("123.").unwrap();
2508        assert_eq!(parts.integer_part, "123");
2509        assert_eq!(parts.fractional_part, None);
2510        assert_eq!(parts.exponent, None);
2511    }
2512
2513    #[test]
2514    fn test_parse_float_symbol_with_underscores() {
2515        let parts = parse_float_symbol("_123._456e_789").unwrap();
2516        assert_eq!(parts.integer_part, "_123");
2517        assert_eq!(parts.fractional_part, Some("_456"));
2518        assert_eq!(parts.exponent, Some("e_789"));
2519
2520        let parts = parse_float_symbol("123_.456_e789_").unwrap();
2521        assert_eq!(parts.integer_part, "123_");
2522        assert_eq!(parts.fractional_part, Some("456_"));
2523        assert_eq!(parts.exponent, Some("e789_"));
2524
2525        let parts = parse_float_symbol("1_23.4_56e7_89").unwrap();
2526        assert_eq!(parts.integer_part, "1_23");
2527        assert_eq!(parts.fractional_part, Some("4_56"));
2528        assert_eq!(parts.exponent, Some("e7_89"));
2529
2530        let parts = parse_float_symbol("_1_23_._4_56_e_7_89_").unwrap();
2531        assert_eq!(parts.integer_part, "_1_23_");
2532        assert_eq!(parts.fractional_part, Some("_4_56_"));
2533        assert_eq!(parts.exponent, Some("e_7_89_"));
2534    }
2535
2536    #[test]
2537    fn test_float_lit_ends_in_dot() {
2538        type TZ = FloatLiteralTrailingZero;
2539
2540        assert!(float_lit_ends_in_dot("1.", None, TZ::Preserve));
2541        assert!(!float_lit_ends_in_dot("1.0", None, TZ::Preserve));
2542        assert!(!float_lit_ends_in_dot("1.e2", None, TZ::Preserve));
2543        assert!(!float_lit_ends_in_dot("1.0e2", None, TZ::Preserve));
2544        assert!(!float_lit_ends_in_dot("1.", Some("f32"), TZ::Preserve));
2545        assert!(!float_lit_ends_in_dot("1.0", Some("f32"), TZ::Preserve));
2546
2547        assert!(!float_lit_ends_in_dot("1.", None, TZ::Always));
2548        assert!(!float_lit_ends_in_dot("1.0", None, TZ::Always));
2549        assert!(!float_lit_ends_in_dot("1.e2", None, TZ::Always));
2550        assert!(!float_lit_ends_in_dot("1.0e2", None, TZ::Always));
2551        assert!(!float_lit_ends_in_dot("1.", Some("f32"), TZ::Always));
2552        assert!(!float_lit_ends_in_dot("1.0", Some("f32"), TZ::Always));
2553
2554        assert!(!float_lit_ends_in_dot("1.", None, TZ::IfNoPostfix));
2555        assert!(!float_lit_ends_in_dot("1.0", None, TZ::IfNoPostfix));
2556        assert!(!float_lit_ends_in_dot("1.e2", None, TZ::IfNoPostfix));
2557        assert!(!float_lit_ends_in_dot("1.0e2", None, TZ::IfNoPostfix));
2558        assert!(!float_lit_ends_in_dot("1.", Some("f32"), TZ::IfNoPostfix));
2559        assert!(!float_lit_ends_in_dot("1.0", Some("f32"), TZ::IfNoPostfix));
2560
2561        assert!(float_lit_ends_in_dot("1.", None, TZ::Never));
2562        assert!(float_lit_ends_in_dot("1.0", None, TZ::Never));
2563        assert!(!float_lit_ends_in_dot("1.e2", None, TZ::Never));
2564        assert!(!float_lit_ends_in_dot("1.0e2", None, TZ::Never));
2565        assert!(!float_lit_ends_in_dot("1.", Some("f32"), TZ::Never));
2566        assert!(!float_lit_ends_in_dot("1.0", Some("f32"), TZ::Never));
2567    }
2568}