Skip to content

Format ExprTuple#4963

Merged
konstin merged 8 commits intomainfrom
format-expr-tupl-try-2
Jun 12, 2023
Merged

Format ExprTuple#4963
konstin merged 8 commits intomainfrom
format-expr-tupl-try-2

Conversation

@konstin
Copy link
Member

@konstin konstin commented Jun 8, 2023

This implements formatting ExprTuple, including magic trailing comma. I intentionally didn't change the settings mechanism but just added a dummy global const flag.

Besides the snapshots, I added custom breaking/joining tests and a deeply nested test case. The diffs look better than previously, proper black compatibility depends on parentheses handling.

@konstin
Copy link
Member Author

konstin commented Jun 8, 2023

Current dependencies on/for this PR:

This comment was auto-generated by Graphite.

}

/// Check if a tuple has already had parentheses in the input
fn is_parenthesized(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this would be useful anywhere else

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to know that it exists. We can pull it out if it is used elsewhere.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 8, 2023

PR Check Results

Ecosystem

✅ ecosystem check detected no changes.

Benchmark

Linux

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.00      6.7±0.30ms     6.1 MB/sec    1.04      6.9±0.31ms     5.9 MB/sec
formatter/numpy/ctypeslib.py               1.00  1440.3±81.39µs    11.6 MB/sec    1.01  1451.6±84.92µs    11.5 MB/sec
formatter/numpy/globals.py                 1.06   145.2±11.10µs    20.3 MB/sec    1.00    137.5±7.88µs    21.5 MB/sec
formatter/pydantic/types.py                1.01      2.8±0.20ms     9.0 MB/sec    1.00      2.8±0.19ms     9.1 MB/sec
linter/all-rules/large/dataset.py          1.02     15.4±0.59ms     2.6 MB/sec    1.00     15.1±0.56ms     2.7 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.00      3.6±0.15ms     4.6 MB/sec    1.00      3.6±0.16ms     4.6 MB/sec
linter/all-rules/numpy/globals.py          1.11   489.9±39.21µs     6.0 MB/sec    1.00   442.0±19.43µs     6.7 MB/sec
linter/all-rules/pydantic/types.py         1.05      6.6±0.28ms     3.9 MB/sec    1.00      6.3±0.37ms     4.0 MB/sec
linter/default-rules/large/dataset.py      1.08      7.9±0.30ms     5.2 MB/sec    1.00      7.3±0.30ms     5.6 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.13  1691.7±95.99µs     9.8 MB/sec    1.00  1503.3±68.99µs    11.1 MB/sec
linter/default-rules/numpy/globals.py      1.06   189.2±11.69µs    15.6 MB/sec    1.00   177.8±10.22µs    16.6 MB/sec
linter/default-rules/pydantic/types.py     1.11      3.5±0.15ms     7.2 MB/sec    1.00      3.2±0.13ms     8.0 MB/sec

Windows

group                                      main                                    pr
-----                                      ----                                    --
formatter/large/dataset.py                 1.02      9.8±0.62ms     4.2 MB/sec     1.00      9.6±0.51ms     4.2 MB/sec
formatter/numpy/ctypeslib.py               1.00  1956.9±114.95µs     8.5 MB/sec    1.01  1971.6±180.69µs     8.4 MB/sec
formatter/numpy/globals.py                 1.00   194.8±15.50µs    15.1 MB/sec     1.03   201.3±16.65µs    14.7 MB/sec
formatter/pydantic/types.py                1.03      4.1±0.21ms     6.3 MB/sec     1.00      3.9±0.21ms     6.5 MB/sec
linter/all-rules/large/dataset.py          1.00     20.5±0.73ms  2036.9 KB/sec     1.00     20.5±0.88ms  2028.7 KB/sec
linter/all-rules/numpy/ctypeslib.py        1.00      5.2±0.23ms     3.2 MB/sec     1.00      5.1±0.28ms     3.2 MB/sec
linter/all-rules/numpy/globals.py          1.00   612.9±39.56µs     4.8 MB/sec     1.03   628.4±44.22µs     4.7 MB/sec
linter/all-rules/pydantic/types.py         1.00      8.6±0.46ms     3.0 MB/sec     1.01      8.7±0.46ms     2.9 MB/sec
linter/default-rules/large/dataset.py      1.05     10.6±0.49ms     3.8 MB/sec     1.00     10.0±0.45ms     4.1 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.00      2.1±0.10ms     7.9 MB/sec     1.03      2.2±0.12ms     7.6 MB/sec
linter/default-rules/numpy/globals.py      1.02   253.9±19.17µs    11.6 MB/sec     1.00   249.2±15.07µs    11.8 MB/sec
linter/default-rules/pydantic/types.py     1.00      4.6±0.28ms     5.5 MB/sec     1.00      4.6±0.38ms     5.5 MB/sec

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Putting it back into your queue to add some tests around comment handling.

// Handle the edge cases of an empty tuple and a tuple with one element
let last = match &elts[..] {
[] => {
return text("()").fmt(f);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for an empty tuple that contains a comment:

(
	# empty
)

You'll need to handle that case manually by:

  • overriding fmt_dangling_comments on FormatNodeRule
  • Use block_indent(&dangling_node_comments(item.into()) here to format the dangling comments proper

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't realize earlier, but it's neat that block_ident is a noop when empty

[group(&format_args![
// A single element tuple always needs parentheses
&text("("),
soft_block_indent(&group(&format_args![single.format(), &text(",")])),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the inner group?


let saved_level = f.context().node_level();
// Tell the children they need parentheses
f.context_mut().set_node_level(NodeLevel::Expression);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this not already always be true? Because the Expr formatting sets the level to NodeLevel::Expression

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, forgot to update that code

Comment on lines +62 to +69
[group(&format_args![
// An expanded group always needs parentheses
&text("("),
hard_line_break(),
block_indent(&ExprSequence::new(elts)),
hard_line_break(),
&text(")"),
])]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The group here is useless because you'll always force it to expand by writing the block_indent and the hard_line_break.

Comment on lines +116 to +124
if pos == self.elts.len() - 1 {
write!(
f,
[
entry.format(),
if_group_breaks(&text(",")),
soft_line_break()
]
)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You can simplify this by always writing the trailing comma after having written all elements.

Comment on lines +113 to +127
for (pos, entry) in self.elts.iter().enumerate() {
// We need a trailing comma on the last entry of an expanded group since we have more
// than one element
if pos == self.elts.len() - 1 {
write!(
f,
[
entry.format(),
if_group_breaks(&text(",")),
soft_line_break()
]
)?;
} else {
write!(f, [entry.format(), text(","), soft_line_break_or_space()])?;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simplify this by using joiner:

Suggested change
for (pos, entry) in self.elts.iter().enumerate() {
// We need a trailing comma on the last entry of an expanded group since we have more
// than one element
if pos == self.elts.len() - 1 {
write!(
f,
[
entry.format(),
if_group_breaks(&text(",")),
soft_line_break()
]
)?;
} else {
write!(f, [entry.format(), text(","), soft_line_break_or_space()])?;
}
let separator = format_with(|f| write!(f[text(","), soft_line_break_or_space()]));
f.join_with(separator).entries(self.elts.iter().formatted()).finish()?;
// Write the trailing comma
write!(f, [if_group_breaks(&text(","))])?;

}

/// Check if a tuple has already had parentheses in the input
fn is_parenthesized(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to know that it exists. We can pull it out if it is used elsewhere.


/// Check if a tuple has already had parentheses in the input
fn is_parenthesized(
range: TextRange,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
range: TextRange,
tuple_range: TextRange,

I had to look it up on the call side to know what range this is.It could even make sense to pass the whole tuple

f: &mut Formatter<PyFormatContext<'_>>,
) -> bool {
let parentheses = "(";
let first_char = &f.context().contents()[TextRange::at(range.start(), parentheses.text_len())];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this panics if the first character is a non-ascii character.

It's probably safe to use `&f.context().contents(usize::from(range.start())..].chars().next()

konstin added 3 commits June 9, 2023 11:14
This implements formatting ExprTuple, including magic trailing comma. I intentionally didn't change the settings mechanism but just added a dummy global const flag.

Besides the snapshots, I added custom breaking/joining tests and a deeply nested test case. The diffs look better than previously, proper black compatibility depends on parentheses handling.
@konstin konstin force-pushed the format-expr-tupl-try-2 branch from 4dd26d6 to 0613004 Compare June 9, 2023 10:49
Comment on lines +35 to +40
[group(&format_args![
// A single element tuple always needs parentheses
&text("("),
block_indent(&dangling_node_comments(item)),
&text(")"),
])]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary to use a group here because the block_indent emits a hard_line_break that always forces the group to expand

konstin and others added 3 commits June 9, 2023 13:34
Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Micha Reiser <micha@reiser.io>
@konstin konstin mentioned this pull request Jun 12, 2023
72 tasks
@konstin konstin enabled auto-merge (squash) June 12, 2023 12:46
@konstin konstin merged commit e586c27 into main Jun 12, 2023
@konstin konstin deleted the format-expr-tupl-try-2 branch June 12, 2023 12:55
konstin added a commit that referenced this pull request Jun 13, 2023
This implements formatting ExprTuple, including magic trailing comma. I
intentionally didn't change the settings mechanism but just added a
dummy global const flag.

Besides the snapshots, I added custom breaking/joining tests and a
deeply nested test case. The diffs look better than previously, proper
black compatibility depends on parentheses handling.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants