Rollup merge of #118726 - dtolnay:matchguardlet, r=compiler-errors
Do not parenthesize exterior struct lit inside match guards Before this PR, the AST pretty-printer injects parentheses around expressions any time parens _could_ be needed depending on what else is in the code that surrounds that expression. But the pretty-printer did not pass around enough context to understand whether parentheses really _are_ needed on any particular expression. As a consequence, there are false positives where unneeded parentheses are being inserted. Example: ```rust #![feature(if_let_guard)] macro_rules! pp { ($e:expr) => { stringify!($e) }; } fn main() { println!("{}", pp!(match () { () if let _ = Struct {} => {} })); } ``` **Before:** ```console match () { () if let _ = (Struct {}) => {} } ``` **After:** ```console match () { () if let _ = Struct {} => {} } ``` This PR introduces a bit of state that is passed across various expression printing methods to help understand accurately whether particular situations require parentheses injected by the pretty printer, and it fixes one such false positive involving match guards as shown above. There are other parenthesization false positive cases not fixed by this PR. I intend to address these in follow-up PRs. For example here is one: the expression `{ let _ = match x {} + 1; }` is pretty-printed as `{ let _ = (match x {}) + 1; }` despite there being no reason for parentheses to appear there.
This commit is contained in:
commit
3a0562b93c
4 changed files with 174 additions and 77 deletions
|
@ -7,6 +7,7 @@ mod item;
|
||||||
|
|
||||||
use crate::pp::Breaks::{Consistent, Inconsistent};
|
use crate::pp::Breaks::{Consistent, Inconsistent};
|
||||||
use crate::pp::{self, Breaks};
|
use crate::pp::{self, Breaks};
|
||||||
|
use crate::pprust::state::expr::FixupContext;
|
||||||
use rustc_ast::attr::AttrIdGenerator;
|
use rustc_ast::attr::AttrIdGenerator;
|
||||||
use rustc_ast::ptr::P;
|
use rustc_ast::ptr::P;
|
||||||
use rustc_ast::token::{self, BinOpToken, CommentKind, Delimiter, Nonterminal, Token, TokenKind};
|
use rustc_ast::token::{self, BinOpToken, CommentKind, Delimiter, Nonterminal, Token, TokenKind};
|
||||||
|
@ -811,7 +812,7 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expr_to_string(&self, e: &ast::Expr) -> String {
|
fn expr_to_string(&self, e: &ast::Expr) -> String {
|
||||||
Self::to_string(|s| s.print_expr(e))
|
Self::to_string(|s| s.print_expr(e, FixupContext::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn meta_item_lit_to_string(&self, lit: &ast::MetaItemLit) -> String {
|
fn meta_item_lit_to_string(&self, lit: &ast::MetaItemLit) -> String {
|
||||||
|
@ -916,7 +917,7 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commasep_exprs(&mut self, b: Breaks, exprs: &[P<ast::Expr>]) {
|
fn commasep_exprs(&mut self, b: Breaks, exprs: &[P<ast::Expr>]) {
|
||||||
self.commasep_cmnt(b, exprs, |s, e| s.print_expr(e), |e| e.span)
|
self.commasep_cmnt(b, exprs, |s, e| s.print_expr(e, FixupContext::default()), |e| e.span)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_opt_lifetime(&mut self, lifetime: &Option<ast::Lifetime>) {
|
pub fn print_opt_lifetime(&mut self, lifetime: &Option<ast::Lifetime>) {
|
||||||
|
@ -953,7 +954,7 @@ impl<'a> State<'a> {
|
||||||
match generic_arg {
|
match generic_arg {
|
||||||
GenericArg::Lifetime(lt) => self.print_lifetime(*lt),
|
GenericArg::Lifetime(lt) => self.print_lifetime(*lt),
|
||||||
GenericArg::Type(ty) => self.print_type(ty),
|
GenericArg::Type(ty) => self.print_type(ty),
|
||||||
GenericArg::Const(ct) => self.print_expr(&ct.value),
|
GenericArg::Const(ct) => self.print_expr(&ct.value, FixupContext::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1020,12 +1021,12 @@ impl<'a> State<'a> {
|
||||||
self.word("[");
|
self.word("[");
|
||||||
self.print_type(ty);
|
self.print_type(ty);
|
||||||
self.word("; ");
|
self.word("; ");
|
||||||
self.print_expr(&length.value);
|
self.print_expr(&length.value, FixupContext::default());
|
||||||
self.word("]");
|
self.word("]");
|
||||||
}
|
}
|
||||||
ast::TyKind::Typeof(e) => {
|
ast::TyKind::Typeof(e) => {
|
||||||
self.word("typeof(");
|
self.word("typeof(");
|
||||||
self.print_expr(&e.value);
|
self.print_expr(&e.value, FixupContext::default());
|
||||||
self.word(")");
|
self.word(")");
|
||||||
}
|
}
|
||||||
ast::TyKind::Infer => {
|
ast::TyKind::Infer => {
|
||||||
|
@ -1081,7 +1082,7 @@ impl<'a> State<'a> {
|
||||||
if let Some((init, els)) = loc.kind.init_else_opt() {
|
if let Some((init, els)) = loc.kind.init_else_opt() {
|
||||||
self.nbsp();
|
self.nbsp();
|
||||||
self.word_space("=");
|
self.word_space("=");
|
||||||
self.print_expr(init);
|
self.print_expr(init, FixupContext::default());
|
||||||
if let Some(els) = els {
|
if let Some(els) = els {
|
||||||
self.cbox(INDENT_UNIT);
|
self.cbox(INDENT_UNIT);
|
||||||
self.ibox(INDENT_UNIT);
|
self.ibox(INDENT_UNIT);
|
||||||
|
@ -1095,14 +1096,14 @@ impl<'a> State<'a> {
|
||||||
ast::StmtKind::Item(item) => self.print_item(item),
|
ast::StmtKind::Item(item) => self.print_item(item),
|
||||||
ast::StmtKind::Expr(expr) => {
|
ast::StmtKind::Expr(expr) => {
|
||||||
self.space_if_not_bol();
|
self.space_if_not_bol();
|
||||||
self.print_expr_outer_attr_style(expr, false);
|
self.print_expr_outer_attr_style(expr, false, FixupContext::default());
|
||||||
if classify::expr_requires_semi_to_be_stmt(expr) {
|
if classify::expr_requires_semi_to_be_stmt(expr) {
|
||||||
self.word(";");
|
self.word(";");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::StmtKind::Semi(expr) => {
|
ast::StmtKind::Semi(expr) => {
|
||||||
self.space_if_not_bol();
|
self.space_if_not_bol();
|
||||||
self.print_expr_outer_attr_style(expr, false);
|
self.print_expr_outer_attr_style(expr, false, FixupContext::default());
|
||||||
self.word(";");
|
self.word(";");
|
||||||
}
|
}
|
||||||
ast::StmtKind::Empty => {
|
ast::StmtKind::Empty => {
|
||||||
|
@ -1154,7 +1155,7 @@ impl<'a> State<'a> {
|
||||||
ast::StmtKind::Expr(expr) if i == blk.stmts.len() - 1 => {
|
ast::StmtKind::Expr(expr) if i == blk.stmts.len() - 1 => {
|
||||||
self.maybe_print_comment(st.span.lo());
|
self.maybe_print_comment(st.span.lo());
|
||||||
self.space_if_not_bol();
|
self.space_if_not_bol();
|
||||||
self.print_expr_outer_attr_style(expr, false);
|
self.print_expr_outer_attr_style(expr, false, FixupContext::default());
|
||||||
self.maybe_print_trailing_comment(expr.span, Some(blk.span.hi()));
|
self.maybe_print_trailing_comment(expr.span, Some(blk.span.hi()));
|
||||||
}
|
}
|
||||||
_ => self.print_stmt(st),
|
_ => self.print_stmt(st),
|
||||||
|
@ -1167,13 +1168,41 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print a `let pat = expr` expression.
|
/// Print a `let pat = expr` expression.
|
||||||
fn print_let(&mut self, pat: &ast::Pat, expr: &ast::Expr) {
|
///
|
||||||
|
/// Parentheses are inserted surrounding `expr` if a round-trip through the
|
||||||
|
/// parser would otherwise work out the wrong way in a condition position.
|
||||||
|
///
|
||||||
|
/// For example each of the following would mean the wrong thing without
|
||||||
|
/// parentheses.
|
||||||
|
///
|
||||||
|
/// ```ignore (illustrative)
|
||||||
|
/// if let _ = (Struct {}) {}
|
||||||
|
///
|
||||||
|
/// if let _ = (true && false) {}
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In a match guard, the second case still requires parens, but the first
|
||||||
|
/// case no longer does because anything until `=>` is considered part of
|
||||||
|
/// the match guard expression. Parsing of the expression is not terminated
|
||||||
|
/// by `{` in that position.
|
||||||
|
///
|
||||||
|
/// ```ignore (illustrative)
|
||||||
|
/// match () {
|
||||||
|
/// () if let _ = Struct {} => {}
|
||||||
|
/// () if let _ = (true && false) => {}
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn print_let(&mut self, pat: &ast::Pat, expr: &ast::Expr, fixup: FixupContext) {
|
||||||
self.word("let ");
|
self.word("let ");
|
||||||
self.print_pat(pat);
|
self.print_pat(pat);
|
||||||
self.space();
|
self.space();
|
||||||
self.word_space("=");
|
self.word_space("=");
|
||||||
let npals = || parser::needs_par_as_let_scrutinee(expr.precedence().order());
|
self.print_expr_cond_paren(
|
||||||
self.print_expr_cond_paren(expr, Self::cond_needs_par(expr) || npals())
|
expr,
|
||||||
|
fixup.parenthesize_exterior_struct_lit && parser::contains_exterior_struct_lit(expr)
|
||||||
|
|| parser::needs_par_as_let_scrutinee(expr.precedence().order()),
|
||||||
|
FixupContext::default(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_mac(&mut self, m: &ast::MacCall) {
|
fn print_mac(&mut self, m: &ast::MacCall) {
|
||||||
|
@ -1220,7 +1249,7 @@ impl<'a> State<'a> {
|
||||||
print_reg_or_class(s, reg);
|
print_reg_or_class(s, reg);
|
||||||
s.pclose();
|
s.pclose();
|
||||||
s.space();
|
s.space();
|
||||||
s.print_expr(expr);
|
s.print_expr(expr, FixupContext::default());
|
||||||
}
|
}
|
||||||
InlineAsmOperand::Out { reg, late, expr } => {
|
InlineAsmOperand::Out { reg, late, expr } => {
|
||||||
s.word(if *late { "lateout" } else { "out" });
|
s.word(if *late { "lateout" } else { "out" });
|
||||||
|
@ -1229,7 +1258,7 @@ impl<'a> State<'a> {
|
||||||
s.pclose();
|
s.pclose();
|
||||||
s.space();
|
s.space();
|
||||||
match expr {
|
match expr {
|
||||||
Some(expr) => s.print_expr(expr),
|
Some(expr) => s.print_expr(expr, FixupContext::default()),
|
||||||
None => s.word("_"),
|
None => s.word("_"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1239,7 +1268,7 @@ impl<'a> State<'a> {
|
||||||
print_reg_or_class(s, reg);
|
print_reg_or_class(s, reg);
|
||||||
s.pclose();
|
s.pclose();
|
||||||
s.space();
|
s.space();
|
||||||
s.print_expr(expr);
|
s.print_expr(expr, FixupContext::default());
|
||||||
}
|
}
|
||||||
InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => {
|
InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => {
|
||||||
s.word(if *late { "inlateout" } else { "inout" });
|
s.word(if *late { "inlateout" } else { "inout" });
|
||||||
|
@ -1247,18 +1276,18 @@ impl<'a> State<'a> {
|
||||||
print_reg_or_class(s, reg);
|
print_reg_or_class(s, reg);
|
||||||
s.pclose();
|
s.pclose();
|
||||||
s.space();
|
s.space();
|
||||||
s.print_expr(in_expr);
|
s.print_expr(in_expr, FixupContext::default());
|
||||||
s.space();
|
s.space();
|
||||||
s.word_space("=>");
|
s.word_space("=>");
|
||||||
match out_expr {
|
match out_expr {
|
||||||
Some(out_expr) => s.print_expr(out_expr),
|
Some(out_expr) => s.print_expr(out_expr, FixupContext::default()),
|
||||||
None => s.word("_"),
|
None => s.word("_"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InlineAsmOperand::Const { anon_const } => {
|
InlineAsmOperand::Const { anon_const } => {
|
||||||
s.word("const");
|
s.word("const");
|
||||||
s.space();
|
s.space();
|
||||||
s.print_expr(&anon_const.value);
|
s.print_expr(&anon_const.value, FixupContext::default());
|
||||||
}
|
}
|
||||||
InlineAsmOperand::Sym { sym } => {
|
InlineAsmOperand::Sym { sym } => {
|
||||||
s.word("sym");
|
s.word("sym");
|
||||||
|
@ -1452,10 +1481,10 @@ impl<'a> State<'a> {
|
||||||
self.print_pat(inner);
|
self.print_pat(inner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PatKind::Lit(e) => self.print_expr(e),
|
PatKind::Lit(e) => self.print_expr(e, FixupContext::default()),
|
||||||
PatKind::Range(begin, end, Spanned { node: end_kind, .. }) => {
|
PatKind::Range(begin, end, Spanned { node: end_kind, .. }) => {
|
||||||
if let Some(e) = begin {
|
if let Some(e) = begin {
|
||||||
self.print_expr(e);
|
self.print_expr(e, FixupContext::default());
|
||||||
}
|
}
|
||||||
match end_kind {
|
match end_kind {
|
||||||
RangeEnd::Included(RangeSyntax::DotDotDot) => self.word("..."),
|
RangeEnd::Included(RangeSyntax::DotDotDot) => self.word("..."),
|
||||||
|
@ -1463,7 +1492,7 @@ impl<'a> State<'a> {
|
||||||
RangeEnd::Excluded => self.word(".."),
|
RangeEnd::Excluded => self.word(".."),
|
||||||
}
|
}
|
||||||
if let Some(e) = end {
|
if let Some(e) = end {
|
||||||
self.print_expr(e);
|
self.print_expr(e, FixupContext::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PatKind::Slice(elts) => {
|
PatKind::Slice(elts) => {
|
||||||
|
@ -1617,7 +1646,7 @@ impl<'a> State<'a> {
|
||||||
if let Some(default) = default {
|
if let Some(default) = default {
|
||||||
s.space();
|
s.space();
|
||||||
s.word_space("=");
|
s.word_space("=");
|
||||||
s.print_expr(&default.value);
|
s.print_expr(&default.value, FixupContext::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,19 @@ use rustc_ast::{
|
||||||
};
|
};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub(crate) struct FixupContext {
|
||||||
|
pub parenthesize_exterior_struct_lit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default amount of fixing is minimal fixing. Fixups should be turned on
|
||||||
|
/// in a targetted fashion where needed.
|
||||||
|
impl Default for FixupContext {
|
||||||
|
fn default() -> Self {
|
||||||
|
FixupContext { parenthesize_exterior_struct_lit: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> State<'a> {
|
impl<'a> State<'a> {
|
||||||
fn print_else(&mut self, els: Option<&ast::Expr>) {
|
fn print_else(&mut self, els: Option<&ast::Expr>) {
|
||||||
if let Some(_else) = els {
|
if let Some(_else) = els {
|
||||||
|
@ -55,21 +68,22 @@ impl<'a> State<'a> {
|
||||||
self.pclose()
|
self.pclose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_expr_maybe_paren(&mut self, expr: &ast::Expr, prec: i8) {
|
fn print_expr_maybe_paren(&mut self, expr: &ast::Expr, prec: i8, fixup: FixupContext) {
|
||||||
self.print_expr_cond_paren(expr, expr.precedence().order() < prec)
|
self.print_expr_cond_paren(expr, expr.precedence().order() < prec, fixup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints an expr using syntax that's acceptable in a condition position, such as the `cond` in
|
/// Prints an expr using syntax that's acceptable in a condition position, such as the `cond` in
|
||||||
/// `if cond { ... }`.
|
/// `if cond { ... }`.
|
||||||
fn print_expr_as_cond(&mut self, expr: &ast::Expr) {
|
fn print_expr_as_cond(&mut self, expr: &ast::Expr) {
|
||||||
self.print_expr_cond_paren(expr, Self::cond_needs_par(expr))
|
let fixup = FixupContext { parenthesize_exterior_struct_lit: true };
|
||||||
|
self.print_expr_cond_paren(expr, Self::cond_needs_par(expr), fixup)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does `expr` need parentheses when printed in a condition position?
|
/// Does `expr` need parentheses when printed in a condition position?
|
||||||
///
|
///
|
||||||
/// These cases need parens due to the parse error observed in #26461: `if return {}`
|
/// These cases need parens due to the parse error observed in #26461: `if return {}`
|
||||||
/// parses as the erroneous construct `if (return {})`, not `if (return) {}`.
|
/// parses as the erroneous construct `if (return {})`, not `if (return) {}`.
|
||||||
pub(super) fn cond_needs_par(expr: &ast::Expr) -> bool {
|
fn cond_needs_par(expr: &ast::Expr) -> bool {
|
||||||
match expr.kind {
|
match expr.kind {
|
||||||
ast::ExprKind::Break(..)
|
ast::ExprKind::Break(..)
|
||||||
| ast::ExprKind::Closure(..)
|
| ast::ExprKind::Closure(..)
|
||||||
|
@ -80,11 +94,32 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints `expr` or `(expr)` when `needs_par` holds.
|
/// Prints `expr` or `(expr)` when `needs_par` holds.
|
||||||
pub(super) fn print_expr_cond_paren(&mut self, expr: &ast::Expr, needs_par: bool) {
|
pub(super) fn print_expr_cond_paren(
|
||||||
|
&mut self,
|
||||||
|
expr: &ast::Expr,
|
||||||
|
needs_par: bool,
|
||||||
|
fixup: FixupContext,
|
||||||
|
) {
|
||||||
if needs_par {
|
if needs_par {
|
||||||
self.popen();
|
self.popen();
|
||||||
}
|
}
|
||||||
self.print_expr(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 {}) {}
|
||||||
|
//
|
||||||
|
let fixup = FixupContext {
|
||||||
|
parenthesize_exterior_struct_lit: fixup.parenthesize_exterior_struct_lit && !needs_par,
|
||||||
|
};
|
||||||
|
self.print_expr(expr, fixup);
|
||||||
|
|
||||||
if needs_par {
|
if needs_par {
|
||||||
self.pclose();
|
self.pclose();
|
||||||
}
|
}
|
||||||
|
@ -111,7 +146,7 @@ impl<'a> State<'a> {
|
||||||
self.ibox(0);
|
self.ibox(0);
|
||||||
self.print_block_with_attrs(block, attrs);
|
self.print_block_with_attrs(block, attrs);
|
||||||
} else {
|
} else {
|
||||||
self.print_expr(&expr.value);
|
self.print_expr(&expr.value, FixupContext::default());
|
||||||
}
|
}
|
||||||
self.end();
|
self.end();
|
||||||
}
|
}
|
||||||
|
@ -119,9 +154,9 @@ impl<'a> State<'a> {
|
||||||
fn print_expr_repeat(&mut self, element: &ast::Expr, count: &ast::AnonConst) {
|
fn print_expr_repeat(&mut self, element: &ast::Expr, count: &ast::AnonConst) {
|
||||||
self.ibox(INDENT_UNIT);
|
self.ibox(INDENT_UNIT);
|
||||||
self.word("[");
|
self.word("[");
|
||||||
self.print_expr(element);
|
self.print_expr(element, FixupContext::default());
|
||||||
self.word_space(";");
|
self.word_space(";");
|
||||||
self.print_expr(&count.value);
|
self.print_expr(&count.value, FixupContext::default());
|
||||||
self.word("]");
|
self.word("]");
|
||||||
self.end();
|
self.end();
|
||||||
}
|
}
|
||||||
|
@ -161,7 +196,7 @@ impl<'a> State<'a> {
|
||||||
self.print_ident(field.ident);
|
self.print_ident(field.ident);
|
||||||
self.word_nbsp(":");
|
self.word_nbsp(":");
|
||||||
}
|
}
|
||||||
self.print_expr(&field.expr);
|
self.print_expr(&field.expr, FixupContext::default());
|
||||||
if !is_last || has_rest {
|
if !is_last || has_rest {
|
||||||
self.word_space(",");
|
self.word_space(",");
|
||||||
} else {
|
} else {
|
||||||
|
@ -174,7 +209,7 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
self.word("..");
|
self.word("..");
|
||||||
if let ast::StructRest::Base(expr) = rest {
|
if let ast::StructRest::Base(expr) = rest {
|
||||||
self.print_expr(expr);
|
self.print_expr(expr, FixupContext::default());
|
||||||
}
|
}
|
||||||
self.space();
|
self.space();
|
||||||
}
|
}
|
||||||
|
@ -192,13 +227,13 @@ impl<'a> State<'a> {
|
||||||
self.pclose()
|
self.pclose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_expr_call(&mut self, func: &ast::Expr, args: &[P<ast::Expr>]) {
|
fn print_expr_call(&mut self, func: &ast::Expr, args: &[P<ast::Expr>], fixup: FixupContext) {
|
||||||
let prec = match func.kind {
|
let prec = match func.kind {
|
||||||
ast::ExprKind::Field(..) => parser::PREC_FORCE_PAREN,
|
ast::ExprKind::Field(..) => parser::PREC_FORCE_PAREN,
|
||||||
_ => parser::PREC_POSTFIX,
|
_ => parser::PREC_POSTFIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.print_expr_maybe_paren(func, prec);
|
self.print_expr_maybe_paren(func, prec, fixup);
|
||||||
self.print_call_post(args)
|
self.print_call_post(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,8 +242,9 @@ impl<'a> State<'a> {
|
||||||
segment: &ast::PathSegment,
|
segment: &ast::PathSegment,
|
||||||
receiver: &ast::Expr,
|
receiver: &ast::Expr,
|
||||||
base_args: &[P<ast::Expr>],
|
base_args: &[P<ast::Expr>],
|
||||||
|
fixup: FixupContext,
|
||||||
) {
|
) {
|
||||||
self.print_expr_maybe_paren(receiver, parser::PREC_POSTFIX);
|
self.print_expr_maybe_paren(receiver, parser::PREC_POSTFIX, fixup);
|
||||||
self.word(".");
|
self.word(".");
|
||||||
self.print_ident(segment.ident);
|
self.print_ident(segment.ident);
|
||||||
if let Some(args) = &segment.args {
|
if let Some(args) = &segment.args {
|
||||||
|
@ -217,7 +253,13 @@ impl<'a> State<'a> {
|
||||||
self.print_call_post(base_args)
|
self.print_call_post(base_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_expr_binary(&mut self, op: ast::BinOp, lhs: &ast::Expr, rhs: &ast::Expr) {
|
fn print_expr_binary(
|
||||||
|
&mut self,
|
||||||
|
op: ast::BinOp,
|
||||||
|
lhs: &ast::Expr,
|
||||||
|
rhs: &ast::Expr,
|
||||||
|
fixup: FixupContext,
|
||||||
|
) {
|
||||||
let assoc_op = AssocOp::from_ast_binop(op.node);
|
let assoc_op = AssocOp::from_ast_binop(op.node);
|
||||||
let prec = assoc_op.precedence() as i8;
|
let prec = assoc_op.precedence() as i8;
|
||||||
let fixity = assoc_op.fixity();
|
let fixity = assoc_op.fixity();
|
||||||
|
@ -253,15 +295,15 @@ impl<'a> State<'a> {
|
||||||
_ => left_prec,
|
_ => left_prec,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.print_expr_maybe_paren(lhs, left_prec);
|
self.print_expr_maybe_paren(lhs, left_prec, fixup);
|
||||||
self.space();
|
self.space();
|
||||||
self.word_space(op.node.as_str());
|
self.word_space(op.node.as_str());
|
||||||
self.print_expr_maybe_paren(rhs, right_prec)
|
self.print_expr_maybe_paren(rhs, right_prec, fixup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_expr_unary(&mut self, op: ast::UnOp, expr: &ast::Expr) {
|
fn print_expr_unary(&mut self, op: ast::UnOp, expr: &ast::Expr, fixup: FixupContext) {
|
||||||
self.word(op.as_str());
|
self.word(op.as_str());
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_PREFIX)
|
self.print_expr_maybe_paren(expr, parser::PREC_PREFIX, fixup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_expr_addr_of(
|
fn print_expr_addr_of(
|
||||||
|
@ -269,6 +311,7 @@ impl<'a> State<'a> {
|
||||||
kind: ast::BorrowKind,
|
kind: ast::BorrowKind,
|
||||||
mutability: ast::Mutability,
|
mutability: ast::Mutability,
|
||||||
expr: &ast::Expr,
|
expr: &ast::Expr,
|
||||||
|
fixup: FixupContext,
|
||||||
) {
|
) {
|
||||||
self.word("&");
|
self.word("&");
|
||||||
match kind {
|
match kind {
|
||||||
|
@ -278,14 +321,19 @@ impl<'a> State<'a> {
|
||||||
self.print_mutability(mutability, true);
|
self.print_mutability(mutability, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_PREFIX)
|
self.print_expr_maybe_paren(expr, parser::PREC_PREFIX, fixup)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn print_expr(&mut self, expr: &ast::Expr) {
|
pub(super) fn print_expr(&mut self, expr: &ast::Expr, fixup: FixupContext) {
|
||||||
self.print_expr_outer_attr_style(expr, true)
|
self.print_expr_outer_attr_style(expr, true, fixup)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn print_expr_outer_attr_style(&mut self, expr: &ast::Expr, is_inline: bool) {
|
pub(super) fn print_expr_outer_attr_style(
|
||||||
|
&mut self,
|
||||||
|
expr: &ast::Expr,
|
||||||
|
is_inline: bool,
|
||||||
|
fixup: FixupContext,
|
||||||
|
) {
|
||||||
self.maybe_print_comment(expr.span.lo());
|
self.maybe_print_comment(expr.span.lo());
|
||||||
|
|
||||||
let attrs = &expr.attrs;
|
let attrs = &expr.attrs;
|
||||||
|
@ -314,19 +362,19 @@ impl<'a> State<'a> {
|
||||||
self.print_expr_tup(exprs);
|
self.print_expr_tup(exprs);
|
||||||
}
|
}
|
||||||
ast::ExprKind::Call(func, args) => {
|
ast::ExprKind::Call(func, args) => {
|
||||||
self.print_expr_call(func, args);
|
self.print_expr_call(func, args, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::MethodCall(box ast::MethodCall { seg, receiver, args, .. }) => {
|
ast::ExprKind::MethodCall(box ast::MethodCall { seg, receiver, args, .. }) => {
|
||||||
self.print_expr_method_call(seg, receiver, args);
|
self.print_expr_method_call(seg, receiver, args, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::Binary(op, lhs, rhs) => {
|
ast::ExprKind::Binary(op, lhs, rhs) => {
|
||||||
self.print_expr_binary(*op, lhs, rhs);
|
self.print_expr_binary(*op, lhs, rhs, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::Unary(op, expr) => {
|
ast::ExprKind::Unary(op, expr) => {
|
||||||
self.print_expr_unary(*op, expr);
|
self.print_expr_unary(*op, expr, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::AddrOf(k, m, expr) => {
|
ast::ExprKind::AddrOf(k, m, expr) => {
|
||||||
self.print_expr_addr_of(*k, *m, expr);
|
self.print_expr_addr_of(*k, *m, expr, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::Lit(token_lit) => {
|
ast::ExprKind::Lit(token_lit) => {
|
||||||
self.print_token_literal(*token_lit, expr.span);
|
self.print_token_literal(*token_lit, expr.span);
|
||||||
|
@ -337,7 +385,7 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
ast::ExprKind::Cast(expr, ty) => {
|
ast::ExprKind::Cast(expr, ty) => {
|
||||||
let prec = AssocOp::As.precedence() as i8;
|
let prec = AssocOp::As.precedence() as i8;
|
||||||
self.print_expr_maybe_paren(expr, prec);
|
self.print_expr_maybe_paren(expr, prec, fixup);
|
||||||
self.space();
|
self.space();
|
||||||
self.word_space("as");
|
self.word_space("as");
|
||||||
self.print_type(ty);
|
self.print_type(ty);
|
||||||
|
@ -345,7 +393,7 @@ impl<'a> State<'a> {
|
||||||
ast::ExprKind::Type(expr, ty) => {
|
ast::ExprKind::Type(expr, ty) => {
|
||||||
self.word("type_ascribe!(");
|
self.word("type_ascribe!(");
|
||||||
self.ibox(0);
|
self.ibox(0);
|
||||||
self.print_expr(expr);
|
self.print_expr(expr, FixupContext::default());
|
||||||
|
|
||||||
self.word(",");
|
self.word(",");
|
||||||
self.space_if_not_bol();
|
self.space_if_not_bol();
|
||||||
|
@ -355,7 +403,7 @@ impl<'a> State<'a> {
|
||||||
self.word(")");
|
self.word(")");
|
||||||
}
|
}
|
||||||
ast::ExprKind::Let(pat, scrutinee, _, _) => {
|
ast::ExprKind::Let(pat, scrutinee, _, _) => {
|
||||||
self.print_let(pat, scrutinee);
|
self.print_let(pat, scrutinee, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::If(test, blk, elseopt) => self.print_if(test, blk, elseopt.as_deref()),
|
ast::ExprKind::If(test, blk, elseopt) => self.print_if(test, blk, elseopt.as_deref()),
|
||||||
ast::ExprKind::While(test, blk, opt_label) => {
|
ast::ExprKind::While(test, blk, opt_label) => {
|
||||||
|
@ -428,7 +476,7 @@ impl<'a> State<'a> {
|
||||||
|
|
||||||
self.print_fn_params_and_ret(fn_decl, true);
|
self.print_fn_params_and_ret(fn_decl, true);
|
||||||
self.space();
|
self.space();
|
||||||
self.print_expr(body);
|
self.print_expr(body, FixupContext::default());
|
||||||
self.end(); // need to close a box
|
self.end(); // need to close a box
|
||||||
|
|
||||||
// a box will be closed by print_expr, but we didn't want an overall
|
// a box will be closed by print_expr, but we didn't want an overall
|
||||||
|
@ -456,33 +504,33 @@ impl<'a> State<'a> {
|
||||||
self.print_block_with_attrs(blk, attrs);
|
self.print_block_with_attrs(blk, attrs);
|
||||||
}
|
}
|
||||||
ast::ExprKind::Await(expr, _) => {
|
ast::ExprKind::Await(expr, _) => {
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX);
|
self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX, fixup);
|
||||||
self.word(".await");
|
self.word(".await");
|
||||||
}
|
}
|
||||||
ast::ExprKind::Assign(lhs, rhs, _) => {
|
ast::ExprKind::Assign(lhs, rhs, _) => {
|
||||||
let prec = AssocOp::Assign.precedence() as i8;
|
let prec = AssocOp::Assign.precedence() as i8;
|
||||||
self.print_expr_maybe_paren(lhs, prec + 1);
|
self.print_expr_maybe_paren(lhs, prec + 1, fixup);
|
||||||
self.space();
|
self.space();
|
||||||
self.word_space("=");
|
self.word_space("=");
|
||||||
self.print_expr_maybe_paren(rhs, prec);
|
self.print_expr_maybe_paren(rhs, prec, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::AssignOp(op, lhs, rhs) => {
|
ast::ExprKind::AssignOp(op, lhs, rhs) => {
|
||||||
let prec = AssocOp::Assign.precedence() as i8;
|
let prec = AssocOp::Assign.precedence() as i8;
|
||||||
self.print_expr_maybe_paren(lhs, prec + 1);
|
self.print_expr_maybe_paren(lhs, prec + 1, fixup);
|
||||||
self.space();
|
self.space();
|
||||||
self.word(op.node.as_str());
|
self.word(op.node.as_str());
|
||||||
self.word_space("=");
|
self.word_space("=");
|
||||||
self.print_expr_maybe_paren(rhs, prec);
|
self.print_expr_maybe_paren(rhs, prec, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::Field(expr, ident) => {
|
ast::ExprKind::Field(expr, ident) => {
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX);
|
self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX, fixup);
|
||||||
self.word(".");
|
self.word(".");
|
||||||
self.print_ident(*ident);
|
self.print_ident(*ident);
|
||||||
}
|
}
|
||||||
ast::ExprKind::Index(expr, index, _) => {
|
ast::ExprKind::Index(expr, index, _) => {
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX);
|
self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX, fixup);
|
||||||
self.word("[");
|
self.word("[");
|
||||||
self.print_expr(index);
|
self.print_expr(index, FixupContext::default());
|
||||||
self.word("]");
|
self.word("]");
|
||||||
}
|
}
|
||||||
ast::ExprKind::Range(start, end, limits) => {
|
ast::ExprKind::Range(start, end, limits) => {
|
||||||
|
@ -492,14 +540,14 @@ impl<'a> State<'a> {
|
||||||
// a "normal" binop gets parenthesized. (`LOr` is the lowest-precedence binop.)
|
// a "normal" binop gets parenthesized. (`LOr` is the lowest-precedence binop.)
|
||||||
let fake_prec = AssocOp::LOr.precedence() as i8;
|
let fake_prec = AssocOp::LOr.precedence() as i8;
|
||||||
if let Some(e) = start {
|
if let Some(e) = start {
|
||||||
self.print_expr_maybe_paren(e, fake_prec);
|
self.print_expr_maybe_paren(e, fake_prec, fixup);
|
||||||
}
|
}
|
||||||
match limits {
|
match limits {
|
||||||
ast::RangeLimits::HalfOpen => self.word(".."),
|
ast::RangeLimits::HalfOpen => self.word(".."),
|
||||||
ast::RangeLimits::Closed => self.word("..="),
|
ast::RangeLimits::Closed => self.word("..="),
|
||||||
}
|
}
|
||||||
if let Some(e) = end {
|
if let Some(e) = end {
|
||||||
self.print_expr_maybe_paren(e, fake_prec);
|
self.print_expr_maybe_paren(e, fake_prec, fixup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::ExprKind::Underscore => self.word("_"),
|
ast::ExprKind::Underscore => self.word("_"),
|
||||||
|
@ -513,7 +561,7 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
if let Some(expr) = opt_expr {
|
if let Some(expr) = opt_expr {
|
||||||
self.space();
|
self.space();
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
|
self.print_expr_maybe_paren(expr, parser::PREC_JUMP, fixup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::ExprKind::Continue(opt_label) => {
|
ast::ExprKind::Continue(opt_label) => {
|
||||||
|
@ -527,7 +575,7 @@ impl<'a> State<'a> {
|
||||||
self.word("return");
|
self.word("return");
|
||||||
if let Some(expr) = result {
|
if let Some(expr) = result {
|
||||||
self.word(" ");
|
self.word(" ");
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
|
self.print_expr_maybe_paren(expr, parser::PREC_JUMP, fixup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::ExprKind::Yeet(result) => {
|
ast::ExprKind::Yeet(result) => {
|
||||||
|
@ -536,13 +584,13 @@ impl<'a> State<'a> {
|
||||||
self.word("yeet");
|
self.word("yeet");
|
||||||
if let Some(expr) = result {
|
if let Some(expr) = result {
|
||||||
self.word(" ");
|
self.word(" ");
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
|
self.print_expr_maybe_paren(expr, parser::PREC_JUMP, fixup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::ExprKind::Become(result) => {
|
ast::ExprKind::Become(result) => {
|
||||||
self.word("become");
|
self.word("become");
|
||||||
self.word(" ");
|
self.word(" ");
|
||||||
self.print_expr_maybe_paren(result, parser::PREC_JUMP);
|
self.print_expr_maybe_paren(result, parser::PREC_JUMP, fixup);
|
||||||
}
|
}
|
||||||
ast::ExprKind::InlineAsm(a) => {
|
ast::ExprKind::InlineAsm(a) => {
|
||||||
// FIXME: This should have its own syntax, distinct from a macro invocation.
|
// FIXME: This should have its own syntax, distinct from a macro invocation.
|
||||||
|
@ -557,7 +605,7 @@ impl<'a> State<'a> {
|
||||||
self.word(reconstruct_format_args_template_string(&fmt.template));
|
self.word(reconstruct_format_args_template_string(&fmt.template));
|
||||||
for arg in fmt.arguments.all_args() {
|
for arg in fmt.arguments.all_args() {
|
||||||
self.word_space(",");
|
self.word_space(",");
|
||||||
self.print_expr(&arg.expr);
|
self.print_expr(&arg.expr, FixupContext::default());
|
||||||
}
|
}
|
||||||
self.end();
|
self.end();
|
||||||
self.pclose();
|
self.pclose();
|
||||||
|
@ -584,7 +632,7 @@ impl<'a> State<'a> {
|
||||||
ast::ExprKind::MacCall(m) => self.print_mac(m),
|
ast::ExprKind::MacCall(m) => self.print_mac(m),
|
||||||
ast::ExprKind::Paren(e) => {
|
ast::ExprKind::Paren(e) => {
|
||||||
self.popen();
|
self.popen();
|
||||||
self.print_expr(e);
|
self.print_expr(e, FixupContext::default());
|
||||||
self.pclose();
|
self.pclose();
|
||||||
}
|
}
|
||||||
ast::ExprKind::Yield(e) => {
|
ast::ExprKind::Yield(e) => {
|
||||||
|
@ -592,11 +640,11 @@ impl<'a> State<'a> {
|
||||||
|
|
||||||
if let Some(expr) = e {
|
if let Some(expr) = e {
|
||||||
self.space();
|
self.space();
|
||||||
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
|
self.print_expr_maybe_paren(expr, parser::PREC_JUMP, fixup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::ExprKind::Try(e) => {
|
ast::ExprKind::Try(e) => {
|
||||||
self.print_expr_maybe_paren(e, parser::PREC_POSTFIX);
|
self.print_expr_maybe_paren(e, parser::PREC_POSTFIX, fixup);
|
||||||
self.word("?")
|
self.word("?")
|
||||||
}
|
}
|
||||||
ast::ExprKind::TryBlock(blk) => {
|
ast::ExprKind::TryBlock(blk) => {
|
||||||
|
@ -628,7 +676,7 @@ impl<'a> State<'a> {
|
||||||
self.space();
|
self.space();
|
||||||
if let Some(e) = &arm.guard {
|
if let Some(e) = &arm.guard {
|
||||||
self.word_space("if");
|
self.word_space("if");
|
||||||
self.print_expr(e);
|
self.print_expr(e, FixupContext::default());
|
||||||
self.space();
|
self.space();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,7 +700,7 @@ impl<'a> State<'a> {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.end(); // Close the ibox for the pattern.
|
self.end(); // Close the ibox for the pattern.
|
||||||
self.print_expr(body);
|
self.print_expr(body, FixupContext::default());
|
||||||
self.word(",");
|
self.word(",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::pp::Breaks::Inconsistent;
|
use crate::pp::Breaks::Inconsistent;
|
||||||
|
use crate::pprust::state::expr::FixupContext;
|
||||||
use crate::pprust::state::{AnnNode, PrintState, State, INDENT_UNIT};
|
use crate::pprust::state::{AnnNode, PrintState, State, INDENT_UNIT};
|
||||||
|
|
||||||
use ast::StaticItem;
|
use ast::StaticItem;
|
||||||
|
@ -97,7 +98,7 @@ impl<'a> State<'a> {
|
||||||
self.end(); // end the head-ibox
|
self.end(); // end the head-ibox
|
||||||
if let Some(body) = body {
|
if let Some(body) = body {
|
||||||
self.word_space("=");
|
self.word_space("=");
|
||||||
self.print_expr(body);
|
self.print_expr(body, FixupContext::default());
|
||||||
}
|
}
|
||||||
self.print_where_clause(&generics.where_clause);
|
self.print_where_clause(&generics.where_clause);
|
||||||
self.word(";");
|
self.word(";");
|
||||||
|
@ -514,7 +515,7 @@ impl<'a> State<'a> {
|
||||||
if let Some(d) = &v.disr_expr {
|
if let Some(d) = &v.disr_expr {
|
||||||
self.space();
|
self.space();
|
||||||
self.word_space("=");
|
self.word_space("=");
|
||||||
self.print_expr(&d.value)
|
self.print_expr(&d.value, FixupContext::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#![feature(coroutines)]
|
#![feature(coroutines)]
|
||||||
#![feature(decl_macro)]
|
#![feature(decl_macro)]
|
||||||
#![feature(explicit_tail_calls)]
|
#![feature(explicit_tail_calls)]
|
||||||
|
#![feature(if_let_guard)]
|
||||||
|
#![feature(let_chains)]
|
||||||
#![feature(more_qualified_paths)]
|
#![feature(more_qualified_paths)]
|
||||||
#![feature(never_patterns)]
|
#![feature(never_patterns)]
|
||||||
#![feature(raw_ref_op)]
|
#![feature(raw_ref_op)]
|
||||||
|
@ -47,7 +49,7 @@ macro_rules! c1 {
|
||||||
// easy to find the cases where the two pretty-printing approaches give
|
// easy to find the cases where the two pretty-printing approaches give
|
||||||
// different results.
|
// different results.
|
||||||
macro_rules! c2 {
|
macro_rules! c2 {
|
||||||
($frag:ident, [$($tt:tt)*], $s1:literal, $s2:literal) => {
|
($frag:ident, [$($tt:tt)*], $s1:literal, $s2:literal $(,)?) => {
|
||||||
assert_ne!($s1, $s2, "should use `c1!` instead");
|
assert_ne!($s1, $s2, "should use `c1!` instead");
|
||||||
assert_eq!($frag!($($tt)*), $s1);
|
assert_eq!($frag!($($tt)*), $s1);
|
||||||
assert_eq!(stringify!($($tt)*), $s2);
|
assert_eq!(stringify!($($tt)*), $s2);
|
||||||
|
@ -127,6 +129,23 @@ fn test_expr() {
|
||||||
|
|
||||||
// ExprKind::Let
|
// ExprKind::Let
|
||||||
c1!(expr, [ if let Some(a) = b { c } else { d } ], "if let Some(a) = b { c } else { d }");
|
c1!(expr, [ if let Some(a) = b { c } else { d } ], "if let Some(a) = b { c } else { d }");
|
||||||
|
c1!(expr, [ if let _ = true && false {} ], "if let _ = true && false {}");
|
||||||
|
c1!(expr, [ if let _ = (true && false) {} ], "if let _ = (true && false) {}");
|
||||||
|
macro_rules! c2_if_let {
|
||||||
|
($expr:expr, $expr_expected:expr, $tokens_expected:expr $(,)?) => {
|
||||||
|
c2!(expr, [ if let _ = $expr {} ], $expr_expected, $tokens_expected);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
c2_if_let!(
|
||||||
|
true && false,
|
||||||
|
"if let _ = (true && false) {}",
|
||||||
|
"if let _ = true && false {}",
|
||||||
|
);
|
||||||
|
c2!(expr,
|
||||||
|
[ match () { _ if let _ = Struct {} => {} } ],
|
||||||
|
"match () { _ if let _ = Struct {} => {} }",
|
||||||
|
"match() { _ if let _ = Struct {} => {} }",
|
||||||
|
);
|
||||||
|
|
||||||
// ExprKind::If
|
// ExprKind::If
|
||||||
c1!(expr, [ if true {} ], "if true {}");
|
c1!(expr, [ if true {} ], "if true {}");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue