From b3124e52b3a547e1218874387e88f83091e06255 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 15 May 2024 22:34:39 -0700 Subject: [PATCH] Insert necessary parentheses in ToTokens for Expr --- src/classify.rs | 193 +++++++++++++++- src/expr.rs | 573 ++++++++++++++++++++++++++++++++++++++-------- src/fixup.rs | 215 +++++++++++++++++ src/generics.rs | 8 + src/lib.rs | 14 +- src/precedence.rs | 78 +++++++ src/stmt.rs | 10 +- 7 files changed, 990 insertions(+), 101 deletions(-) create mode 100644 src/fixup.rs diff --git a/src/classify.rs b/src/classify.rs index 7b1bbc324a..be9bf3bd85 100644 --- a/src/classify.rs +++ b/src/classify.rs @@ -3,10 +3,11 @@ use crate::generics::TypeParamBound; use crate::path::{Path, PathArguments}; use crate::punctuated::Punctuated; use crate::ty::{ReturnType, Type}; +#[cfg(feature = "full")] use proc_macro2::{Delimiter, TokenStream, TokenTree}; use std::ops::ControlFlow; -#[cfg(feature = "parsing")] +#[cfg(feature = "full")] pub(crate) fn requires_semi_to_be_stmt(expr: &Expr) -> bool { match expr { Expr::Macro(expr) => !expr.mac.delimiter.is_brace(), @@ -14,6 +15,7 @@ pub(crate) fn requires_semi_to_be_stmt(expr: &Expr) -> bool { } } +#[cfg(feature = "full")] pub(crate) fn requires_comma_to_be_match_arm(expr: &Expr) -> bool { match expr { Expr::If(_) @@ -58,7 +60,196 @@ pub(crate) fn requires_comma_to_be_match_arm(expr: &Expr) -> bool { } } +#[cfg(all(feature = "printing", feature = "full"))] +pub(crate) fn confusable_with_adjacent_block(mut expr: &Expr) -> bool { + let mut stack = Vec::new(); + + while let Some(next) = match expr { + Expr::Assign(e) => { + stack.push(&e.right); + Some(&e.left) + } + Expr::Await(e) => Some(&e.base), + Expr::Binary(e) => { + stack.push(&e.right); + Some(&e.left) + } + Expr::Break(e) => { + if let Some(Expr::Block(_)) = e.expr.as_deref() { + return true; + } + stack.pop() + } + Expr::Call(e) => Some(&e.func), + Expr::Cast(e) => Some(&e.expr), + Expr::Closure(e) => Some(&e.body), + Expr::Field(e) => Some(&e.base), + Expr::Index(e) => Some(&e.expr), + Expr::Let(e) => Some(&e.expr), + Expr::MethodCall(e) => Some(&e.receiver), + Expr::Range(e) => { + if let Some(Expr::Block(_)) = e.end.as_deref() { + return true; + } + match (&e.start, &e.end) { + (Some(start), end) => { + stack.extend(end); + Some(start) + } + (None, Some(end)) => Some(end), + (None, None) => stack.pop(), + } + } + Expr::Reference(e) => Some(&e.expr), + Expr::Return(e) => { + if e.expr.is_none() && stack.is_empty() { + return true; + } + stack.pop() + } + Expr::Struct(_) => return true, + Expr::Try(e) => Some(&e.expr), + Expr::Unary(e) => Some(&e.expr), + Expr::Yield(e) => { + if e.expr.is_none() && stack.is_empty() { + return true; + } + stack.pop() + } + + Expr::Array(_) + | Expr::Async(_) + | Expr::Block(_) + | Expr::Const(_) + | Expr::Continue(_) + | Expr::ForLoop(_) + | Expr::Group(_) + | Expr::If(_) + | Expr::Infer(_) + | Expr::Lit(_) + | Expr::Loop(_) + | Expr::Macro(_) + | Expr::Match(_) + | Expr::Paren(_) + | Expr::Path(_) + | Expr::Repeat(_) + | Expr::TryBlock(_) + | Expr::Tuple(_) + | Expr::Unsafe(_) + | Expr::Verbatim(_) + | Expr::While(_) => stack.pop(), + } { + expr = next; + } + + false +} + +#[cfg(feature = "printing")] +pub(crate) fn confusable_with_trailing_lt(mut expr: &Expr) -> bool { + loop { + match expr { + Expr::Binary(e) => expr = &e.right, + Expr::Cast(e) => return type_trailing_unparameterized_path(&e.ty), + Expr::Reference(e) => expr = &e.expr, + Expr::Unary(e) => expr = &e.expr, + + Expr::Array(_) + | Expr::Assign(_) + | Expr::Async(_) + | Expr::Await(_) + | Expr::Block(_) + | Expr::Break(_) + | Expr::Call(_) + | Expr::Closure(_) + | Expr::Const(_) + | Expr::Continue(_) + | Expr::Field(_) + | Expr::ForLoop(_) + | Expr::Group(_) + | Expr::If(_) + | Expr::Index(_) + | Expr::Infer(_) + | Expr::Let(_) + | Expr::Lit(_) + | Expr::Loop(_) + | Expr::Macro(_) + | Expr::Match(_) + | Expr::MethodCall(_) + | Expr::Paren(_) + | Expr::Path(_) + | Expr::Range(_) + | Expr::Repeat(_) + | Expr::Return(_) + | Expr::Struct(_) + | Expr::Try(_) + | Expr::TryBlock(_) + | Expr::Tuple(_) + | Expr::Unsafe(_) + | Expr::Verbatim(_) + | Expr::While(_) + | Expr::Yield(_) => return false, + } + } + + fn type_trailing_unparameterized_path(mut ty: &Type) -> bool { + loop { + match ty { + Type::BareFn(t) => match &t.output { + ReturnType::Default => return false, + ReturnType::Type(_, ret) => ty = ret, + }, + Type::ImplTrait(t) => match last_type_in_bounds(&t.bounds) { + ControlFlow::Break(trailing_path) => return trailing_path, + ControlFlow::Continue(t) => ty = t, + }, + Type::Path(t) => match last_type_in_path(&t.path) { + ControlFlow::Break(trailing_path) => return trailing_path, + ControlFlow::Continue(t) => ty = t, + }, + Type::Ptr(t) => ty = &t.elem, + Type::Reference(t) => ty = &t.elem, + Type::TraitObject(t) => match last_type_in_bounds(&t.bounds) { + ControlFlow::Break(trailing_path) => return trailing_path, + ControlFlow::Continue(t) => ty = t, + }, + + Type::Array(_) + | Type::Group(_) + | Type::Infer(_) + | Type::Macro(_) + | Type::Never(_) + | Type::Paren(_) + | Type::Slice(_) + | Type::Tuple(_) + | Type::Verbatim(_) => return false, + } + } + } + + fn last_type_in_path(path: &Path) -> ControlFlow { + match &path.segments.last().unwrap().arguments { + PathArguments::None => ControlFlow::Break(true), + PathArguments::AngleBracketed(_) => ControlFlow::Break(false), + PathArguments::Parenthesized(arg) => match &arg.output { + ReturnType::Default => ControlFlow::Break(false), + ReturnType::Type(_, ret) => ControlFlow::Continue(ret), + }, + } + } + + fn last_type_in_bounds( + bounds: &Punctuated, + ) -> ControlFlow { + match bounds.last().unwrap() { + TypeParamBound::Trait(t) => last_type_in_path(&t.path), + TypeParamBound::Lifetime(_) | TypeParamBound::Verbatim(_) => ControlFlow::Break(false), + } + } +} + /// Whether the expression's last token is `}`. +#[cfg(feature = "full")] pub(crate) fn expr_trailing_brace(mut expr: &Expr) -> bool { loop { match expr { diff --git a/src/expr.rs b/src/expr.rs index 2a9478dadb..d08c3f52b3 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -2976,41 +2976,30 @@ pub(crate) mod printing { use crate::attr::Attribute; #[cfg(feature = "full")] use crate::attr::FilterAttrs; - #[cfg(feature = "full")] use crate::classify; #[cfg(feature = "full")] use crate::expr::{ - Arm, Expr, ExprArray, ExprAssign, ExprAsync, ExprAwait, ExprBlock, ExprBreak, ExprClosure, + Arm, ExprArray, ExprAssign, ExprAsync, ExprAwait, ExprBlock, ExprBreak, ExprClosure, ExprConst, ExprContinue, ExprForLoop, ExprIf, ExprInfer, ExprLet, ExprLoop, ExprMatch, ExprRange, ExprRepeat, ExprReturn, ExprTry, ExprTryBlock, ExprTuple, ExprUnsafe, ExprWhile, ExprYield, Label, RangeLimits, }; use crate::expr::{ - ExprBinary, ExprCall, ExprCast, ExprField, ExprGroup, ExprIndex, ExprLit, ExprMacro, + Expr, ExprBinary, ExprCall, ExprCast, ExprField, ExprGroup, ExprIndex, ExprLit, ExprMacro, ExprMethodCall, ExprParen, ExprPath, ExprReference, ExprStruct, ExprUnary, FieldValue, Index, Member, }; - use crate::path; #[cfg(feature = "full")] + use crate::fixup::FixupContext; + use crate::op::BinOp; + use crate::path; + use crate::precedence::Precedence; use crate::token; #[cfg(feature = "full")] use crate::ty::ReturnType; use proc_macro2::{Literal, Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; - // If the given expression is a bare `ExprStruct`, wraps it in parenthesis - // before appending it to `TokenStream`. - #[cfg(feature = "full")] - fn wrap_bare_struct(tokens: &mut TokenStream, e: &Expr) { - if let Expr::Struct(_) = *e { - token::Paren::default().surround(tokens, |tokens| { - e.to_tokens(tokens); - }); - } else { - e.to_tokens(tokens); - } - } - #[cfg(feature = "full")] pub(crate) fn outer_attrs_to_tokens(attrs: &[Attribute], tokens: &mut TokenStream) { tokens.append_all(attrs.outer()); @@ -3024,6 +3013,107 @@ pub(crate) mod printing { #[cfg(not(feature = "full"))] pub(crate) fn outer_attrs_to_tokens(_attrs: &[Attribute], _tokens: &mut TokenStream) {} + #[cfg(feature = "full")] + fn print_condition(expr: &Expr, tokens: &mut TokenStream) { + print_subexpression( + expr, + classify::confusable_with_adjacent_block(expr), + tokens, + FixupContext::new_condition(), + ); + } + + fn print_subexpression( + expr: &Expr, + needs_group: bool, + tokens: &mut TokenStream, + #[cfg(feature = "full")] mut fixup: FixupContext, + ) { + #[cfg(not(feature = "full"))] + let do_print_expr = |tokens: &mut TokenStream| expr.to_tokens(tokens); + + #[cfg(feature = "full")] + let do_print_expr = { + // If we are surrounding the whole cond in parentheses, such as: + // + // if (return Struct {}) {} + // + // then there is no need for parenthesizing the individual struct + // expressions within. On the other hand if the whole cond is not + // parenthesized, then print_expr must parenthesize exterior struct + // literals. + // + // if x == (Struct {}) {} + // + if needs_group { + fixup = FixupContext::default(); + } + |tokens: &mut TokenStream| print_expr(expr, tokens, fixup) + }; + + if needs_group { + token::Paren::default().surround(tokens, do_print_expr); + } else { + do_print_expr(tokens); + } + } + + #[cfg(feature = "full")] + pub(crate) fn print_expr(expr: &Expr, tokens: &mut TokenStream, mut fixup: FixupContext) { + let needs_group = fixup.would_cause_statement_boundary(expr); + if needs_group { + fixup = FixupContext::default(); + } + + let do_print_expr = |tokens: &mut TokenStream| match expr { + Expr::Array(e) => e.to_tokens(tokens), + Expr::Assign(e) => print_expr_assign(e, tokens, fixup), + Expr::Async(e) => e.to_tokens(tokens), + Expr::Await(e) => print_expr_await(e, tokens, fixup), + Expr::Binary(e) => print_expr_binary(e, tokens, fixup), + Expr::Block(e) => e.to_tokens(tokens), + Expr::Break(e) => print_expr_break(e, tokens, fixup), + Expr::Call(e) => print_expr_call(e, tokens, fixup), + Expr::Cast(e) => print_expr_cast(e, tokens, fixup), + Expr::Closure(e) => e.to_tokens(tokens), + Expr::Const(e) => e.to_tokens(tokens), + Expr::Continue(e) => e.to_tokens(tokens), + Expr::Field(e) => print_expr_field(e, tokens, fixup), + Expr::ForLoop(e) => e.to_tokens(tokens), + Expr::Group(e) => e.to_tokens(tokens), + Expr::If(e) => e.to_tokens(tokens), + Expr::Index(e) => print_expr_index(e, tokens, fixup), + Expr::Infer(e) => e.to_tokens(tokens), + Expr::Let(e) => print_expr_let(e, tokens, fixup), + Expr::Lit(e) => e.to_tokens(tokens), + Expr::Loop(e) => e.to_tokens(tokens), + Expr::Macro(e) => e.to_tokens(tokens), + Expr::Match(e) => e.to_tokens(tokens), + Expr::MethodCall(e) => print_expr_method_call(e, tokens, fixup), + Expr::Paren(e) => e.to_tokens(tokens), + Expr::Path(e) => e.to_tokens(tokens), + Expr::Range(e) => print_expr_range(e, tokens, fixup), + Expr::Reference(e) => print_expr_reference(e, tokens, fixup), + Expr::Repeat(e) => e.to_tokens(tokens), + Expr::Return(e) => print_expr_return(e, tokens, fixup), + Expr::Struct(e) => e.to_tokens(tokens), + Expr::Try(e) => print_expr_try(e, tokens, fixup), + Expr::TryBlock(e) => e.to_tokens(tokens), + Expr::Tuple(e) => e.to_tokens(tokens), + Expr::Unary(e) => print_expr_unary(e, tokens, fixup), + Expr::Unsafe(e) => e.to_tokens(tokens), + Expr::Verbatim(e) => e.to_tokens(tokens), + Expr::While(e) => e.to_tokens(tokens), + Expr::Yield(e) => print_expr_yield(e, tokens, fixup), + }; + + if needs_group { + token::Paren::default().surround(tokens, do_print_expr); + } else { + do_print_expr(tokens); + } + } + #[cfg(feature = "full")] #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprArray { @@ -3039,13 +3129,29 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprAssign { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.left.to_tokens(tokens); - self.eq_token.to_tokens(tokens); - self.right.to_tokens(tokens); + let fixup = FixupContext::default(); + print_expr_assign(self, tokens, fixup); } } + #[cfg(feature = "full")] + fn print_expr_assign(e: &ExprAssign, tokens: &mut TokenStream, fixup: FixupContext) { + outer_attrs_to_tokens(&e.attrs, tokens); + print_subexpression( + &e.left, + Precedence::of(&e.left) <= Precedence::Assign, + tokens, + fixup.leftmost_subexpression(), + ); + e.eq_token.to_tokens(tokens); + print_subexpression( + &e.right, + Precedence::of_rhs(&e.right) < Precedence::Assign, + tokens, + fixup.subsequent_subexpression(), + ); + } + #[cfg(feature = "full")] #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprAsync { @@ -3061,21 +3167,94 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprAwait { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.base.to_tokens(tokens); - self.dot_token.to_tokens(tokens); - self.await_token.to_tokens(tokens); + let fixup = FixupContext::default(); + print_expr_await(self, tokens, fixup); } } + #[cfg(feature = "full")] + fn print_expr_await(e: &ExprAwait, tokens: &mut TokenStream, fixup: FixupContext) { + outer_attrs_to_tokens(&e.attrs, tokens); + print_subexpression( + &e.base, + Precedence::of(&e.base) < Precedence::Postfix, + tokens, + fixup.leftmost_subexpression_with_dot(), + ); + e.dot_token.to_tokens(tokens); + e.await_token.to_tokens(tokens); + } + #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprBinary { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.left.to_tokens(tokens); - self.op.to_tokens(tokens); - self.right.to_tokens(tokens); + print_expr_binary( + self, + tokens, + #[cfg(feature = "full")] + FixupContext::default(), + ); + } + } + + fn print_expr_binary( + e: &ExprBinary, + tokens: &mut TokenStream, + #[cfg(feature = "full")] fixup: FixupContext, + ) { + outer_attrs_to_tokens(&e.attrs, tokens); + + let binop_prec = Precedence::of_binop(&e.op); + let left_prec = Precedence::of(&e.left); + let right_prec = Precedence::of_rhs(&e.right); + let (mut left_needs_group, right_needs_group) = if let Precedence::Assign = binop_prec { + (left_prec <= binop_prec, right_prec < binop_prec) + } else { + (left_prec < binop_prec, right_prec <= binop_prec) + }; + + // These cases require parenthesization independently of precedence. + match (&*e.left, &e.op) { + // `x as i32 < y` has the parser thinking that `i32 < y` is the + // beginning of a path type. It starts trying to parse `x as (i32 < + // y ...` instead of `(x as i32) < ...`. We need to convince it + // _not_ to do that. + (_, BinOp::Lt(_) | BinOp::Shl(_)) if classify::confusable_with_trailing_lt(&e.left) => { + left_needs_group = true; + } + + // We are given `(let _ = a) OP b`. + // + // - When `OP <= LAnd` we should print `let _ = a OP b` to avoid + // redundant parens as the parser will interpret this as `(let _ = + // a) OP b`. + // + // - Otherwise, e.g. when we have `(let a = b) < c` in AST, parens + // are required since the parser would interpret `let a = b < c` + // as `let a = (b < c)`. To achieve this, we force parens. + #[cfg(feature = "full")] + (Expr::Let(_), _) if binop_prec > Precedence::And => { + left_needs_group = true; + } + + _ => {} } + + print_subexpression( + &e.left, + left_needs_group, + tokens, + #[cfg(feature = "full")] + fixup.leftmost_subexpression(), + ); + e.op.to_tokens(tokens); + print_subexpression( + &e.right, + right_needs_group, + tokens, + #[cfg(feature = "full")] + fixup.subsequent_subexpression(), + ); } #[cfg(feature = "full")] @@ -3095,34 +3274,87 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprBreak { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.break_token.to_tokens(tokens); - self.label.to_tokens(tokens); - self.expr.to_tokens(tokens); + let fixup = FixupContext::default(); + print_expr_break(self, tokens, fixup); + } + } + + #[cfg(feature = "full")] + fn print_expr_break(e: &ExprBreak, tokens: &mut TokenStream, fixup: FixupContext) { + outer_attrs_to_tokens(&e.attrs, tokens); + e.break_token.to_tokens(tokens); + e.label.to_tokens(tokens); + if let Some(expr) = &e.expr { + print_expr(expr, tokens, fixup.subsequent_subexpression()); } } #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprCall { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.func.to_tokens(tokens); - self.paren_token.surround(tokens, |tokens| { - self.args.to_tokens(tokens); - }); + print_expr_call( + self, + tokens, + #[cfg(feature = "full")] + FixupContext::default(), + ); } } + fn print_expr_call( + e: &ExprCall, + tokens: &mut TokenStream, + #[cfg(feature = "full")] fixup: FixupContext, + ) { + outer_attrs_to_tokens(&e.attrs, tokens); + + let precedence = if let Expr::Field(_) = &*e.func { + Precedence::Any + } else { + Precedence::Postfix + }; + print_subexpression( + &e.func, + Precedence::of(&e.func) < precedence, + tokens, + #[cfg(feature = "full")] + fixup.leftmost_subexpression(), + ); + + e.paren_token.surround(tokens, |tokens| { + e.args.to_tokens(tokens); + }); + } + #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprCast { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.expr.to_tokens(tokens); - self.as_token.to_tokens(tokens); - self.ty.to_tokens(tokens); + print_expr_cast( + self, + tokens, + #[cfg(feature = "full")] + FixupContext::default(), + ); } } + fn print_expr_cast( + e: &ExprCast, + tokens: &mut TokenStream, + #[cfg(feature = "full")] fixup: FixupContext, + ) { + outer_attrs_to_tokens(&e.attrs, tokens); + print_subexpression( + &e.expr, + Precedence::of(&e.expr) < Precedence::Cast, + tokens, + #[cfg(feature = "full")] + fixup.leftmost_subexpression(), + ); + e.as_token.to_tokens(tokens); + e.ty.to_tokens(tokens); + } + #[cfg(feature = "full")] #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprClosure { @@ -3141,7 +3373,7 @@ pub(crate) mod printing { self.body.to_tokens(tokens); } else { token::Brace::default().surround(tokens, |tokens| { - self.body.to_tokens(tokens); + print_expr(&self.body, tokens, FixupContext::new_stmt()); }); } } @@ -3173,13 +3405,32 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprField { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.base.to_tokens(tokens); - self.dot_token.to_tokens(tokens); - self.member.to_tokens(tokens); + print_expr_field( + self, + tokens, + #[cfg(feature = "full")] + FixupContext::default(), + ); } } + fn print_expr_field( + e: &ExprField, + tokens: &mut TokenStream, + #[cfg(feature = "full")] fixup: FixupContext, + ) { + outer_attrs_to_tokens(&e.attrs, tokens); + print_subexpression( + &e.base, + Precedence::of(&e.base) < Precedence::Postfix, + tokens, + #[cfg(feature = "full")] + fixup.leftmost_subexpression_with_dot(), + ); + e.dot_token.to_tokens(tokens); + e.member.to_tokens(tokens); + } + #[cfg(feature = "full")] #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprForLoop { @@ -3189,7 +3440,7 @@ pub(crate) mod printing { self.for_token.to_tokens(tokens); self.pat.to_tokens(tokens); self.in_token.to_tokens(tokens); - wrap_bare_struct(tokens, &self.expr); + print_condition(&self.expr, tokens); self.body.brace_token.surround(tokens, |tokens| { inner_attrs_to_tokens(&self.attrs, tokens); tokens.append_all(&self.body.stmts); @@ -3216,7 +3467,7 @@ pub(crate) mod printing { let mut expr = self; loop { expr.if_token.to_tokens(tokens); - wrap_bare_struct(tokens, &expr.cond); + print_condition(&expr.cond, tokens); expr.then_branch.to_tokens(tokens); let (else_token, else_) = match &expr.else_branch { @@ -3236,7 +3487,9 @@ pub(crate) mod printing { // If this is not one of the valid expressions to exist in // an else clause, wrap it in a block. other => { - token::Brace::default().surround(tokens, |tokens| other.to_tokens(tokens)); + token::Brace::default().surround(tokens, |tokens| { + print_expr(other, tokens, FixupContext::new_stmt()); + }); break; } } @@ -3247,14 +3500,33 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprIndex { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.expr.to_tokens(tokens); - self.bracket_token.surround(tokens, |tokens| { - self.index.to_tokens(tokens); - }); + print_expr_index( + self, + tokens, + #[cfg(feature = "full")] + FixupContext::default(), + ); } } + fn print_expr_index( + e: &ExprIndex, + tokens: &mut TokenStream, + #[cfg(feature = "full")] fixup: FixupContext, + ) { + outer_attrs_to_tokens(&e.attrs, tokens); + print_subexpression( + &e.expr, + Precedence::of(&e.expr) < Precedence::Postfix, + tokens, + #[cfg(feature = "full")] + fixup.leftmost_subexpression(), + ); + e.bracket_token.surround(tokens, |tokens| { + e.index.to_tokens(tokens); + }); + } + #[cfg(feature = "full")] #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprInfer { @@ -3268,14 +3540,25 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprLet { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.let_token.to_tokens(tokens); - self.pat.to_tokens(tokens); - self.eq_token.to_tokens(tokens); - wrap_bare_struct(tokens, &self.expr); + let fixup = FixupContext::default(); + print_expr_let(self, tokens, fixup); } } + #[cfg(feature = "full")] + fn print_expr_let(e: &ExprLet, tokens: &mut TokenStream, fixup: FixupContext) { + outer_attrs_to_tokens(&e.attrs, tokens); + e.let_token.to_tokens(tokens); + e.pat.to_tokens(tokens); + e.eq_token.to_tokens(tokens); + print_subexpression( + &e.expr, + fixup.needs_group_as_let_scrutinee(&e.expr), + tokens, + FixupContext::default(), + ); + } + #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprLit { fn to_tokens(&self, tokens: &mut TokenStream) { @@ -3312,7 +3595,7 @@ pub(crate) mod printing { fn to_tokens(&self, tokens: &mut TokenStream) { outer_attrs_to_tokens(&self.attrs, tokens); self.match_token.to_tokens(tokens); - wrap_bare_struct(tokens, &self.expr); + print_condition(&self.expr, tokens); self.brace_token.surround(tokens, |tokens| { inner_attrs_to_tokens(&self.attrs, tokens); for (i, arm) in self.arms.iter().enumerate() { @@ -3334,17 +3617,36 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprMethodCall { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.receiver.to_tokens(tokens); - self.dot_token.to_tokens(tokens); - self.method.to_tokens(tokens); - self.turbofish.to_tokens(tokens); - self.paren_token.surround(tokens, |tokens| { - self.args.to_tokens(tokens); - }); + print_expr_method_call( + self, + tokens, + #[cfg(feature = "full")] + FixupContext::default(), + ); } } + fn print_expr_method_call( + e: &ExprMethodCall, + tokens: &mut TokenStream, + #[cfg(feature = "full")] fixup: FixupContext, + ) { + outer_attrs_to_tokens(&e.attrs, tokens); + print_subexpression( + &e.receiver, + Precedence::of(&e.receiver) < Precedence::Postfix, + tokens, + #[cfg(feature = "full")] + fixup.leftmost_subexpression_with_dot(), + ); + e.dot_token.to_tokens(tokens); + e.method.to_tokens(tokens); + e.turbofish.to_tokens(tokens); + e.paren_token.surround(tokens, |tokens| { + e.args.to_tokens(tokens); + }); + } + #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprParen { fn to_tokens(&self, tokens: &mut TokenStream) { @@ -3367,21 +3669,60 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprRange { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.start.to_tokens(tokens); - self.limits.to_tokens(tokens); - self.end.to_tokens(tokens); + let fixup = FixupContext::default(); + print_expr_range(self, tokens, fixup); + } + } + + #[cfg(feature = "full")] + fn print_expr_range(e: &ExprRange, tokens: &mut TokenStream, fixup: FixupContext) { + outer_attrs_to_tokens(&e.attrs, tokens); + if let Some(start) = &e.start { + print_subexpression( + start, + Precedence::of(start) <= Precedence::Range, + tokens, + fixup.leftmost_subexpression(), + ); + } + e.limits.to_tokens(tokens); + if let Some(end) = &e.end { + print_subexpression( + end, + Precedence::of_rhs(end) <= Precedence::Range, + tokens, + fixup.subsequent_subexpression(), + ); } } #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprReference { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.and_token.to_tokens(tokens); - self.mutability.to_tokens(tokens); - self.expr.to_tokens(tokens); - } + print_expr_reference( + self, + tokens, + #[cfg(feature = "full")] + FixupContext::default(), + ); + } + } + + fn print_expr_reference( + e: &ExprReference, + tokens: &mut TokenStream, + #[cfg(feature = "full")] fixup: FixupContext, + ) { + outer_attrs_to_tokens(&e.attrs, tokens); + e.and_token.to_tokens(tokens); + e.mutability.to_tokens(tokens); + print_subexpression( + &e.expr, + Precedence::of_rhs(&e.expr) < Precedence::Prefix, + tokens, + #[cfg(feature = "full")] + fixup.subsequent_subexpression(), + ); } #[cfg(feature = "full")] @@ -3401,9 +3742,17 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprReturn { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.return_token.to_tokens(tokens); - self.expr.to_tokens(tokens); + let fixup = FixupContext::default(); + print_expr_return(self, tokens, fixup); + } + } + + #[cfg(feature = "full")] + fn print_expr_return(e: &ExprReturn, tokens: &mut TokenStream, fixup: FixupContext) { + outer_attrs_to_tokens(&e.attrs, tokens); + e.return_token.to_tokens(tokens); + if let Some(expr) = &e.expr { + print_expr(expr, tokens, fixup.subsequent_subexpression()); } } @@ -3428,12 +3777,23 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprTry { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.expr.to_tokens(tokens); - self.question_token.to_tokens(tokens); + let fixup = FixupContext::default(); + print_expr_try(self, tokens, fixup); } } + #[cfg(feature = "full")] + fn print_expr_try(e: &ExprTry, tokens: &mut TokenStream, fixup: FixupContext) { + outer_attrs_to_tokens(&e.attrs, tokens); + print_subexpression( + &e.expr, + Precedence::of(&e.expr) < Precedence::Postfix, + tokens, + fixup.leftmost_subexpression_with_dot(), + ); + e.question_token.to_tokens(tokens); + } + #[cfg(feature = "full")] #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprTryBlock { @@ -3463,10 +3823,29 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprUnary { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.op.to_tokens(tokens); - self.expr.to_tokens(tokens); - } + print_expr_unary( + self, + tokens, + #[cfg(feature = "full")] + FixupContext::default(), + ); + } + } + + fn print_expr_unary( + e: &ExprUnary, + tokens: &mut TokenStream, + #[cfg(feature = "full")] fixup: FixupContext, + ) { + outer_attrs_to_tokens(&e.attrs, tokens); + e.op.to_tokens(tokens); + print_subexpression( + &e.expr, + Precedence::of_rhs(&e.expr) < Precedence::Prefix, + tokens, + #[cfg(feature = "full")] + fixup.subsequent_subexpression(), + ); } #[cfg(feature = "full")] @@ -3489,7 +3868,7 @@ pub(crate) mod printing { outer_attrs_to_tokens(&self.attrs, tokens); self.label.to_tokens(tokens); self.while_token.to_tokens(tokens); - wrap_bare_struct(tokens, &self.cond); + print_condition(&self.cond, tokens); self.body.brace_token.surround(tokens, |tokens| { inner_attrs_to_tokens(&self.attrs, tokens); tokens.append_all(&self.body.stmts); @@ -3501,9 +3880,17 @@ pub(crate) mod printing { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ToTokens for ExprYield { fn to_tokens(&self, tokens: &mut TokenStream) { - outer_attrs_to_tokens(&self.attrs, tokens); - self.yield_token.to_tokens(tokens); - self.expr.to_tokens(tokens); + let fixup = FixupContext::default(); + print_expr_yield(self, tokens, fixup); + } + } + + #[cfg(feature = "full")] + fn print_expr_yield(e: &ExprYield, tokens: &mut TokenStream, fixup: FixupContext) { + outer_attrs_to_tokens(&e.attrs, tokens); + e.yield_token.to_tokens(tokens); + if let Some(expr) = &e.expr { + print_expr(expr, tokens, fixup.subsequent_subexpression()); } } @@ -3518,7 +3905,7 @@ pub(crate) mod printing { guard.to_tokens(tokens); } self.fat_arrow_token.to_tokens(tokens); - self.body.to_tokens(tokens); + print_expr(&self.body, tokens, FixupContext::new_match_arm()); self.comma.to_tokens(tokens); } } diff --git a/src/fixup.rs b/src/fixup.rs new file mode 100644 index 0000000000..b8b867d994 --- /dev/null +++ b/src/fixup.rs @@ -0,0 +1,215 @@ +use crate::classify; +use crate::expr::Expr; +use crate::precedence::Precedence; + +#[derive(Copy, Clone, Debug)] +pub(crate) struct FixupContext { + // Print expression such that it can be parsed back as a statement + // consisting of the original expression. + // + // The effect of this is for binary operators in statement position to set + // `leftmost_subexpression_in_stmt` when printing their left-hand operand. + // + // (match x {}) - 1; // match needs parens when LHS of binary operator + // + // match x {}; // not when its own statement + // + stmt: bool, + + // This is the difference between: + // + // (match x {}) - 1; // subexpression needs parens + // + // let _ = match x {} - 1; // no parens + // + // There are 3 distinguishable contexts in which `print_expr` might be + // called with the expression `$match` as its argument, where `$match` + // represents an expression of kind `ExprKind::Match`: + // + // - stmt=false leftmost_subexpression_in_stmt=false + // + // Example: `let _ = $match - 1;` + // + // No parentheses required. + // + // - stmt=false leftmost_subexpression_in_stmt=true + // + // Example: `$match - 1;` + // + // Must parenthesize `($match)`, otherwise parsing back the output as a + // statement would terminate the statement after the closing brace of + // the match, parsing `-1;` as a separate statement. + // + // - stmt=true leftmost_subexpression_in_stmt=false + // + // Example: `$match;` + // + // No parentheses required. + leftmost_subexpression_in_stmt: bool, + + // Print expression such that it can be parsed as a match arm. + // + // This is almost equivalent to `stmt`, but the grammar diverges a tiny bit + // between statements and match arms when it comes to braced macro calls. + // Macro calls with brace delimiter terminate a statement without a + // semicolon, but do not terminate a match-arm without comma. + // + // m! {} - 1; // two statements: a macro call followed by -1 literal + // + // match () { + // _ => m! {} - 1, // binary subtraction operator + // } + // + match_arm: bool, + + // This is almost equivalent to `leftmost_subexpression_in_stmt`, other than + // for braced macro calls. + // + // If we have `m! {} - 1` as an expression, the leftmost subexpression + // `m! {}` will need to be parenthesized in the statement case but not the + // match-arm case. + // + // (m! {}) - 1; // subexpression needs parens + // + // match () { + // _ => m! {} - 1, // no parens + // } + // + leftmost_subexpression_in_match_arm: bool, + + // This is the difference between: + // + // if let _ = (Struct {}) {} // needs parens + // + // match () { + // () if let _ = Struct {} => {} // no parens + // } + // + parenthesize_exterior_struct_lit: bool, +} + +/// The default amount of fixing is minimal fixing. Fixups should be turned on +/// in a targeted fashion where needed. +impl Default for FixupContext { + fn default() -> Self { + FixupContext { + stmt: false, + leftmost_subexpression_in_stmt: false, + match_arm: false, + leftmost_subexpression_in_match_arm: false, + parenthesize_exterior_struct_lit: false, + } + } +} + +impl FixupContext { + /// Create the initial fixup for printing an expression in statement + /// position. + pub fn new_stmt() -> Self { + FixupContext { + stmt: true, + ..FixupContext::default() + } + } + + /// Create the initial fixup for printing an expression as the right-hand + /// side of a match arm. + pub fn new_match_arm() -> Self { + FixupContext { + match_arm: true, + ..FixupContext::default() + } + } + + /// Create the initial fixup for printing an expression as the "condition" + /// of an `if` or `while`. There are a few other positions which are + /// grammatically equivalent and also use this, such as the iterator + /// expression in `for` and the scrutinee in `match`. + pub fn new_condition() -> Self { + FixupContext { + parenthesize_exterior_struct_lit: true, + ..FixupContext::default() + } + } + + /// Transform this fixup into the one that should apply when printing the + /// leftmost subexpression of the current expression. + /// + /// The leftmost subexpression is any subexpression that has the same first + /// token as the current expression, but has a different last token. + /// + /// For example in `$a + $b` and `$a.method()`, the subexpression `$a` is a + /// leftmost subexpression. + /// + /// Not every expression has a leftmost subexpression. For example neither + /// `-$a` nor `[$a]` have one. + pub fn leftmost_subexpression(self) -> Self { + FixupContext { + stmt: false, + leftmost_subexpression_in_stmt: self.stmt || self.leftmost_subexpression_in_stmt, + match_arm: false, + leftmost_subexpression_in_match_arm: self.match_arm + || self.leftmost_subexpression_in_match_arm, + ..self + } + } + + /// Transform this fixup into the one that should apply when printing a + /// leftmost subexpression followed by a `.` or `?` token, which confer + /// different statement boundary rules compared to other leftmost + /// subexpressions. + pub fn leftmost_subexpression_with_dot(self) -> Self { + FixupContext { + stmt: self.stmt || self.leftmost_subexpression_in_stmt, + leftmost_subexpression_in_stmt: false, + match_arm: self.match_arm || self.leftmost_subexpression_in_match_arm, + leftmost_subexpression_in_match_arm: false, + ..self + } + } + + /// Transform this fixup into the one that should apply when printing any + /// subexpression that is neither a leftmost subexpression nor surrounded in + /// delimiters. + /// + /// This is for any subexpression that has a different first token than the + /// current expression, and is not surrounded by a paren/bracket/brace. For + /// example the `$b` in `$a + $b` and `-$b`, but not the one in `[$b]` or + /// `$a.f($b)`. + pub fn subsequent_subexpression(self) -> Self { + FixupContext { + stmt: false, + leftmost_subexpression_in_stmt: false, + match_arm: false, + leftmost_subexpression_in_match_arm: false, + ..self + } + } + + /// Determine whether parentheses are needed around the given expression to + /// head off an unintended statement boundary. + /// + /// The documentation on `FixupContext::leftmost_subexpression_in_stmt` has + /// examples. + pub fn would_cause_statement_boundary(self, expr: &Expr) -> bool { + (self.leftmost_subexpression_in_stmt && !classify::requires_semi_to_be_stmt(expr)) + || (self.leftmost_subexpression_in_match_arm + && !classify::requires_comma_to_be_match_arm(expr)) + } + + /// Determine whether parentheses are needed around the given `let` + /// scrutinee. + /// + /// In `if let _ = $e {}`, some examples of `$e` that would need parentheses + /// are: + /// + /// - `Struct {}.f()`, because otherwise the `{` would be misinterpreted + /// as the opening of the if's then-block. + /// + /// - `true && false`, because otherwise this would be misinterpreted as a + /// "let chain". + pub fn needs_group_as_let_scrutinee(self, expr: &Expr) -> bool { + self.parenthesize_exterior_struct_lit && classify::confusable_with_adjacent_block(expr) + || Precedence::of_rhs(expr) <= Precedence::And + } +} diff --git a/src/generics.rs b/src/generics.rs index 4440ce8b85..0b677ff655 100644 --- a/src/generics.rs +++ b/src/generics.rs @@ -991,7 +991,11 @@ pub(crate) mod parsing { #[cfg(feature = "printing")] pub(crate) mod printing { use crate::attr::FilterAttrs; + #[cfg(feature = "full")] + use crate::expr; use crate::expr::Expr; + #[cfg(feature = "full")] + use crate::fixup::FixupContext; use crate::generics::{ BoundLifetimes, ConstParam, GenericParam, Generics, ImplGenerics, LifetimeParam, PredicateLifetime, PredicateType, TraitBound, TraitBoundModifier, Turbofish, TypeGenerics, @@ -1271,6 +1275,10 @@ pub(crate) mod printing { // ERROR CORRECTION: Add braces to make sure that the // generated code is valid. _ => token::Brace::default().surround(tokens, |tokens| { + #[cfg(feature = "full")] + expr::printing::print_expr(expr, tokens, FixupContext::new_stmt()); + + #[cfg(not(feature = "full"))] expr.to_tokens(tokens); }), } diff --git a/src/lib.rs b/src/lib.rs index b05a0ce847..c73bd0623b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -328,8 +328,10 @@ mod bigint; #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] pub mod buffer; -#[cfg(any(feature = "parsing", feature = "printing"))] -#[cfg(feature = "full")] +#[cfg(any( + all(feature = "parsing", feature = "full"), + all(feature = "printing", any(feature = "full", feature = "derive")), +))] mod classify; mod custom_keyword; @@ -383,6 +385,9 @@ mod file; #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] pub use crate::file::File; +#[cfg(all(feature = "full", feature = "printing"))] +mod fixup; + #[cfg(any(feature = "full", feature = "derive"))] mod generics; #[cfg(any(feature = "full", feature = "derive"))] @@ -478,7 +483,10 @@ pub use crate::path::{ ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf, }; -#[cfg(all(any(feature = "full", feature = "derive"), feature = "parsing"))] +#[cfg(all( + any(feature = "full", feature = "derive"), + any(feature = "parsing", feature = "printing") +))] mod precedence; #[cfg(all(any(feature = "full", feature = "derive"), feature = "printing"))] diff --git a/src/precedence.rs b/src/precedence.rs index 25d90b4d7c..450958ad34 100644 --- a/src/precedence.rs +++ b/src/precedence.rs @@ -1,4 +1,8 @@ +#[cfg(feature = "printing")] +use crate::expr::Expr; use crate::op::BinOp; +#[cfg(all(feature = "printing", feature = "full"))] +use crate::ty::ReturnType; use std::cmp::Ordering; // Reference: https://doc.rust-lang.org/reference/expressions.html#expression-precedence @@ -29,6 +33,15 @@ pub(crate) enum Precedence { Term, // as Cast, + // unary - * ! & &mut + #[cfg(feature = "printing")] + Prefix, + // function calls, array indexing, field expressions, method calls, ? + #[cfg(feature = "printing")] + Postfix, + // paths, loops + #[cfg(feature = "printing")] + Unambiguous, } impl Precedence { @@ -42,12 +55,14 @@ impl Precedence { BinOp::BitAnd(_) => Precedence::BitAnd, BinOp::BitOr(_) => Precedence::BitOr, BinOp::Shl(_) | BinOp::Shr(_) => Precedence::Shift, + BinOp::Eq(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Ne(_) | BinOp::Ge(_) | BinOp::Gt(_) => Precedence::Compare, + BinOp::AddAssign(_) | BinOp::SubAssign(_) | BinOp::MulAssign(_) @@ -60,6 +75,69 @@ impl Precedence { | BinOp::ShrAssign(_) => Precedence::Assign, } } + + #[cfg(feature = "printing")] + pub(crate) fn of(e: &Expr) -> Self { + match e { + #[cfg(feature = "full")] + Expr::Closure(e) => match e.output { + ReturnType::Default => Precedence::Any, + ReturnType::Type(..) => Precedence::Unambiguous, + }, + + Expr::Break(_) | Expr::Return(_) | Expr::Yield(_) => Precedence::Any, + Expr::Assign(_) => Precedence::Assign, + Expr::Range(_) => Precedence::Range, + Expr::Binary(e) => Precedence::of_binop(&e.op), + Expr::Cast(_) => Precedence::Cast, + Expr::Let(_) | Expr::Reference(_) | Expr::Unary(_) => Precedence::Prefix, + + Expr::Await(_) + | Expr::Call(_) + | Expr::MethodCall(_) + | Expr::Field(_) + | Expr::Index(_) + | Expr::Try(_) => Precedence::Postfix, + + Expr::Array(_) + | Expr::Async(_) + | Expr::Block(_) + | Expr::Const(_) + | Expr::Continue(_) + | Expr::ForLoop(_) + | Expr::Group(_) + | Expr::If(_) + | Expr::Infer(_) + | Expr::Lit(_) + | Expr::Loop(_) + | Expr::Macro(_) + | Expr::Match(_) + | Expr::Paren(_) + | Expr::Path(_) + | Expr::Repeat(_) + | Expr::Struct(_) + | Expr::TryBlock(_) + | Expr::Tuple(_) + | Expr::Unsafe(_) + | Expr::Verbatim(_) + | Expr::While(_) => Precedence::Unambiguous, + + #[cfg(not(feature = "full"))] + Expr::Closure(_) => unreachable!(), + } + } + + #[cfg(feature = "printing")] + pub(crate) fn of_rhs(e: &Expr) -> Self { + match e { + Expr::Break(_) | Expr::Closure(_) | Expr::Return(_) | Expr::Yield(_) => { + Precedence::Prefix + } + #[cfg(feature = "full")] + Expr::Range(e) if e.start.is_none() => Precedence::Prefix, + _ => Precedence::of(e), + } + } } impl Copy for Precedence {} diff --git a/src/stmt.rs b/src/stmt.rs index fa6cf022d4..e2e7b5e059 100644 --- a/src/stmt.rs +++ b/src/stmt.rs @@ -410,9 +410,10 @@ pub(crate) mod parsing { } #[cfg(feature = "printing")] -mod printing { +pub(crate) mod printing { use crate::classify; use crate::expr::{self, Expr}; + use crate::fixup::FixupContext; use crate::stmt::{Block, Local, Stmt, StmtMacro}; use crate::token; use proc_macro2::TokenStream; @@ -434,7 +435,7 @@ mod printing { Stmt::Local(local) => local.to_tokens(tokens), Stmt::Item(item) => item.to_tokens(tokens), Stmt::Expr(expr, semi) => { - expr.to_tokens(tokens); + expr::printing::print_expr(expr, tokens, FixupContext::new_stmt()); semi.to_tokens(tokens); } Stmt::Macro(mac) => mac.to_tokens(tokens), @@ -459,8 +460,9 @@ mod printing { else_token.to_tokens(tokens); match &**diverge { Expr::Block(diverge) => diverge.to_tokens(tokens), - _ => token::Brace::default() - .surround(tokens, |tokens| diverge.to_tokens(tokens)), + _ => token::Brace::default().surround(tokens, |tokens| { + expr::printing::print_expr(diverge, tokens, FixupContext::new_stmt()); + }), } } }