scalacenter / student-projects

The list of the available projects at the Scala Center for bachelor and master students.
8 stars 0 forks source link

Better call-stack in the debugger #11

Closed adpi2 closed 1 year ago

adpi2 commented 2 years ago

Description

The backend of the compiler encodes all Scala definitions into erased Java definitions. It also introduces intermediate methods that we cannot see directly in the source file.

The debugger allows to view the call stack of a program. For the reason exposed above the call stacks contain encoded names that are hard to interpret.

The goal of the project is to show a better call stack by decoding back all the symbols and hiding some intermediate methods.

Example 1: top level definition and encoded operator

@main def run(): Unit = !!

def !! : Unit = println("!!")

The runtime call-stack is:

example$package$.$bang$bang()
example$package$.run()

Where:

The better call-stack should be:

example.!!()
example.run()

Example 2: private method accessed by inner class

package example

@main def run(): Unit =
  val outer = new Outer
  val inner = new outer.Inner
  println(inner.bar)

class Outer:
  private def foo: String =
    "foo"

  class Inner:
    def bar: String = foo

The runtime call-stack is:

Outer.example$Outer$$foo()
Outer$Inner.bar()
example$package$.run()

Where:

The better call-stack should be:

Outer.foo()
Outer.Inner.bar()
example.run()

Example 3: mixin-forwarder

package example

@main def run(): Unit =
  val foo: Foo = new Bar
  foo.m()

trait Foo:
  def m(): String = "foo"

class Bar extends Foo

The runtime call-stack is:

Foo.m()
Foo.m$(Foo)
Bar.m()
example$package$.run()

Where Bar.m() and Foo.m$(Foo) are some intermediate methods generated by the compiler, called the mixin forwarders.

We don't need to show those methods to the user. So a better call-stack should be:

Foo.m()
example.run()

Example 4: given instance and erased signature

package example

@main def run(): Unit =
  foo(5)

def foo[T](x: T)(using show: Show[T]): String =
  show.show(x)

trait Show[T]:
  def show(x: T): String

object Show:
  given Show[Int] with
    def show(x: Int) = x.toString

The runtime call-stack is:

Show$given_Show_Int$.show(int)
Show$given_Show_Int$.show(Object)
example$package$.foo(Object,Show)
example$package$.run()

Where:

The better call-stack should be:

Show.given_Show_Int.show(x: Int)
example.foo[T](x: T)(using show: Show[T])
example.run()

Notice that it is a lot easier to relate the call-stack with the code now.

Expected Outcome

We expect the student to implement the better-stack in the Scala Debug Adapter using TASTy Query. We already use TASTy Quert in the Scala Debug Adapter to find the scala definition of any runtime method, and this should be reused.

An important requirement is to test the implementation, using the test framework of the Scala Debug Adapter. See for instance the StepFilterTests to get an idea of how we write the tests.

Supervisor

Adrien Piquerez(adrien.piquerez@epfl.ch): Tooling Engineer at the Scala Center