use alloc::fmt; use core::fmt::Display as _; use ruff_python_ast as ast; use ruff_text_size::Ranged; use rustpython_compiler_core::SourceFile; use rustpython_literal::escape::{AsciiEscape, UnicodeEscape}; mod precedence { macro_rules! precedence { ($($op:ident,)*) => { precedence!(@0, $($op,)*); }; (@$i:expr, $op1:ident, $($op:ident,)*) => { pub const $op1: u8 = $i; precedence!(@$i + 1, $($op,)*); }; (@$i:expr,) => {}; } precedence!( TUPLE, TEST, OR, AND, NOT, CMP, // "EXPR" = BOR, BXOR, BAND, SHIFT, ARITH, TERM, FACTOR, POWER, AWAIT, ATOM, ); pub const EXPR: u8 = BOR; } struct Unparser<'a, 'b, 'c> { f: &'b mut fmt::Formatter<'a>, source: &'c SourceFile, } impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { const fn new(f: &'b mut fmt::Formatter<'a>, source: &'c SourceFile) -> Self { Self { f, source } } fn p(&mut self, s: &str) -> fmt::Result { self.f.write_str(s) } fn p_id(&mut self, s: &ast::Identifier) -> fmt::Result { self.f.write_str(s.as_str()) } fn p_if(&mut self, cond: bool, s: &str) -> fmt::Result { if cond { self.f.write_str(s)?; } Ok(()) } fn p_delim(&mut self, first: &mut bool, s: &str) -> fmt::Result { self.p_if(!core::mem::take(first), s) } fn write_fmt(&mut self, f: fmt::Arguments<'_>) -> fmt::Result { self.f.write_fmt(f) } fn unparse_expr(&mut self, ast: &ast::Expr, level: u8) -> fmt::Result { macro_rules! op_prec { ($op_ty:ident, $x:expr, $enu:path, $($var:ident($op:literal, $prec:ident)),*$(,)?) => { match $x { $(<$enu>::$var => (op_prec!(@space $op_ty, $op), precedence::$prec),)* } }; (@space bin, $op:literal) => { concat!(" ", $op, " ") }; (@space un, $op:literal) => { $op }; } macro_rules! group_if { ($lvl:expr, $body:block) => {{ let group = level > $lvl; self.p_if(group, "(")?; let ret = $body; self.p_if(group, ")")?; ret }}; } match &ast { ast::Expr::BoolOp(ast::ExprBoolOp { op, values, node_index: _, range: _range, }) => { let (op, prec) = op_prec!(bin, op, ast::BoolOp, And("and", AND), Or("or", OR)); group_if!(prec, { let mut first = true; for val in values { self.p_delim(&mut first, op)?; self.unparse_expr(val, prec + 1)?; } }) } ast::Expr::Named(ast::ExprNamed { target, value, node_index: _, range: _range, }) => { group_if!(precedence::TUPLE, { self.unparse_expr(target, precedence::ATOM)?; self.p(" := ")?; self.unparse_expr(value, precedence::ATOM)?; }) } ast::Expr::BinOp(ast::ExprBinOp { left, op, right, node_index: _, range: _range, }) => { let right_associative = matches!(op, ast::Operator::Pow); let (op, prec) = op_prec!( bin, op, ast::Operator, Add("+", ARITH), Sub("-", ARITH), Mult("*", TERM), MatMult("@", TERM), Div("/", TERM), Mod("%", TERM), Pow("**", POWER), LShift("<<", SHIFT), RShift(">>", SHIFT), BitOr("|", BOR), BitXor("^", BXOR), BitAnd("&", BAND), FloorDiv("//", TERM), ); group_if!(prec, { self.unparse_expr(left, prec + right_associative as u8)?; self.p(op)?; self.unparse_expr(right, prec + !right_associative as u8)?; }) } ast::Expr::UnaryOp(ast::ExprUnaryOp { op, operand, node_index: _, range: _range, }) => { let (op, prec) = op_prec!( un, op, ast::UnaryOp, Invert("~", FACTOR), Not("not ", NOT), UAdd("+", FACTOR), USub("-", FACTOR) ); group_if!(prec, { self.p(op)?; self.unparse_expr(operand, prec)?; }) } ast::Expr::Lambda(ast::ExprLambda { parameters, body, node_index: _, range: _range, }) => { group_if!(precedence::TEST, { if let Some(parameters) = parameters { self.p("lambda ")?; self.unparse_arguments(parameters)?; } else { self.p("lambda")?; } write!(self, ": {}", UnparseExpr::new(body, self.source))?; }) } ast::Expr::If(ast::ExprIf { test, body, orelse, node_index: _, range: _range, }) => { group_if!(precedence::TEST, { self.unparse_expr(body, precedence::TEST + 1)?; self.p(" if ")?; self.unparse_expr(test, precedence::TEST + 1)?; self.p(" else ")?; self.unparse_expr(orelse, precedence::TEST)?; }) } ast::Expr::Dict(ast::ExprDict { items, node_index: _, range: _range, }) => { self.p("{")?; let mut first = true; for item in items { self.p_delim(&mut first, ", ")?; if let Some(k) = &item.key { write!(self, "{}: ", UnparseExpr::new(k, self.source))?; } else { self.p("**")?; } self.unparse_expr(&item.value, level)?; } self.p("}")?; } ast::Expr::Set(ast::ExprSet { elts, node_index: _, range: _range, }) => { self.p("{")?; let mut first = true; for v in elts { self.p_delim(&mut first, ", ")?; self.unparse_expr(v, precedence::TEST)?; } self.p("}")?; } ast::Expr::ListComp(ast::ExprListComp { elt, generators, node_index: _, range: _range, }) => { self.p("[")?; self.unparse_expr(elt, precedence::TEST)?; self.unparse_comp(generators)?; self.p("]")?; } ast::Expr::SetComp(ast::ExprSetComp { elt, generators, node_index: _, range: _range, }) => { self.p("{")?; self.unparse_expr(elt, precedence::TEST)?; self.unparse_comp(generators)?; self.p("}")?; } ast::Expr::DictComp(ast::ExprDictComp { key, value, generators, node_index: _, range: _range, }) => { self.p("{")?; self.unparse_expr(key, precedence::TEST)?; self.p(": ")?; self.unparse_expr(value, precedence::TEST)?; self.unparse_comp(generators)?; self.p("}")?; } ast::Expr::Generator(ast::ExprGenerator { parenthesized: _, elt, generators, node_index: _, range: _range, }) => { self.p("(")?; self.unparse_expr(elt, precedence::TEST)?; self.unparse_comp(generators)?; self.p(")")?; } ast::Expr::Await(ast::ExprAwait { value, node_index: _, range: _range, }) => { group_if!(precedence::AWAIT, { self.p("await ")?; self.unparse_expr(value, precedence::ATOM)?; }) } ast::Expr::Yield(ast::ExprYield { value, node_index: _, range: _range, }) => { if let Some(value) = value { write!(self, "(yield {})", UnparseExpr::new(value, self.source))?; } else { self.p("(yield)")?; } } ast::Expr::YieldFrom(ast::ExprYieldFrom { value, node_index: _, range: _range, }) => { write!( self, "(yield from {})", UnparseExpr::new(value, self.source) )?; } ast::Expr::Compare(ast::ExprCompare { left, ops, comparators, node_index: _, range: _range, }) => { group_if!(precedence::CMP, { let new_lvl = precedence::CMP + 1; self.unparse_expr(left, new_lvl)?; for (op, cmp) in ops.iter().zip(comparators) { self.p(" ")?; self.p(op.as_str())?; self.p(" ")?; self.unparse_expr(cmp, new_lvl)?; } }) } ast::Expr::Call(ast::ExprCall { func, arguments: ast::Arguments { args, keywords, .. }, node_index: _, range: _range, }) => { self.unparse_expr(func, precedence::ATOM)?; self.p("(")?; if let ( [ ast::Expr::Generator(ast::ExprGenerator { elt, generators, node_index: _, range: _range, .. }), ], [], ) = (&**args, &**keywords) { // make sure a single genexpr doesn't get double parens self.unparse_expr(elt, precedence::TEST)?; self.unparse_comp(generators)?; } else { let mut first = true; for arg in args { self.p_delim(&mut first, ", ")?; self.unparse_expr(arg, precedence::TEST)?; } for kw in keywords { self.p_delim(&mut first, ", ")?; if let Some(arg) = &kw.arg { self.p_id(arg)?; self.p("=")?; } else { self.p("**")?; } self.unparse_expr(&kw.value, precedence::TEST)?; } } self.p(")")?; } ast::Expr::FString(ast::ExprFString { value, .. }) => self.unparse_fstring(value)?, ast::Expr::TString(ast::ExprTString { value, .. }) => self.unparse_tstring(value)?, ast::Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { if value.is_unicode() { self.p("u")? } UnicodeEscape::new_repr(value.to_str().as_ref()) .str_repr() .fmt(self.f)? } ast::Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => { AsciiEscape::new_repr(&value.bytes().collect::>()) .bytes_repr() .fmt(self.f)? } ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => { #[allow(clippy::correctness, clippy::assertions_on_constants)] const { assert!(f64::MAX_10_EXP == 308) }; let inf_str = "1e309"; match value { ast::Number::Int(int) => int.fmt(self.f)?, &ast::Number::Float(fp) => { if fp.is_infinite() { self.p(inf_str)? } else { self.p(&rustpython_literal::float::to_string(fp))? } } &ast::Number::Complex { real, imag } => self .p(&rustpython_literal::complex::to_string(real, imag) .replace("inf", inf_str))?, } } ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => { self.p(if *value { "True" } else { "False" })? } ast::Expr::NoneLiteral(ast::ExprNoneLiteral { .. }) => self.p("None")?, ast::Expr::EllipsisLiteral(ast::ExprEllipsisLiteral { .. }) => self.p("...")?, ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => { self.unparse_expr(value, precedence::ATOM)?; let period = if let ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(_), .. }) = value.as_ref() { " ." } else { "." }; self.p(period)?; self.p_id(attr)?; } ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { self.unparse_expr(value, precedence::ATOM)?; let lvl = precedence::TUPLE; self.p("[")?; self.unparse_expr(slice, lvl)?; self.p("]")?; } ast::Expr::Starred(ast::ExprStarred { value, .. }) => { self.p("*")?; self.unparse_expr(value, precedence::EXPR)?; } ast::Expr::Name(ast::ExprName { id, .. }) => self.p(id.as_str())?, ast::Expr::List(ast::ExprList { elts, .. }) => { self.p("[")?; let mut first = true; for elt in elts { self.p_delim(&mut first, ", ")?; self.unparse_expr(elt, precedence::TEST)?; } self.p("]")?; } ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => { if elts.is_empty() { self.p("()")?; } else { group_if!(precedence::TUPLE, { let mut first = true; for elt in elts { self.p_delim(&mut first, ", ")?; self.unparse_expr(elt, precedence::TEST)?; } self.p_if(elts.len() == 1, ",")?; }) } } ast::Expr::Slice(ast::ExprSlice { lower, upper, step, node_index: _, range: _range, }) => { if let Some(lower) = lower { self.unparse_expr(lower, precedence::TEST)?; } self.p(":")?; if let Some(upper) = upper { self.unparse_expr(upper, precedence::TEST)?; } if let Some(step) = step { self.p(":")?; self.unparse_expr(step, precedence::TEST)?; } } ast::Expr::IpyEscapeCommand(_) => {} } Ok(()) } fn unparse_arguments(&mut self, args: &ast::Parameters) -> fmt::Result { let mut first = true; for (i, arg) in args.posonlyargs.iter().chain(&args.args).enumerate() { self.p_delim(&mut first, ", ")?; self.unparse_function_arg(arg)?; self.p_if(i + 1 == args.posonlyargs.len(), ", /")?; } if args.vararg.is_some() || !args.kwonlyargs.is_empty() { self.p_delim(&mut first, ", ")?; self.p("*")?; } if let Some(vararg) = &args.vararg { self.unparse_arg(vararg)?; } for kwarg in &args.kwonlyargs { self.p_delim(&mut first, ", ")?; self.unparse_function_arg(kwarg)?; } if let Some(kwarg) = &args.kwarg { self.p_delim(&mut first, ", ")?; self.p("**")?; self.unparse_arg(kwarg)?; } Ok(()) } fn unparse_function_arg(&mut self, arg: &ast::ParameterWithDefault) -> fmt::Result { self.unparse_arg(&arg.parameter)?; if let Some(default) = &arg.default { write!(self, "={}", UnparseExpr::new(default, self.source))?; } Ok(()) } fn unparse_arg(&mut self, arg: &ast::Parameter) -> fmt::Result { self.p_id(&arg.name)?; if let Some(ann) = &arg.annotation { write!(self, ": {}", UnparseExpr::new(ann, self.source))?; } Ok(()) } fn unparse_comp(&mut self, generators: &[ast::Comprehension]) -> fmt::Result { for comp in generators { self.p(if comp.is_async { " async for " } else { " for " })?; self.unparse_expr(&comp.target, precedence::TUPLE)?; self.p(" in ")?; self.unparse_expr(&comp.iter, precedence::TEST + 1)?; for cond in &comp.ifs { self.p(" if ")?; self.unparse_expr(cond, precedence::TEST + 1)?; } } Ok(()) } fn unparse_fstring_body(&mut self, elements: &[ast::InterpolatedStringElement]) -> fmt::Result { for elem in elements { self.unparse_fstring_elem(elem)?; } Ok(()) } fn unparse_formatted( &mut self, val: &ast::Expr, debug_text: Option<&ast::DebugText>, conversion: ast::ConversionFlag, spec: Option<&ast::InterpolatedStringFormatSpec>, ) -> fmt::Result { let buffered = fmt::from_fn(|f| Unparser::new(f, self.source).unparse_expr(val, precedence::TEST + 1)) .to_string(); if let Some(ast::DebugText { leading, trailing }) = debug_text { self.p(leading)?; self.p(self.source.slice(val.range()))?; self.p(trailing)?; } let brace = if buffered.starts_with('{') { // put a space to avoid escaping the bracket "{ " } else { // Preserve leading whitespace between '{' and the expression let source_text = self.source.source_text(); let start = val.range().start().to_usize(); if start > 0 && source_text .as_bytes() .get(start - 1) .is_some_and(|b| b.is_ascii_whitespace()) { "{ " } else { "{" } }; self.p(brace)?; self.p(&buffered)?; drop(buffered); if conversion != ast::ConversionFlag::None { self.p("!")?; let buf = &[conversion as u8]; let c = core::str::from_utf8(buf).unwrap(); self.p(c)?; } if let Some(spec) = spec { self.p(":")?; self.unparse_fstring_body(&spec.elements)?; } self.p("}")?; Ok(()) } fn unparse_fstring_elem(&mut self, elem: &ast::InterpolatedStringElement) -> fmt::Result { match elem { ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement { expression, debug_text, conversion, format_spec, .. }) => self.unparse_formatted( expression, debug_text.as_ref(), *conversion, format_spec.as_deref(), ), ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement { value, .. }) => self.unparse_fstring_str(value), } } fn unparse_fstring_str(&mut self, s: &str) -> fmt::Result { let s = s.replace('{', "{{").replace('}', "}}"); self.p(&s) } fn unparse_fstring(&mut self, value: &ast::FStringValue) -> fmt::Result { self.p("f")?; let body = fmt::from_fn(|f| { value.iter().try_for_each(|part| match part { ast::FStringPart::Literal(lit) => f.write_str(lit), ast::FStringPart::FString(ast::FString { elements, .. }) => { Unparser::new(f, self.source).unparse_fstring_body(elements) } }) }) .to_string(); // .unparse_fstring_body(elements)); UnicodeEscape::new_repr(body.as_str().as_ref()) .str_repr() .write(self.f) } fn unparse_tstring(&mut self, value: &ast::TStringValue) -> fmt::Result { self.p("t")?; let body = fmt::from_fn(|f| { value.iter().try_for_each(|tstring| { Unparser::new(f, self.source).unparse_fstring_body(&tstring.elements) }) }) .to_string(); UnicodeEscape::new_repr(body.as_str().as_ref()) .str_repr() .write(self.f) } } pub struct UnparseExpr<'a> { expr: &'a ast::Expr, source: &'a SourceFile, } impl<'a> UnparseExpr<'a> { pub const fn new(expr: &'a ast::Expr, source: &'a SourceFile) -> Self { Self { expr, source } } } impl fmt::Display for UnparseExpr<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Unparser::new(f, self.source).unparse_expr(self.expr, precedence::TEST) } }