Search code examples
scalascopevariable-assignmentscala-3

Scala 3: Unexpected Successful Compilation when Using Variable Before Assignment


I was expecting this to raise an error as I'm trying to use x before assigning it. Here's the Scala 3.3.1 REPL:

scala> val x: Int =
     |  val y: Int = x + 10
     |  y
     |
val x: Int = 10

Why is the result 10? When Scala evaluates the x + 10, it assumes x=0?


Solution

  • It's happening because of the same REPL-trickery that allows you to compile and execute

    > val x = 1
    > val x = 2
    

    in the interactive REPL-mode: the vals are not compiled as local variables inside of function bodies, but as members of wrapper objects, as described here.

    In your particular case, when you type your example

    scala> val x: Int =
         |  val y: Int = x + 10
         |  y
         |
    

    into the REPL, it gets wrapped into a synthetic "execution template"-object, which behaves roughly as the following script:

    object replInputLine$1 {
      val x: Int =
        val y = x + 10
        y
      println(x)
    }
    
    @main def entry(): Unit =
      replInputLine$1
    

    What is happening in this script? Well, the replInputLine$1-object is initialized. As always during object-initialization, the member x: Int is allocated and initialized to 0 during the allocation of the object, and then the actual object initializer is executed, which updates x to 10.

    Since x is an object member, and not a local variable in a method body, it can be accessed without problems.

    If instead you tried to use the same code snippet inside of a method body, you would get compilation errors, as the following example shows:

    @main def entry(): Unit =
      val x: Int =
        val y: Int = x + 10
        y
      println(x)
    

    This will fail the compilation with the message

    Reference error: [...] x is a forward reference extending over the definition of x

    So, it's just an artifact resulting from the fact that REPL treats vals as members of a synthetic object instead of treating them as local variables.

    Playing "language-lawyer" games with the REPL is pointless and boring: if you want to understand what the compiler is doing "for real", don't test it in the REPL, write a separate program, then compile it.