From d3d92bbb6f7b16930e233e7c216d259585060f03 Mon Sep 17 00:00:00 2001 From: Noa Date: Thu, 27 Mar 2025 17:33:21 -0500 Subject: [PATCH] Update unparse to work with ruff & remove ruff_python_codegen --- Cargo.lock | 25 +- Cargo.toml | 1 - Lib/test/test_future_stmt/test_future.py | 6 - compiler/codegen/Cargo.toml | 2 +- compiler/codegen/src/compile.rs | 54 ++-- compiler/codegen/src/lib.rs | 1 + compiler/codegen/src/unparse.rs | 341 +++++++++++------------ compiler/literal/src/float.rs | 33 +++ vm/src/builtins/complex.rs | 32 +-- 9 files changed, 219 insertions(+), 276 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55872f1d6..5de6ad43a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2185,29 +2185,6 @@ dependencies = [ "rustc-hash 2.1.1", ] -[[package]] -name = "ruff_python_codegen" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" -dependencies = [ - "ruff_python_ast", - "ruff_python_literal", - "ruff_python_parser", - "ruff_source_file", - "ruff_text_size", -] - -[[package]] -name = "ruff_python_literal" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" -dependencies = [ - "bitflags 2.8.0", - "itertools 0.14.0", - "ruff_python_ast", - "unic-ucd-category", -] - [[package]] name = "ruff_python_parser" version = "0.0.0" @@ -2323,11 +2300,11 @@ dependencies = [ "num-complex", "num-traits", "ruff_python_ast", - "ruff_python_codegen", "ruff_source_file", "ruff_text_size", "rustpython-compiler-core", "rustpython-compiler-source", + "rustpython-literal", "rustpython-wtf8", "thiserror 2.0.11", "unicode_names2", diff --git a/Cargo.toml b/Cargo.toml index c9f9c9652..ccfec120f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,7 +148,6 @@ ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.1 ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } -ruff_python_codegen = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } ahash = "0.8.11" ascii = "1.1" diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py index ad9b5d5a8..0e0805103 100644 --- a/Lib/test/test_future_stmt/test_future.py +++ b/Lib/test/test_future_stmt/test_future.py @@ -198,8 +198,6 @@ class AnnotationsFutureTestCase(unittest.TestCase): ) return scope - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_annotations(self): eq = self.assertAnnotationEqual eq('...') @@ -364,8 +362,6 @@ class AnnotationsFutureTestCase(unittest.TestCase): eq('(((a, b)))', '(a, b)') eq("1 + 2 + 3") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_fstring_debug_annotations(self): # f-strings with '=' don't round trip very well, so set the expected # result explicitly. @@ -376,8 +372,6 @@ class AnnotationsFutureTestCase(unittest.TestCase): self.assertAnnotationEqual("f'{x=!a}'", expected="f'x={x!a}'") self.assertAnnotationEqual("f'{x=!s:*^20}'", expected="f'x={x!s:*^20}'") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_infinity_numbers(self): inf = "1e" + repr(sys.float_info.max_10_exp + 1) infj = f"{inf}j" diff --git a/compiler/codegen/Cargo.toml b/compiler/codegen/Cargo.toml index f54d6476a..53469b9f6 100644 --- a/compiler/codegen/Cargo.toml +++ b/compiler/codegen/Cargo.toml @@ -14,11 +14,11 @@ license.workspace = true # rustpython-parser-core = { workspace = true } rustpython-compiler-core = { workspace = true } rustpython-compiler-source = {workspace = true } +rustpython-literal = {workspace = true } rustpython-wtf8 = { workspace = true } ruff_python_ast = { workspace = true } ruff_text_size = { workspace = true } ruff_source_file = { workspace = true } -ruff_python_codegen = { workspace = true } ahash = { workspace = true } bitflags = { workspace = true } diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 03eff74ba..38d5b8fb1 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -12,6 +12,7 @@ use crate::{ error::{CodegenError, CodegenErrorType}, ir, symboltable::{self, SymbolFlags, SymbolScope, SymbolTable}, + unparse::unparse_expr, }; use itertools::Itertools; use malachite_bigint::BigInt; @@ -2026,11 +2027,10 @@ impl Compiler<'_> { fn compile_annotation(&mut self, annotation: &Expr) -> CompileResult<()> { if self.future_annotations { - // FIXME: codegen? - let ident = Default::default(); - let codegen = ruff_python_codegen::Generator::new(&ident, Default::default()); self.emit_load_const(ConstantData::Str { - value: codegen.expr(annotation).into(), + value: unparse_expr(annotation, &self.source_code) + .to_string() + .into(), }); } else { self.compile_expression(annotation)?; @@ -3397,7 +3397,9 @@ impl Compiler<'_> { flags: FStringFlags, fstring_elements: &FStringElements, ) -> CompileResult<()> { + let mut element_count = 0; for element in fstring_elements { + element_count += 1; match element { FStringElement::Literal(string) => { if string.value.contains(char::REPLACEMENT_CHARACTER) { @@ -3419,26 +3421,14 @@ impl Compiler<'_> { FStringElement::Expression(fstring_expr) => { let mut conversion = fstring_expr.conversion; - let debug_text_count = match &fstring_expr.debug_text { - None => 0, - Some(DebugText { leading, trailing }) => { - let range = fstring_expr.expression.range(); - let source = self.source_code.get_range(range); - let source = source.to_string(); + if let Some(DebugText { leading, trailing }) = &fstring_expr.debug_text { + let range = fstring_expr.expression.range(); + let source = self.source_code.get_range(range); + let text = [leading, source, trailing].concat(); - self.emit_load_const(ConstantData::Str { - value: leading.to_string().into(), - }); - self.emit_load_const(ConstantData::Str { - value: source.into(), - }); - self.emit_load_const(ConstantData::Str { - value: trailing.to_string().into(), - }); - - 3 - } - }; + self.emit_load_const(ConstantData::Str { value: text.into() }); + element_count += 1; + } match &fstring_expr.format_spec { None => { @@ -3447,7 +3437,9 @@ impl Compiler<'_> { }); // Match CPython behavior: If debug text is present, apply repr conversion. // See: https://github.com/python/cpython/blob/f61afca262d3a0aa6a8a501db0b1936c60858e35/Parser/action_helpers.c#L1456 - if conversion == ConversionFlag::None && debug_text_count > 0 { + if conversion == ConversionFlag::None + && fstring_expr.debug_text.is_some() + { conversion = ConversionFlag::Repr; } } @@ -3465,24 +3457,10 @@ impl Compiler<'_> { ConversionFlag::Repr => bytecode::ConversionFlag::Repr, }; emit!(self, Instruction::FormatValue { conversion }); - - // concatenate formatted string and debug text (if present) - if debug_text_count > 0 { - emit!( - self, - Instruction::BuildString { - size: debug_text_count + 1 - } - ); - } } } } - let element_count: u32 = fstring_elements - .len() - .try_into() - .expect("BuildString size overflowed"); if element_count == 0 { // ensure to put an empty string on the stack if there aren't any fstring elements self.emit_load_const(ConstantData::Str { diff --git a/compiler/codegen/src/lib.rs b/compiler/codegen/src/lib.rs index d44844543..90e11c5b8 100644 --- a/compiler/codegen/src/lib.rs +++ b/compiler/codegen/src/lib.rs @@ -13,6 +13,7 @@ pub mod error; pub mod ir; mod string_parser; pub mod symboltable; +mod unparse; pub use compile::CompileOpts; use ruff_python_ast::Expr; diff --git a/compiler/codegen/src/unparse.rs b/compiler/codegen/src/unparse.rs index ae4a2d161..458ff76fc 100644 --- a/compiler/codegen/src/unparse.rs +++ b/compiler/codegen/src/unparse.rs @@ -1,8 +1,13 @@ -use crate::{ - Arg, ArgWithDefault, Arguments, BoolOp, Comprehension, Constant, ConversionFlag, Expr, - Identifier, Operator, PythonArguments, +use ruff_python_ast as ruff; +use ruff_text_size::Ranged; +use rustpython_compiler_source::SourceCode; +use rustpython_literal::escape::{AsciiEscape, UnicodeEscape}; +use std::fmt::{self, Display as _}; + +use ruff::{ + Arguments, BoolOp, Comprehension, ConversionFlag, Expr, Identifier, Operator, Parameter, + ParameterWithDefault, Parameters, }; -use std::fmt; mod precedence { macro_rules! precedence { @@ -22,13 +27,13 @@ mod precedence { pub const EXPR: u8 = BOR; } -#[repr(transparent)] -struct Unparser<'a> { - f: fmt::Formatter<'a>, +struct Unparser<'a, 'b, 'c> { + f: &'b mut fmt::Formatter<'a>, + source: &'c SourceCode<'c>, } -impl<'a> Unparser<'a> { - fn new<'b>(f: &'b mut fmt::Formatter<'a>) -> &'b mut Unparser<'a> { - unsafe { &mut *(f as *mut fmt::Formatter<'a> as *mut Unparser<'a>) } +impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { + fn new(f: &'b mut fmt::Formatter<'a>, source: &'c SourceCode<'c>) -> Self { + Unparser { f, source } } fn p(&mut self, s: &str) -> fmt::Result { @@ -50,7 +55,7 @@ impl<'a> Unparser<'a> { self.f.write_fmt(f) } - fn unparse_expr(&mut self, ast: &Expr, level: u8) -> fmt::Result { + fn unparse_expr(&mut self, 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 { @@ -74,7 +79,7 @@ impl<'a> Unparser<'a> { }}; } match &ast { - Expr::BoolOp(crate::ExprBoolOp { + Expr::BoolOp(ruff::ExprBoolOp { op, values, range: _range, @@ -88,7 +93,7 @@ impl<'a> Unparser<'a> { } }) } - Expr::NamedExpr(crate::ExprNamedExpr { + Expr::Named(ruff::ExprNamed { target, value, range: _range, @@ -99,7 +104,7 @@ impl<'a> Unparser<'a> { self.unparse_expr(value, precedence::ATOM)?; }) } - Expr::BinOp(crate::ExprBinOp { + Expr::BinOp(ruff::ExprBinOp { left, op, right, @@ -130,7 +135,7 @@ impl<'a> Unparser<'a> { self.unparse_expr(right, prec + !right_associative as u8)?; }) } - Expr::UnaryOp(crate::ExprUnaryOp { + Expr::UnaryOp(ruff::ExprUnaryOp { op, operand, range: _range, @@ -138,7 +143,7 @@ impl<'a> Unparser<'a> { let (op, prec) = op_prec!( un, op, - crate::UnaryOp, + ruff::UnaryOp, Invert("~", FACTOR), Not("not ", NOT), UAdd("+", FACTOR), @@ -149,19 +154,22 @@ impl<'a> Unparser<'a> { self.unparse_expr(operand, prec)?; }) } - Expr::Lambda(crate::ExprLambda { - args, + Expr::Lambda(ruff::ExprLambda { + parameters, body, range: _range, }) => { group_if!(precedence::TEST, { - let pos = args.args.len() + args.posonlyargs.len(); - self.p(if pos > 0 { "lambda " } else { "lambda" })?; - self.unparse_arguments(args)?; - write!(self, ": {}", **body)?; + if let Some(parameters) = parameters { + self.p("lambda ")?; + self.unparse_arguments(parameters)?; + } else { + self.p("lambda")?; + } + write!(self, ": {}", unparse_expr(body, self.source))?; }) } - Expr::IfExp(crate::ExprIfExp { + Expr::If(ruff::ExprIf { test, body, orelse, @@ -175,29 +183,24 @@ impl<'a> Unparser<'a> { self.unparse_expr(orelse, precedence::TEST)?; }) } - Expr::Dict(crate::ExprDict { - keys, - values, + Expr::Dict(ruff::ExprDict { + items, range: _range, }) => { self.p("{")?; let mut first = true; - let (packed, unpacked) = values.split_at(keys.len()); - for (k, v) in keys.iter().zip(packed) { + for item in items { self.p_delim(&mut first, ", ")?; - if let Some(k) = k { - write!(self, "{}: {}", *k, *v)?; + if let Some(k) = &item.key { + write!(self, "{}: ", unparse_expr(k, self.source))?; } else { - write!(self, "**{}", *v)?; + self.p("**")?; } - } - for d in unpacked { - self.p_delim(&mut first, ", ")?; - write!(self, "**{}", *d)?; + self.unparse_expr(&item.value, level)?; } self.p("}")?; } - Expr::Set(crate::ExprSet { + Expr::Set(ruff::ExprSet { elts, range: _range, }) => { @@ -209,7 +212,7 @@ impl<'a> Unparser<'a> { } self.p("}")?; } - Expr::ListComp(crate::ExprListComp { + Expr::ListComp(ruff::ExprListComp { elt, generators, range: _range, @@ -219,7 +222,7 @@ impl<'a> Unparser<'a> { self.unparse_comp(generators)?; self.p("]")?; } - Expr::SetComp(crate::ExprSetComp { + Expr::SetComp(ruff::ExprSetComp { elt, generators, range: _range, @@ -229,7 +232,7 @@ impl<'a> Unparser<'a> { self.unparse_comp(generators)?; self.p("}")?; } - Expr::DictComp(crate::ExprDictComp { + Expr::DictComp(ruff::ExprDictComp { key, value, generators, @@ -242,7 +245,8 @@ impl<'a> Unparser<'a> { self.unparse_comp(generators)?; self.p("}")?; } - Expr::GeneratorExp(crate::ExprGeneratorExp { + Expr::Generator(ruff::ExprGenerator { + parenthesized: _, elt, generators, range: _range, @@ -252,7 +256,7 @@ impl<'a> Unparser<'a> { self.unparse_comp(generators)?; self.p(")")?; } - Expr::Await(crate::ExprAwait { + Expr::Await(ruff::ExprAwait { value, range: _range, }) => { @@ -261,23 +265,23 @@ impl<'a> Unparser<'a> { self.unparse_expr(value, precedence::ATOM)?; }) } - Expr::Yield(crate::ExprYield { + Expr::Yield(ruff::ExprYield { value, range: _range, }) => { if let Some(value) = value { - write!(self, "(yield {})", **value)?; + write!(self, "(yield {})", unparse_expr(value, self.source))?; } else { self.p("(yield)")?; } } - Expr::YieldFrom(crate::ExprYieldFrom { + Expr::YieldFrom(ruff::ExprYieldFrom { value, range: _range, }) => { - write!(self, "(yield from {})", **value)?; + write!(self, "(yield from {})", unparse_expr(value, self.source))?; } - Expr::Compare(crate::ExprCompare { + Expr::Compare(ruff::ExprCompare { left, ops, comparators, @@ -294,20 +298,22 @@ impl<'a> Unparser<'a> { } }) } - Expr::Call(crate::ExprCall { + Expr::Call(ruff::ExprCall { func, - args, - keywords, + arguments: Arguments { args, keywords, .. }, range: _range, }) => { self.unparse_expr(func, precedence::ATOM)?; self.p("(")?; if let ( - [Expr::GeneratorExp(crate::ExprGeneratorExp { - elt, - generators, - range: _range, - })], + [ + Expr::Generator(ruff::ExprGenerator { + elt, + generators, + range: _range, + .. + }), + ], [], ) = (&**args, &**keywords) { @@ -333,40 +339,46 @@ impl<'a> Unparser<'a> { } self.p(")")?; } - Expr::FormattedValue(crate::ExprFormattedValue { - value, - conversion, - format_spec, - range: _range, - }) => self.unparse_formatted(value, *conversion, format_spec.as_deref())?, - Expr::JoinedStr(crate::ExprJoinedStr { - values, - range: _range, - }) => self.unparse_joined_str(values, false)?, - Expr::Constant(crate::ExprConstant { - value, - kind, - range: _range, - }) => { - if let Some(kind) = kind { - self.p(kind)?; + Expr::FString(ruff::ExprFString { value, .. }) => self.unparse_fstring(value)?, + Expr::StringLiteral(ruff::ExprStringLiteral { value, .. }) => { + if value.is_unicode() { + self.p("u")? } - assert_eq!(f64::MAX_10_EXP, 308); + UnicodeEscape::new_repr(value.to_str().as_ref()) + .str_repr() + .fmt(self.f)? + } + Expr::BytesLiteral(ruff::ExprBytesLiteral { value, .. }) => { + AsciiEscape::new_repr(&value.bytes().collect::>()) + .bytes_repr() + .fmt(self.f)? + } + Expr::NumberLiteral(ruff::ExprNumberLiteral { value, .. }) => { + const { assert!(f64::MAX_10_EXP == 308) }; let inf_str = "1e309"; match value { - Constant::Float(f) if f.is_infinite() => self.p(inf_str)?, - Constant::Complex { real, imag } - if real.is_infinite() || imag.is_infinite() => - { - self.p(&value.to_string().replace("inf", inf_str))? + ruff::Number::Int(int) => int.fmt(self.f)?, + &ruff::Number::Float(fp) => { + if fp.is_infinite() { + self.p(inf_str)? + } else { + self.p(&rustpython_literal::float::to_string(fp))? + } } - _ => fmt::Display::fmt(value, &mut self.f)?, + &ruff::Number::Complex { real, imag } => self + .p(&rustpython_literal::float::complex_to_string(real, imag) + .replace("inf", inf_str))?, } } - Expr::Attribute(crate::ExprAttribute { value, attr, .. }) => { + Expr::BooleanLiteral(ruff::ExprBooleanLiteral { value, .. }) => { + self.p(if *value { "True" } else { "False" })? + } + Expr::NoneLiteral(ruff::ExprNoneLiteral { .. }) => self.p("None")?, + Expr::EllipsisLiteral(ruff::ExprEllipsisLiteral { .. }) => self.p("...")?, + Expr::Attribute(ruff::ExprAttribute { value, attr, .. }) => { self.unparse_expr(value, precedence::ATOM)?; - let period = if let Expr::Constant(crate::ExprConstant { - value: Constant::Int(_), + let period = if let Expr::NumberLiteral(ruff::ExprNumberLiteral { + value: ruff::Number::Int(_), .. }) = value.as_ref() { @@ -377,19 +389,19 @@ impl<'a> Unparser<'a> { self.p(period)?; self.p_id(attr)?; } - Expr::Subscript(crate::ExprSubscript { value, slice, .. }) => { + Expr::Subscript(ruff::ExprSubscript { value, slice, .. }) => { self.unparse_expr(value, precedence::ATOM)?; let lvl = precedence::TUPLE; self.p("[")?; self.unparse_expr(slice, lvl)?; self.p("]")?; } - Expr::Starred(crate::ExprStarred { value, .. }) => { + Expr::Starred(ruff::ExprStarred { value, .. }) => { self.p("*")?; self.unparse_expr(value, precedence::EXPR)?; } - Expr::Name(crate::ExprName { id, .. }) => self.p_id(id)?, - Expr::List(crate::ExprList { elts, .. }) => { + Expr::Name(ruff::ExprName { id, .. }) => self.p(id.as_str())?, + Expr::List(ruff::ExprList { elts, .. }) => { self.p("[")?; let mut first = true; for elt in elts { @@ -398,7 +410,7 @@ impl<'a> Unparser<'a> { } self.p("]")?; } - Expr::Tuple(crate::ExprTuple { elts, .. }) => { + Expr::Tuple(ruff::ExprTuple { elts, .. }) => { if elts.is_empty() { self.p("()")?; } else { @@ -412,7 +424,7 @@ impl<'a> Unparser<'a> { }) } } - Expr::Slice(crate::ExprSlice { + Expr::Slice(ruff::ExprSlice { lower, upper, step, @@ -430,11 +442,12 @@ impl<'a> Unparser<'a> { self.unparse_expr(step, precedence::TEST)?; } } + Expr::IpyEscapeCommand(_) => {} } Ok(()) } - fn unparse_arguments(&mut self, args: &Arguments) -> fmt::Result { + fn unparse_arguments(&mut self, args: &Parameters) -> fmt::Result { let mut first = true; for (i, arg) in args.posonlyargs.iter().chain(&args.args).enumerate() { self.p_delim(&mut first, ", ")?; @@ -459,63 +472,23 @@ impl<'a> Unparser<'a> { } Ok(()) } - fn unparse_function_arg(&mut self, arg: &ArgWithDefault) -> fmt::Result { - self.p_id(&arg.def.arg)?; - if let Some(ann) = &arg.def.annotation { - write!(self, ": {}", **ann)?; - } + fn unparse_function_arg(&mut self, arg: &ParameterWithDefault) -> fmt::Result { + self.unparse_arg(&arg.parameter)?; if let Some(default) = &arg.default { - write!(self, "={}", default)?; + write!(self, "={}", unparse_expr(default, self.source))?; } Ok(()) } - #[allow(dead_code)] - fn unparse_python_arguments(&mut self, args: &PythonArguments) -> fmt::Result { - let mut first = true; - let defaults_start = args.posonlyargs.len() + args.args.len() - args.defaults.len(); - for (i, arg) in args.posonlyargs.iter().chain(&args.args).enumerate() { - self.p_delim(&mut first, ", ")?; - self.unparse_arg(arg)?; - if let Some(i) = i.checked_sub(defaults_start) { - write!(self, "={}", &args.defaults[i])?; - } - 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)?; - } - let defaults_start = args.kwonlyargs.len() - args.kw_defaults.len(); - for (i, kwarg) in args.kwonlyargs.iter().enumerate() { - self.p_delim(&mut first, ", ")?; - self.unparse_arg(kwarg)?; - if let Some(default) = i - .checked_sub(defaults_start) - .and_then(|i| args.kw_defaults.get(i)) - { - write!(self, "={default}")?; - } - } - if let Some(kwarg) = &args.kwarg { - self.p_delim(&mut first, ", ")?; - self.p("**")?; - self.unparse_arg(kwarg)?; - } - Ok(()) - } - fn unparse_arg(&mut self, arg: &Arg) -> fmt::Result { - self.p_id(&arg.arg)?; + fn unparse_arg(&mut self, arg: &Parameter) -> fmt::Result { + self.p_id(&arg.name)?; if let Some(ann) = &arg.annotation { - write!(self, ": {}", **ann)?; + write!(self, ": {}", unparse_expr(ann, self.source))?; } Ok(()) } - fn unparse_comp(&mut self, generators: &[Comprehension]) -> fmt::Result { + fn unparse_comp(&mut self, generators: &[Comprehension]) -> fmt::Result { for comp in generators { self.p(if comp.is_async { " async for " @@ -533,20 +506,28 @@ impl<'a> Unparser<'a> { Ok(()) } - fn unparse_fstring_body(&mut self, values: &[Expr], is_spec: bool) -> fmt::Result { - for value in values { - self.unparse_fstring_elem(value, is_spec)?; + fn unparse_fstring_body(&mut self, elements: &[ruff::FStringElement]) -> fmt::Result { + for elem in elements { + self.unparse_fstring_elem(elem)?; } Ok(()) } - fn unparse_formatted( + fn unparse_formatted( &mut self, - val: &Expr, + val: &Expr, + debug_text: Option<&ruff::DebugText>, conversion: ConversionFlag, - spec: Option<&Expr>, + spec: Option<&ruff::FStringFormatSpec>, ) -> fmt::Result { - let buffered = to_string_fmt(|f| Unparser::new(f).unparse_expr(val, precedence::TEST + 1)); + let buffered = to_string_fmt(|f| { + Unparser::new(f, self.source).unparse_expr(val, precedence::TEST + 1) + }); + if let Some(ruff::DebugText { leading, trailing }) = debug_text { + self.p(leading)?; + self.p(self.source.get_range(val.range()))?; + self.p(trailing)?; + } let brace = if buffered.starts_with('{') { // put a space to avoid escaping the bracket "{ " @@ -566,7 +547,7 @@ impl<'a> Unparser<'a> { if let Some(spec) = spec { self.p(":")?; - self.unparse_fstring_elem(spec, true)?; + self.unparse_fstring_body(&spec.elements)?; } self.p("}")?; @@ -574,26 +555,23 @@ impl<'a> Unparser<'a> { Ok(()) } - fn unparse_fstring_elem(&mut self, expr: &Expr, is_spec: bool) -> fmt::Result { - match &expr { - Expr::Constant(crate::ExprConstant { value, .. }) => { - if let Constant::Str(s) = value { - self.unparse_fstring_str(s) - } else { - unreachable!() - } - } - Expr::JoinedStr(crate::ExprJoinedStr { - values, - range: _range, - }) => self.unparse_joined_str(values, is_spec), - Expr::FormattedValue(crate::ExprFormattedValue { - value, + fn unparse_fstring_elem(&mut self, elem: &ruff::FStringElement) -> fmt::Result { + match elem { + ruff::FStringElement::Expression(ruff::FStringExpressionElement { + expression, + debug_text, conversion, format_spec, - range: _range, - }) => self.unparse_formatted(value, *conversion, format_spec.as_deref()), - _ => unreachable!(), + .. + }) => self.unparse_formatted( + expression, + debug_text.as_ref(), + *conversion, + format_spec.as_deref(), + ), + ruff::FStringElement::Literal(ruff::FStringLiteralElement { value, .. }) => { + self.unparse_fstring_str(value) + } } } @@ -602,29 +580,42 @@ impl<'a> Unparser<'a> { self.p(&s) } - fn unparse_joined_str(&mut self, values: &[Expr], is_spec: bool) -> fmt::Result { - if is_spec { - self.unparse_fstring_body(values, is_spec) - } else { - self.p("f")?; - let body = to_string_fmt(|f| Unparser::new(f).unparse_fstring_body(values, is_spec)); - rustpython_literal::escape::UnicodeEscape::new_repr(&body) - .str_repr() - .write(&mut self.f) - } + fn unparse_fstring(&mut self, value: &ruff::FStringValue) -> fmt::Result { + self.p("f")?; + let body = to_string_fmt(|f| { + value.iter().try_for_each(|part| match part { + ruff::FStringPart::Literal(lit) => f.write_str(lit), + ruff::FStringPart::FString(ruff::FString { elements, .. }) => { + Unparser::new(f, self.source).unparse_fstring_body(elements) + } + }) + }); + // .unparse_fstring_body(elements)); + UnicodeEscape::new_repr(body.as_str().as_ref()) + .str_repr() + .write(self.f) } } -impl fmt::Display for Expr { +pub struct UnparseExpr<'a> { + expr: &'a Expr, + source: &'a SourceCode<'a>, +} + +pub fn unparse_expr<'a>(expr: &'a Expr, source: &'a SourceCode<'a>) -> UnparseExpr<'a> { + UnparseExpr { expr, source } +} + +impl fmt::Display for UnparseExpr<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - Unparser::new(f).unparse_expr(self, precedence::TEST) + Unparser::new(f, self.source).unparse_expr(self.expr, precedence::TEST) } } -fn to_string_fmt(f: impl FnOnce(&mut fmt::Formatter) -> fmt::Result) -> String { +fn to_string_fmt(f: impl FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result) -> String { use std::cell::Cell; struct Fmt(Cell>); - impl fmt::Result> fmt::Display for Fmt { + impl) -> fmt::Result> fmt::Display for Fmt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.take().unwrap()(f) } diff --git a/compiler/literal/src/float.rs b/compiler/literal/src/float.rs index a58a3f48a..e05a105fd 100644 --- a/compiler/literal/src/float.rs +++ b/compiler/literal/src/float.rs @@ -223,6 +223,39 @@ pub fn to_string(value: f64) -> String { } } +pub fn complex_to_string(re: f64, im: f64) -> String { + // integer => drop ., fractional => float_ops + let mut im_part = if im.fract() == 0.0 { + im.to_string() + } else { + to_string(im) + }; + im_part.push('j'); + + // positive empty => return im_part, integer => drop ., fractional => float_ops + let re_part = if re == 0.0 { + if re.is_sign_positive() { + return im_part; + } else { + re.to_string() + } + } else if re.fract() == 0.0 { + re.to_string() + } else { + to_string(re) + }; + let mut result = + String::with_capacity(re_part.len() + im_part.len() + 2 + im.is_sign_positive() as usize); + result.push('('); + result.push_str(&re_part); + if im.is_sign_positive() || im.is_nan() { + result.push('+'); + } + result.push_str(&im_part); + result.push(')'); + result +} + pub fn from_hex(s: &str) -> Option { if let Ok(f) = hexf_parse::parse_hexf64(s, false) { return Some(f); diff --git a/vm/src/builtins/complex.rs b/vm/src/builtins/complex.rs index 01dd65f51..a3a6d4d68 100644 --- a/vm/src/builtins/complex.rs +++ b/vm/src/builtins/complex.rs @@ -494,37 +494,7 @@ impl Representable for PyComplex { // TODO: when you fix this, move it to rustpython_common::complex::repr and update // ast/src/unparse.rs + impl Display for Constant in ast/src/constant.rs let Complex64 { re, im } = zelf.value; - // integer => drop ., fractional => float_ops - let mut im_part = if im.fract() == 0.0 { - im.to_string() - } else { - crate::literal::float::to_string(im) - }; - im_part.push('j'); - - // positive empty => return im_part, integer => drop ., fractional => float_ops - let re_part = if re == 0.0 { - if re.is_sign_positive() { - return Ok(im_part); - } else { - re.to_string() - } - } else if re.fract() == 0.0 { - re.to_string() - } else { - crate::literal::float::to_string(re) - }; - let mut result = String::with_capacity( - re_part.len() + im_part.len() + 2 + im.is_sign_positive() as usize, - ); - result.push('('); - result.push_str(&re_part); - if im.is_sign_positive() || im.is_nan() { - result.push('+'); - } - result.push_str(&im_part); - result.push(')'); - Ok(result) + Ok(rustpython_literal::float::complex_to_string(re, im)) } }