scalameta / scalafmt

Code formatter for Scala
http://scalameta.org/scalafmt
Apache License 2.0
1.44k stars 276 forks source link

Scalafmt in scala 3 dialect adds a new line after a `'` expression which changes behaviour of program #3824

Closed mdedetrich closed 8 months ago

mdedetrich commented 8 months ago

Configuration (required)

Please paste the contents of your .scalafmt.conf file here:

version = 3.8.0
runner.dialect = scala212source3
maxColumn = 120
project.git = true

# http://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style.
# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala
docstrings.style = Asterisk
literals.hexDigits = upper
project.layout = StandardConvention

Command-line parameters (required)

When I run scalafmt via CLI like this: scalafmt

Steps

Given code like this:

val g = '{ ((r: R) => $upm.fromProduct(r.asInstanceOf[Product])).asInstanceOf[Any => Any] }
(f, g, elemTpes)

Problem

Scalafmt formats code like this:

val g = '{ ((r: R) => $upm.fromProduct(r.asInstanceOf[Product])).asInstanceOf[Any => Any] }(f, g, elemTpes)

Expectation

I would like the formatted output to look like this:

val g = '{ ((r: R) => $upm.fromProduct(r.asInstanceOf[Product])).asInstanceOf[Any => Any] }
(f, g, elemTpes)

(i.e. unchanged)

Note that the original and formatted expressions are not the same, scalafmt removing the newline changes the behaviour of the program so that it no longer compiles

Workaround

// format: on and // format: off to disable the formatting on this block

Notes

Happens with the scala 3 dialect (project.layout = StandardConvention will apply the scala 3 dialect to the source folder since it follows a standard layout). My suspicion is that ' is whats tripping up the formatter but thats just a hunch.

This is a result of adding scalafmt to the slick codebase, the reference to the original PR is at https://github.com/slick/slick/pull/2909/files#diff-f4ef0bdda0e34a7f4ba3a4e58612a011a59cd7e3ef77a5dc1b78c28ace407392R67-R72 (note that there are many occurrences in the file, this is just the first one)

tgodzik commented 8 months ago

Looks like the same issue in mdoc https://github.com/scalameta/mdoc/pull/849/files

kitbellew commented 8 months ago

@mdedetrich thank you. scalafmt is very much context-sensitive, so could you please double-check that the code snippet you've provided formats incorrectly if put in a small file in its entirety?

your link doesn't load a relevant example for me (starts at the beginning of the diff and can't find anything that contains '{).

mdedetrich commented 8 months ago

@mdedetrich thank you. scalafmt is very much context-sensitive, so could you please double-check that the code snippet you've provided formats incorrectly if put in a small file in its entirety?

your link doesn't load a relevant example for me (starts at the beginning of the diff and can't find anything that contains '{).

You seem to be right, with the following scalafmt doesn't remove the line break

object Test {
  val g = '{ ((r: R) => $upm.fromProduct(r.asInstanceOf[Product])).asInstanceOf[Any => Any] }
  (f, g, elemTpes)
}

Let me create a minimal reproducible sample

tgodzik commented 8 months ago

Maybe the case is needed, so something like:

  private def printStatement: Unit = {
     stat match {
        case _ =>
          sb.line { _.append(s"val $fresh = ") }
          (true, List(Name(fresh) -> stat.pos))
      }

from the mdoc issue?

mdedetrich commented 8 months ago

So this appears to be the quick minimal reproduction

object Test {
  Expr.summon[Mirror.ProductOf[U]] match {
    case  _ =>
      val g = '{ ((r: R) => $upm.fromProduct(r.asInstanceOf[Product])).asInstanceOf[Any => Any] }
      (f, g, elemTpes)
  }
}

The pattern match with case is necessary here

kitbellew commented 8 months ago

3790