psf / black

The uncompromising Python code formatter
https://black.readthedocs.io/en/stable/
MIT License
38.85k stars 2.45k forks source link

string-processing: F-string with triple quoted string containing single quotes #4494

Open MichaReiser opened 3 days ago

MichaReiser commented 3 days ago

Describe the bug

Sorry for another cursed example.

Black changes the " in the second inner f-string expression to ' which changes the program output.

f"{'''test ' '''}" f'{"""other " """}'

Actual output

f"{'''test ' '''}{'''other ' '''}"
#                          ^ note the changed quote 

To Reproduce

Create a file with the above code and run with --unstable with a pre python 312 target.

Or see this playground

Expected behavior

JelleZijlstra commented 3 days ago

At least I don't see any quote selection that results in valid pre python312 code.

This would work: f"""{'''test ' '''}{'''other " '''}""". Probably better for Black to just leave the string alone, though.

MichaReiser commented 3 days ago

At least I don't see any quote selection that results in valid pre python312 code.

This would work: f"""{'''test ' '''}{'''other " '''}""". Probably better for Black to just leave the string alone, though.

True. To be more specific. I don't see a way without changing from single to triple quotes :)

MeGaGiGaGon commented 3 hours ago

While trying to fix #4495, I found this is actually doesn't need triple quotes to happen, since the quote flipping logic itself is flawed.

"" f'{"'"}' # output: Cannot parse for target version Python 3.13: 1:3: EOF in multi-line string
"" f'{"' "}' # output: Cannot parse for target version Python 3.13: 1:5: f"{'' '}"

This happens because _toggle_fexpr_quotes does a replace, instead of considering if any strings in the expr have the other quote type.

MeGaGiGaGon commented 2 hours ago

Also in this same area of _toggle_fexpr_quotes being broken, playground link, formatting "" f'{1:""}' causes an "inequivalent to source code" error since it changes the :"" to :'', which is correct since it is observable if the thing being formatted has a custom __format__.