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

Different behavior with @main vs extends App for variables in scope #200

Open
ckipp01 opened this issue Mar 19, 2022 · 1 comment
Open

Comments

@ckipp01
Copy link
Member

ckipp01 commented Mar 19, 2022

This might just be me misunderstanding something, and I know expression evaluation isn't supported for Scala 3 yet, but I stumbled on this today and don't fully get what's happening. Give the following two pieces of code both using 3.1.1:

package example

@main def dapExample() =
  val greeting = "hello people interested in DAP"
  println(greeting) // <-- set a breakpoint here

This returns the following in scope:

Local

  greeting: "hello people interested in DAP"
  this: Hello$package$@183

Whereas doing the same for this snippet:

package example

object Main extends App {
  val greeting = "hello people interested in DAP"
  println(greeting) // <-- set a breakpoint here
}

Will return

Local

  this: Main$@186
    MODULE$: null
    executionStart: 0
    greeting: null
    scala$App$$_args: null
    scala$App$$initCode: null

Why is greeting null in the second?

@adpi2
Copy link
Member

adpi2 commented Mar 21, 2022

In the second example:

package example

object Main extends App {
  val greeting = "hello people interested in DAP"
  println(greeting) // <-- set a breakpoint here
}

Looking at the class file of $Main (I am using javap -l -p -c example.Main$) we can see the sequence of instructions. It is roughly:

  1. Call the static constructor of Main$
  2. Call the constructor of Main$
  3. Return from the constructor of Main$
  4. Initialize the static field $MODULE with the instance of Main$
  5. Initialize the static field greeting
  6. Get the value of greeting
  7. Call println

And the debugger stops twice on the line println(greeting), once for 3 and once for 7. On 3 the greeting field is not yet initialized.

Why does the compiler set line println(greeting) as being the return instruction of the constructor of Main$. That's probably because this is the last line of the body of object Main. It is a debatable choice.

And it is not specific to objects that extends App. I can see the same behavior in:

@main def foo(): Unit =
  val _ = Foo

object Foo {
  val greeting = "hello people interested in DAP"
  println(greeting) // <-- set a breakpoint here
}

What can we do?

  1. So first, maybe the compiler should not link this instruction to any source line (is it permitted by the Java spec?). There are plenty of cases like this where a source line in the bytecode does not really correspond to the user code in the source file. I don't know if it is possible for the compiler to not emit those lines but that would make the debugger a lot more user-friendly. This is somehow related to Smart Step Into #122 and the proposed Solution 2.

  2. In the debugger itself maybe we can skip the return line of the constructor of the classes that are associated to objects.

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

2 participants