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, 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)
{
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::<Vec<_>>())
.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('{') {
"{ "
} else {
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();
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)
}
}