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

Incorrect rewriting of code from significant indentation to non-significant indentation based syntax #17187

Closed
eloots opened this issue Mar 30, 2023 · 12 comments · Fixed by #17366

Comments

@eloots
Copy link
Contributor

eloots commented Mar 30, 2023

Compiler version

3.0.2 and higher

Minimized code

I haven't managed to come up with a minimised example, but I created this repo that illustrates the issue and that pinpoints the Scala version that introduced the problem (note that I tried this a long time back with RC releases of Scala 3.0.0 and the GA release of Scala 3; that version didn't have this issue).

Output

When rewriting syntax from significant indentation based syntax to non-significant indentation based syntax, the compiler generates code where in some spots, a closing curly brace is missing. In the code provided in the linked repo, this occurs 12 times.

For example, in file src/main/scala/org/lunatechlabs/dotty/SudokuSolverMain.scala, notice the missing } for method main:

object SudokuSolverMain {

  def main(args: Array[String]): Unit = {

    val system = ActorSystem[NotUsed](Main(), "sudoku-solver-system")

    println(s"${GREEN}Hit RETURN to stop solver${RESET}")
    StdIn.readLine()
    system.terminate()
}

Expectation

The compiler should rewrite code correctly. In fact, it should be possible to go "full circle" through the different syntax version as noted in the README in the linked repo.

@eloots eloots added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 30, 2023
@mbovel
Copy link
Member

mbovel commented Mar 31, 2023

Thanks for reporting and for the great reproduction!

What seems mostly relevant to me is this commit where we can see what braces are missing.

Have you noticed any pattern in which braces are missing? The commit message answers my question:

I didn't discover a specific pattern in the spots where the closing '}' are omitted. So, it's not obvious to create minimised reproductions...

@mbovel mbovel added area:rewriting tool and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 31, 2023
@mbovel
Copy link
Member

mbovel commented Mar 31, 2023

Minimized to (rewrite.scala):

object A:
    object B:
        def a = 2

Works with scala-cli compile --scala-version="3.0.0" -rewrite -no-indent rewrite.scala, fails with --scala-version="3.2.2":

object A {
    object B {
        def a = 2
}

@eloots
Copy link
Contributor Author

eloots commented Mar 31, 2023

@mbovel

Have you noticed any pattern in which braces are missing?

Yes, as you noted, I didn't manage to find a pattern... The good news is that you found at least one minimal reproduction!

For the cases reported, there's probably more to it than meets the eye; for example, the problem occurs in if ... then ... else constructs and try ... catch ... expressions.

@mbovel
Copy link
Member

mbovel commented Mar 31, 2023

I could indeed reproduce with other constructs. For example:

def a =
    def b =
        def c = 2

becomes:

def a = {
    def b = {
        def c = 2
}

What problem have you observed with an if ... then ... else? Shouldn't -rewrite -no-indent leave them untouched?

@eloots
Copy link
Contributor Author

eloots commented Mar 31, 2023

What problem have you observed with an if ... then ... else? Shouldn't -rewrite -no-indent leave them untouched?

In src/main/scala/org/lunatechlabs/dotty/sudoku/SudokuDetailProcessor.scala, line 75, there's a missing } on the else branch of the nested if:

    Behaviors.receiveMessage {
      case Update(cellUpdates, replyTo) if !fullyReduced =>
        val previousState = state
        val updatedState = mergeState(state, cellUpdates)
        if updatedState == previousState && cellUpdates != cellUpdatesEmpty then {
          replyTo ! SudokuDetailUnchanged
          Behaviors.same
        }
        else {
          val transformedUpdatedState = reductionRuleTwo(reductionRuleOne(updatedState))
          if transformedUpdatedState == state then {
            replyTo ! SudokuDetailUnchanged
            Behaviors.same
          }
          else {
            val updateSender = implicitly[UpdateSender[DetailType]]
            updateSender.sendUpdate(id, stateChanges(state, transformedUpdatedState))(replyTo)
            operational(id, transformedUpdatedState, isFullyReduced(transformedUpdatedState))
        }
  ...

@eloots
Copy link
Contributor Author

eloots commented Mar 31, 2023

So, if there is a pattern, it seems to be linked to nested constructs...

@mbovel
Copy link
Member

mbovel commented Mar 31, 2023

Minimization with if-then-else:

def f =
    if true then
        val x = 3
        if (false)
            x
        else
            val y = 4
            y

becomes:

def f =
    if true then {
        val x = 3
        if (false)
            x
        else {
            val y = 4
            y
        // Missing closing brace
    }

So, if there is a pattern, it seems to be linked to nested constructs...

Yup.

@eloots
Copy link
Contributor Author

eloots commented Apr 3, 2023

Here's another case; with try/catch nested in a def:

Minimised to (nested5.scala):

def b =
  try
    val n2 = 21
    val n1 = 4
    n2 / n1
  catch
    case _ => 4

def a =
  val n2 = 21
  try
    val n1 = 4
    n2 / n1
  catch
    case _ => 4

scala-cli compile --scala-version="3.0.2" -rewrite -no-indent nested5.scala gives:

// Method 'b' is correctly rewritten
def b =
  try {
    val n2 = 21
    val n1 = 4
    n2 / n1
  }
  catch {
    case _ => 4
  }



// Method 'a' isn't. The difference with 'b' is the moving of the 'n2' value declaration
def a = {
  val n2 = 21
  try {
    val n1 = 4
    n2 / n1
  }
  catch {
    case _ => 4
  // Missing closing brace
}

@mbovel
Copy link
Member

mbovel commented Apr 27, 2023

@adpi2
Copy link
Member

adpi2 commented Apr 28, 2023

@som-snytt
Copy link
Contributor

I'll follow up what I broke. Thanks for the diagnosis. I'll update here if I don't understand how rewrite works. This is modulo whether scalac should be rewriting. (I think it's a great feature.)

@adpi2
Copy link
Member

adpi2 commented Apr 28, 2023

@som-snytt I actually have a fix that I am ready to push. I'll assign you as reviewer.

adpi2 added a commit to adpi2/scala3 that referenced this issue Apr 28, 2023
adpi2 added a commit that referenced this issue May 1, 2023
Fix #17187

The no-indent rewrite sometimes needs to add several `}` at the same
position, to close nested constructs.

For example, it rewrites:
```scala
object A:
  object B:
    def foo = 42
```
Into:
```scala
object A {
  object B {
    def foo = 42
  }
}
```

We deal with end marker in the `indentedToBraces` method by checking the
next token of the region. This also fixes the following error:

```scala
class A:
  def foo = 42

end A
```
Rewritten to
```scala
class A {
  def foo = 42
}

end A
```
Instead Of:
```scala
class A {
  def foo = 42

} // end A
```
@Kordyjan Kordyjan modified the milestones: 3.3.1, 3.3.0 Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants