Skip to content

Commit af4f883

Browse files
committed
improve f-string unparsing
1 parent 44ca88a commit af4f883

4 files changed

Lines changed: 118 additions & 16 deletions

File tree

Cargo.lock

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
rustpython-ast = { version = "0.4.0" }
8+
rustpython-literal = "0.4.0"
89

910
[dev-dependencies]
1011
rustpython-parser = "0.4.0"

src/unparser.rs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::ops::Deref;
2+
13
use rustpython_ast::{
24
text_size::TextRange, Alias, Arg, Arguments, BoolOp, CmpOp, Comprehension, ExceptHandler,
35
ExceptHandlerExceptHandler, Expr, ExprAttribute, ExprAwait, ExprBinOp, ExprBoolOp, ExprCall,
@@ -13,7 +15,7 @@ use rustpython_ast::{
1315
StmtWhile, StmtWith, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
1416
UnaryOp, WithItem,
1517
};
16-
use rustpython_ast::{Constant, Int};
18+
use rustpython_ast::{Constant, ConversionFlag, Int};
1719

1820
enum Precedence {
1921
NamedExpr = 1,
@@ -42,6 +44,8 @@ impl Precedence {
4244
self as usize
4345
}
4446
}
47+
48+
#[allow(dead_code)]
4549
const EXPR_PRECEDENCE: usize = 9;
4650

4751
fn get_precedence(node: &Expr<TextRange>) -> usize {
@@ -686,7 +690,7 @@ impl Unparser {
686690
Expr::Compare(data) => self.unparse_expr_compare(data),
687691
Expr::Call(data) => self.unparse_expr_call(data),
688692
Expr::FormattedValue(data) => self.unparse_expr_formatted_value(data),
689-
Expr::JoinedStr(data) => self.unparse_expr_joined_str(data),
693+
Expr::JoinedStr(data) => self.unparse_expr_joined_str(data, false),
690694
Expr::Constant(data) => self.unparse_expr_constant(data),
691695
Expr::Attribute(data) => self.unparse_expr_attribute(data),
692696
Expr::Subscript(data) => self.unparse_expr_subscript(data),
@@ -944,31 +948,57 @@ impl Unparser {
944948
self.write_str(" ");
945949
}
946950
self.write_str(inner_expr);
947-
951+
if node.conversion != ConversionFlag::None {
952+
self.write_str("!");
953+
let buf = &[node.conversion as u8];
954+
let c = std::str::from_utf8(buf).unwrap();
955+
self.write_str(c);
956+
}
948957
if let Some(format_spec) = &node.format_spec {
949958
self.write_str(":");
950-
self.unparse_expr(format_spec);
959+
match format_spec.deref() {
960+
Expr::JoinedStr(joined_str) => {
961+
if joined_str.values.len() > 0 {
962+
self.unparse_expr_joined_str(joined_str, true);
963+
}
964+
}
965+
_ => self.unparse_expr(&format_spec),
966+
};
951967
}
952968
self.write_str("}");
953969
}
954970

955-
fn unparse_expr_joined_str(&mut self, node: &ExprJoinedStr<TextRange>) {
956-
self.write_str("f");
957-
if node.values.len() == 0 {
958-
self.write_str("\"\"");
971+
fn unparse_expr_joined_str(&mut self, node: &ExprJoinedStr<TextRange>, is_spec: bool) {
972+
if !is_spec {
973+
self.write_str("f");
959974
}
960-
for (index, expr) in node.values.iter().enumerate() {
975+
let mut expr_source = String::new();
976+
for expr in node.values.iter() {
961977
let mut inner_unparser = Unparser::new();
962-
inner_unparser.unparse_expr(expr);
963-
let mut expr_source = inner_unparser.source.as_str();
964-
if index > 0 {
965-
expr_source = expr_source.trim_start_matches("\"")
978+
match expr {
979+
Expr::Constant(ExprConstant { value, .. }) => {
980+
if let Constant::Str(str_) = value {
981+
inner_unparser.write_str(str_);
982+
} else {
983+
unreachable!()
984+
}
985+
}
986+
_ => {
987+
inner_unparser.unparse_expr(expr);
988+
}
966989
}
967990

968-
if index != node.values.len() - 1 {
969-
expr_source = expr_source.trim_end_matches("\"")
970-
}
991+
expr_source += inner_unparser.source.as_str();
992+
}
993+
994+
if is_spec {
971995
self.write_str(&expr_source);
996+
} else {
997+
let escaped_source = rustpython_literal::escape::UnicodeEscape::new_repr(&expr_source)
998+
.str_repr()
999+
.to_string()
1000+
.unwrap();
1001+
self.write_str(&escaped_source);
9721002
}
9731003
}
9741004

test_files/simple_f_string.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
world = "World"
22
empty_f_string = f"" # noqa: F541
3+
f"{world!a:}None{empty_f_string!s:}None"
4+
answer = 42.000001
5+
f"{answer:.03f}"
36
if __name__ == "__main__":
47
print(f"Hello {world}!")

0 commit comments

Comments
 (0)