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

quote annotations that already contain quotes #11811

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

Glyphack
Copy link
Contributor

@Glyphack Glyphack commented Jun 9, 2024

Summary

  • feat: quote annotations that contain quotes
  • test: add test case for auto quoting annotation with a quote

In the new logic if there's an annotation containing same quotes as the current quoting style the fix will replace the current quotes with the opposite of what quoting style is going to be used for quoting the whole annotation.
So this will depend on the stylist.quote() value to decide what character to replace.

If the annotation contain quotes of opposite style, then quoting it will not cause syntax errors.

Another option is escape the current quotes, but this might lead to conflict with other ruff rule Q0004.

Fixes: #9137

Test Plan

@Glyphack Glyphack force-pushed the auto-quote-annotation-quotes branch from 41e9c0f to d97e2bd Compare June 10, 2024 19:29
@Glyphack Glyphack force-pushed the auto-quote-annotation-quotes branch from 41064e7 to 54e4882 Compare June 10, 2024 19:35
@Glyphack Glyphack force-pushed the auto-quote-annotation-quotes branch from 54e4882 to 29e0d4a Compare June 10, 2024 19:40
Copy link

codspeed-hq bot commented Jun 10, 2024

CodSpeed Performance Report

Merging #11811 will not alter performance

Comparing Glyphack:auto-quote-annotation-quotes (5079a8c) with main (3b57faf)

Summary

✅ 32 untouched benchmarks

@Glyphack Glyphack marked this pull request as ready for review June 10, 2024 19:59
@Glyphack Glyphack force-pushed the auto-quote-annotation-quotes branch from b11a732 to 14f51e3 Compare June 11, 2024 17:39
@@ -221,7 +220,6 @@ pub(crate) fn is_singledispatch_implementation(
pub(crate) fn quote_annotation(
Copy link
Member

Choose a reason for hiding this comment

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

I'm sorry I didn't notice this earlier but looking at the documentation of quote_annotation, I don't know if this is the intended solution. I'm referring to the last two points in the examples which mentions the following conversion:

  1. Series["pd.Timestamp"] -> "Series[pd.Timestamp]"
  2. Series[Literal["pd.Timestamp"]] -> "Series[Literal['pd.Timestamp']]"

In (1), the inner quote isn't required while in (2) it is required. So, all in all, it's a little bit more complicated than just replacing the quote chars 😅

Copy link
Member

@dhruvmanila dhruvmanila Jun 12, 2024

Choose a reason for hiding this comment

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

I discussed this with @AlexWaygood and I think this requires a different solution. Basically, per https://typing.readthedocs.io/en/latest/spec/annotations.html#type-and-annotation-expressions and what Alex suggests in general is that:

Broadly speaking, any sub-element in the typing grammar that is marked as expression cannot have the quotes removed, but all other sub-elements (whether they're annotation expressions or type expressions, etc.), you should be able to remove the quotes from if a broader type/annotation expression that the sub-element is part of is quoted

So,

  • We shouldn't removes quotes from all parameters in Literal and all except the first parameter in Annotated
  • While, we can remove quotes from other types which is why in (1) the quotes around pd.Timestamp is removed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries, I also expected my initial implementation to be wrong. Just wanted to open the PR and see the difference in test cases. Let me go over this again but it will be in a few days I'll notify when it's ready.

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for understanding and take your time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dhruvmanila I tried to make some adjustment to make it work but I can use your help.

So now we can remove the inner quotes if there's any all the type except for when the inner quote is around values inside a Literal and specific parameters of Annotated.

To find out if the quotes are around those structures we need to take the expression and traverse down the AST and see if the quotes are at these specific places or not.
I'm thinking to have a function that does something similar to https://github.com/Glyphack/ruff/blob/14f51e3d9e96829deab91dd6e24cda5d0ec2fe36/crates/ruff_linter/src/rules/flake8_type_checking/helpers.rs#L229 and checks all children of that annotation expression to make sure the quotes inside are not belonging to those two types and we can safely remove them.

I'm thinking how can I limit the check here since traversing and checking all the node types and visiting the children might be a lot of code. For example do you think if we move this logic somewhere else it could help? Or maybe I'm missing something here.

An alternative way could be only checking if the annotation contains the work Literal or Annotated and then check based on characters in the string to determine if the quotes are there or not. I'm not entirely sure if this would work or not. Sounds very hacky but less code than checking node types of children.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried your way and I could implement something that solves the initial problem and the Litral and Annotated cases. What do you think about this?
Is there any edge cases that I'm not seeing in this scenario?

I am not checking all ast node types right now but will complete that if this is not missing any critical parts.

Copy link
Member

Choose a reason for hiding this comment

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

Perfect, I'll look at this today. Thanks for working on this!

Copy link
Member

Choose a reason for hiding this comment

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

I tried your way and I could implement something that solves the initial problem and the Litral and Annotated cases. What do you think about this?

I think the visitor pattern seems to be working well.

I am not checking all ast node types right now but will complete that if this is not missing any critical parts.

Can you give me an example of what do you mean here?

Copy link
Contributor Author

@Glyphack Glyphack Sep 11, 2024

Choose a reason for hiding this comment

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

Can you give me an example of what do you mean here?

I thought there are more expression enum values that I should handle here(not send everything to catch all), after checking them now I did not see any of them that require special attention like the subscript and tuple so I think it's fine.

I applied the comments but this is not ready for review yet please wait until I add more test cases.

Copy link
Member

Choose a reason for hiding this comment

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

I applied the comments but this is not ready for review yet please wait until I add more test cases.

No worries, feel free to ping me if you're stuck or when it's ready to review.

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.

Putting back in your queue according to Dhruv's comment

@Glyphack
Copy link
Contributor Author

Glyphack commented Aug 2, 2024

Thanks, I was away for a while I want to work on this tomorrow.

@Glyphack Glyphack force-pushed the auto-quote-annotation-quotes branch from 06462cb to e8c0f90 Compare August 23, 2024 22:58
Copy link
Contributor

github-actions bot commented Aug 23, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@Glyphack Glyphack force-pushed the auto-quote-annotation-quotes branch from 169add9 to 75f4407 Compare August 25, 2024 15:40
@dhruvmanila dhruvmanila self-assigned this Sep 2, 2024
@dhruvmanila dhruvmanila added the rule Implementing or modifying a lint rule label Sep 2, 2024
static MAX_ITERATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(10) };
static MAX_ITERATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(12) };
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason for changing this? Can you specify which test case required this?

Copy link
Contributor Author

@Glyphack Glyphack Sep 11, 2024

Choose a reason for hiding this comment

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

This test case fails after the new changes:

rules::flake8_type_checking::tests::quote::rule_typingonlythirdpartyimport_path_new_quote_py_expects

I initially did this before trying the visitor approach. I'm not sure if now it can be removed or not. I will look into it to make sure we are not flipping the quotes multiple times. Please let me know if you have a suggestion for figuring out if this is indication of a bug or it's okay to increase the number.

``` Failed to converge after 10 iterations. This likely indicates a bug in the implementation of the fix. Last diagnostics: quote.py:24:24: TCH002 [*] Move third-party import `pandas.DataFrame` into a type-checking block | 23 | def f(): 24 | from pandas import DataFrame | ^^^^^^^^^ TCH002 25 | 26 | def baz() -> DataFrame[int]: | = help: Move into type-checking block

ℹ Unsafe fix
2 2 |
3 3 | if TYPE_CHECKING:
4 4 | from pandas import DataFrame
5 |+ from pandas import DataFrame
5 6 | import pandas as pd
6 7 | import pandas as pd
7 8 | import pandas as pd

21 22 |
22 23 |
23 24 | def f():
24 |- from pandas import DataFrame
25 25 |
26 |- def baz() -> DataFrame[int]:
26 |+ def baz() -> "DataFrame[int]":
27 27 | ...
28 28 |
29 29 |

quote.py:17:24: TCH002 [*] Move third-party import pandas.DataFrame into a type-checking block
|
16 | def f():
17 | from pandas import DataFrame
| ^^^^^^^^^ TCH002
18 |
19 | def baz() -> DataFrame:
|
= help: Move into type-checking block

ℹ Unsafe fix
2 2 |
3 3 | if TYPE_CHECKING:
4 4 | from pandas import DataFrame
5 |+ from pandas import DataFrame
5 6 | import pandas as pd
6 7 | import pandas as pd
7 8 | import pandas as pd

14 15 |
15 16 |
16 17 | def f():
17 |- from pandas import DataFrame
18 18 |
19 |- def baz() -> DataFrame:
19 |+ def baz() -> "DataFrame":
20 20 | ...
21 21 |
22 22 |

</details>

Copy link
Member

Choose a reason for hiding this comment

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

I just ran it on your branch with the original value and it works. I think it can be reverted back.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dhruvmanila
Copy link
Member

Thanks for working on this. Overall, the approach looks good. I'll give it a detailed review once you think it's ready. I'd also recommend adding test cases with mixed quote style i.e., scenarios where we might not be able to provide a fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rule Implementing or modifying a lint rule
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for auto-quoting when annotations contain quotes
3 participants