Skip to content

unambiguous (to parse) expressions can still have confusing whitespace when using optional braces (and the less confusing version does not parse !) #18018

Open
@Sporarum

Description

@Sporarum

Compiler version

Scala 3.3.0

Introduction

The following are both (somewhat surprisingly) allowed:

def wrapPlaceholders(t: Int) = if true then
  println("might fail")
else println("done")
if true then
  ()
  else
    ()

These can be combined to create the following visually ambiguous piece of code:

Minimized code

if true then
  def wrapPlaceholders(t: Int) = if true then
    println("might fail")
  else
    println("done")
  else
    ()

Above there is no space between the end of the first else and the start of the second, this is not necessary:

if true then
  def wrapPlaceholders(t: Int) = if true then
    println("might fail")
    // many lines of code
  else
    println("done")
  // many lines of code
  else
    ()

https://scastie.scala-lang.org/vgZDbHiSTby8OVyQNyOPsA

The same works with try finally:

try 
  def wrapPlaceholders(t: Int) = try
    println("might fail")
    // many lines of code
  finally
    println("done")
  // many lines of code
  finally
    ()

https://scastie.scala-lang.org/hTAZVYOOSwqgLClEZ8ZtVw

Expectation

The above should not be possible, I believe this can be done by doing at least one of the following:

  • Matching keyword pairs should be on the same indentation level
  • There should not be any part of the body of a method at the same indentation level as that of its def

(In the above, I use defs, but this should also apply to vals and potentially other things)

More details

I know technically, in the following, both the try and the catch are at the same indentation level for the compiler:

def wrapPlaceholders(t: Int) = try
  println("might fail")
finally println("done")

I believe this should be changed:

If the starting keyword follows a = (or similar), its level is "one more" than the level of the def enclosing it

But there are no notions of "levels" as such in the parser, instead the Indent after the try should be able to be split into two non-empty sections, the first of which is exactly the indent difference between the def and the finally

Example:

def wrapPlaceholders(t: Int) = try
<tab><tab>println("might fail")
<tab>finally println("done")

def wrapPlaceholders(t: Int) = try
<4spaces>println("might fail")
<2spaces>finally println("done")

def wrapPlaceholders(t: Int) = try
<tab><2spaces>println("might fail")
<tab>finally println("done")

Should all work

Disclaimer

I am not trying to find reasons to "go back to braces", on the contrary,
I think fixing these kinds of ambiguities will make braceless more popular (and they might have been one of the causes of the backlash)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions