diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 970c30f..532a9b9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,6 +3,7 @@ name: Upload to crates.io on: release: types: [published] + workflow_dispatch: env: CARGO_TERM_COLOR: always diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b83677..82e5f4f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,11 +9,11 @@ repos: - id: cargo-fix - id: cargo-fmt - repo: https://github.com/jvllmr/pre-commit-prettier - rev: v3.4.2 + rev: v3.5.3 hooks: - id: prettier - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.9.4" + rev: "v0.9.9" hooks: - id: ruff args: diff --git a/Cargo.lock b/Cargo.lock index 53e06b7..72f5b51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -484,7 +484,7 @@ dependencies = [ [[package]] name = "rustpython-unparser" -version = "0.2.1" +version = "0.2.4" dependencies = [ "pretty_assertions", "rand", diff --git a/Cargo.toml b/Cargo.toml index aeb6d90..13d986f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpython-unparser" -version = "0.2.1" +version = "0.2.4" edition = "2021" authors = ["Jan Vollmer "] readme = "README.md" @@ -13,7 +13,7 @@ rust-version = "1.72.1" transformer = [] [dependencies] -rustpython-ast = { version = ">=0.4.0" } +rustpython-ast = { version = ">=0.4.0", features = ["rustpython-literal"] } rustpython-literal = ">=0.4.0" [dev-dependencies] diff --git a/src/unparser.rs b/src/unparser.rs index 8b6368f..b57681c 100644 --- a/src/unparser.rs +++ b/src/unparser.rs @@ -789,11 +789,15 @@ impl Unparser { fn unparse_expr_if_exp(&mut self, node: &ExprIfExp) { let enum_member = Expr::IfExp(node.to_owned()); self.delimit_precedence(&enum_member, |block_self| { - block_self.unparse_expr(&node.body); - block_self.write_str(" if "); - block_self.unparse_expr(&node.test); - block_self.write_str(" else "); - block_self.unparse_expr(&node.orelse); + block_self.with_precedence_num(Precedence::Test.value() + 1, |prec_self| { + prec_self.unparse_expr(&node.body); + prec_self.write_str(" if "); + prec_self.unparse_expr(&node.test); + }); + block_self.with_precedence(Precedence::Test, |prec_self| { + prec_self.write_str(" else "); + prec_self.unparse_expr(&node.orelse); + }); }) } @@ -811,7 +815,10 @@ impl Unparser { self.write_str("**"); } } - self.unparse_expr(value); + self.with_precedence_num(EXPR_PRECEDENCE, |prec_self| { + prec_self.unparse_expr(value); + }); + if zipped.peek().is_some() { self.write_str(", "); } @@ -1057,6 +1064,7 @@ impl Unparser { } fn _unparse_constant(&mut self, constant: &Constant) { + let inf_str = "1e309"; return match constant { Constant::Tuple(values) => { self.write_str("("); @@ -1094,8 +1102,37 @@ impl Unparser { self.write_str(&escaped); } Constant::None => self.write_str("None"), - Constant::Complex { real, imag: _ } => self.write_str(&real.to_string()), - Constant::Float(value) => self.write_str(&value.to_string()), + Constant::Complex { real, imag } => { + if real.is_infinite() || imag.is_infinite() { + self.write_str(&constant.to_string().replace("inf", &inf_str)); + } else { + self.write_str(&constant.to_string()); + } + } + Constant::Float(value) => { + if value.is_infinite() { + self.write_str(inf_str); + } else { + let mut str_value = value.to_string(); + if value.fract() == 0.0 { + let mut trailing_zeroes = 0; + while str_value.ends_with("0") { + str_value.pop(); + trailing_zeroes += 1; + } + str_value = format!("{}e{}", str_value, trailing_zeroes); + } else if str_value.starts_with(&format!("0.{}", "0".repeat(5))) { + let mut trimmed = str_value[2..].to_owned(); + let mut factor = 1; + while trimmed.starts_with("0") { + trimmed = trimmed[1..].to_owned(); + factor += 1; + } + str_value = format!("{}e-{}", trimmed, factor); + } + self.write_str(&str_value); + } + } }; } @@ -1112,7 +1149,9 @@ impl Unparser { self.write_str(&node.attr); } fn unparse_expr_subscript(&mut self, node: &ExprSubscript) { - self.unparse_expr(&node.value); + self.with_precedence(Precedence::Atom, |prec_self| { + prec_self.unparse_expr(&node.value); + }); self.write_str("["); self.unparse_expr(&node.slice); self.write_str("]"); @@ -1141,14 +1180,21 @@ impl Unparser { fn unparse_expr_tuple(&mut self, node: &ExprTuple) { let mut elts_iter = node.elts.iter().peekable(); - self.write_str("("); + let should_delimit = + node.elts.len() == 0 || self.precedence_level > Precedence::Tuple.value(); + if should_delimit { + self.write_str("("); + } + while let Some(expr) = elts_iter.next() { self.unparse_expr(expr); if elts_iter.peek().is_some() || node.elts.len() == 1 { self.write_str(", "); } } - self.write_str(")"); + if should_delimit { + self.write_str(")"); + } } fn unparse_expr_slice(&mut self, node: &ExprSlice) { @@ -1189,13 +1235,19 @@ impl Unparser { } else { self.write_str(" for "); } - self.unparse_expr(&node.target); + self.with_precedence(Precedence::Tuple, |prec_self| { + prec_self.unparse_expr(&node.target); + }); + self.write_str(" in "); - self.unparse_expr(&node.iter); - for if_ in &node.ifs { - self.write_str(" if "); - self.unparse_expr(if_); - } + + self.with_precedence_num(Precedence::Test.value() + 1, |prec_self| { + prec_self.unparse_expr(&node.iter); + for if_ in &node.ifs { + prec_self.write_str(" if "); + prec_self.unparse_expr(if_); + } + }); } fn unparse_excepthandler(&mut self, node: &ExceptHandler) { diff --git a/test_files/constants.py b/test_files/constants.py index e0f67f4..95ef7c2 100644 --- a/test_files/constants.py +++ b/test_files/constants.py @@ -1,3 +1,17 @@ escaped_string_expr = "'\"'''\"\"\"{}\\" escaped_unicode_expr = u"'\"'''\"\"\"{}\\" # fmt: skip escaped_bytes_expr = b"'\"'''\"\"\"{}\\" + + +# real code from yaml.constructor +# https://github.com/yaml/pyyaml/blob/69c141adcf805c5ebdc9ba519927642ee5c7f639/lib/yaml/constructor.py#L265 +inf_value = 1e300 + +# if inf_value would not be initialized in scientific notation +# the following loop would run for a long time +while inf_value != inf_value * inf_value: + inf_value *= inf_value + +scientific_fraction = 1e-69 + +complex_number = 3 + 4j diff --git a/test_files/precedence.py b/test_files/precedence.py new file mode 100644 index 0000000..b1ad95a --- /dev/null +++ b/test_files/precedence.py @@ -0,0 +1,20 @@ +import dataclasses + +reduce = "abcdefhijklmnopqrstuvwxyz" +reduce_list = (list(reduce) + [None] * 5)[:5] + + +@dataclasses.dataclass +class MyDataclass: + name: str + + +# from pydantic._internal._fields +dataclass_fields = { + field.name + for field in ( + dataclasses.fields(MyDataclass) if dataclasses.is_dataclass(MyDataclass) else () + ) +} + +a, b, c = "a", "b", "c"