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

Formatter fails on string interpolation #6565

Closed
newtonapple opened this issue Aug 19, 2018 · 9 comments
Closed

Formatter fails on string interpolation #6565

newtonapple opened this issue Aug 19, 2018 · 9 comments

Comments

@newtonapple
Copy link

A simple running Crystal program (bug.cr):

echo 'puts "#{"1"}"' > bug.cr

cat bug.cr
# => puts "#{"1"}"

crystal run bug.cr
# => 1

However, the formatter will fail when it tries to format bug.cr:

crystal tool format bug.cr

Error: couldn't format 'bug.cr', please report a bug including the contents of it: https://github.com/crystal-lang/crystal/issues

expecting }, not `NUMBER, 1`, at :1:10 (Exception)
  from Crystal::Formatter#check<Symbol>:Nil
  from Crystal::Formatter#visit<Crystal::StringLiteral>:Bool
  from Crystal::ASTNode+@Crystal::ASTNode#accept<Crystal::Formatter>:(Bool | Nil)
  from Crystal::Formatter#format_args_simple<Array(Crystal::ASTNode+), Int32, Bool>:Tuple(Bool, Bool, Int32)
  from Crystal::Formatter#format_args<Array(Crystal::ASTNode+), Bool, (Array(Crystal::NamedArgument) | Nil), (Crystal::ASTNode+ | Nil), Int32, Bool>:Tuple(Bool | Nil, Bool, Int32)
  from Crystal::Formatter#visit<Crystal::Call>:Bool
  from Crystal::ASTNode+@Crystal::ASTNode#accept<Crystal::Formatter>:(Bool | Nil)
  from Crystal::format<String, String>:String
  from Crystal::Command#tool:(Bool | Nil)
  from Crystal::Command#run:(Bool | Crystal::Compiler::Result | Nil)
  from main

Crystal version:

Crystal 0.26.0 (2018-08-17)

LLVM: 6.0.1
Default target: x86_64-apple-macosx
@makenowjust
Copy link
Contributor

"#{"1"}" is reduced to simple string literal "1" internally by the compiler. Unfortunately, the formatter does not support this.
I'll try to fix it.

@asterite
Copy link
Member

@makenowjust It's ok if you try and succeed. However, I don't think it's really important to fix this, there's no point in interpolating a string literal. This is just an edge-case.

@makenowjust
Copy link
Contributor

@asterite I understand what you said as that you must not add a complex change to fix an edge-case. I'd keep in mind. Thank you.

@newtonapple
Copy link
Author

Sure, this is definitely a corner case. It's one of those things that could really trip people up and give them a bad experience for the language. I ran into the problem because I was writing some test code that would be refactored later. The interpolated string was just a placeholder that would be substituted out later. And, having the formatter blowing up or not formatting code on a perfectly valid syntax is just not a very good experience. It took me a good 15-20 minutes to track down the problem. I agree that this is probably not the highest priority bug, but it probably should be fixed at some point.

@asterite
Copy link
Member

We should probably fix this by keeping the interpolation instead of just generating a string literal. I'll fix it later today.

makenowjust added a commit to makenowjust/crystal that referenced this issue Aug 19, 2018
…ions

Fixed crystal-lang#6565

Currently `"foo#{"bar"}baz"` is parsed as `StringLiteral` with joining
all parts.
It is bad for formatting, so this changes it is parsed as `StringInterpolation`.
Above example's `expressions` property is now `["foo", "bar", "baz"]`.

In addition, this changes `StringInterpolation` ensure to interleave
string literals with interpolations.
In other words, it ensures `expressions` odd-index elements are
string literals and even-index elements are interpolations.
For example `"#{123}"` is parsed as `StringInterpolation` and its
`expressions` property is `["", 123, ""]`.
It is good for formatting.

Other changes in this are for keeping back-compatibility and formatter
improvement.
makenowjust added a commit to makenowjust/crystal that referenced this issue Aug 19, 2018
@makenowjust
Copy link
Contributor

makenowjust commented Aug 19, 2018

I implemented two solutions for this:

  1. makenowjust@dacbdbd: modify parser to keep interpolation and fix formatter and literal expander.
  2. makenowjust@adbf845: fix formatter

The first diff has 101 additions and 74 deletions, but the second diff has 6 additions and 4 deletions.
The second fix is very simpler than the first.

Modifying parser to keep interpolation affects in many places unfortunately, for example macro expansion.
And I'm not sure this implementation is perfect.

@asterite I guess you would fix like the first, however I can't think it is better than the simple solution.

Additional comment by implementer: this is just formatter bug, not related to parser. I'm sure it cannot be fixed without formatter fix, even if the parser keeps interpolations.

We can fix parser to keep interpolation after fixed this. I prefer to apply simple and perfect solution first, then consider to merge complex solution after.

@asterite
Copy link
Member

@makenowjust thank you! I vote to keep the simpler fix. Does it work with a string interpolation that has many string literals interpolated, with string content too? For example "foo#{"bar"} Baz #{"qux"} "

@straight-shoota
Copy link
Member

I don't think it's really important to fix this, there's no point in interpolating a string literal.

I strongly disagree. It is valid code and the formatter should support all valid code.

@makenowjust
Copy link
Contributor

@asterite Of course, yes. I'll add this to spec then open pull request.

RX14 pushed a commit that referenced this issue Aug 20, 2018
* Format: fix formatting StringLiteral in StringInterpolation

Fixed #6565

* Add more complex specs
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

No branches or pull requests

4 participants