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