Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format ExprTuple #4963

Merged
merged 8 commits into from
Jun 12, 2023
Merged

Format ExprTuple #4963

merged 8 commits into from
Jun 12, 2023

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 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 <[email protected]>
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