SourceLocation and SourceContext

TiarkRompf edited this page Oct 7, 2011 · 1 revision

When lifting DSL constructs, we typically lose source information. For example, the IR nodes no longer contain line numbers and variable names that correspond to the original source. This can be problematic if the DSL performs checks on the generated IR. In that case, failing checks should produce meaningful error messages that refer back to the location in the source that contains the error.

Virtualized Scala allows DSL authors to capture source information of DSL constructs, such as line numbers and method names, using implicit parameters of type SourceLocation. For this, a method corresponding to a DSL operation can be augmented as follows:

def infix_+(x: Exp[Double], y: Exp[Double])(implicit loc: SourceLocation) =
  toAtom(Plus(x, y))(loc)

In the above example, whenever the infix_+ method is called (in a DSL program), the implicit loc argument describes the dynamic invocation site. The compiler automatically generates this loc object for each invocation of the infix_+ method, containing source information specific to the invocation site. This source information is accessible through the following methods:

def fileName: String
def line: Int
def charOffset: Int

The SourceLocation object can then be passed to the constructors of the IR. In the above example, we are passing loc to the toAtom method. toAtom converts a "definition node" to an "expression node" by finding a symbol (or creating one if it doesn't already exist) which is mapped to the definition node. The symbol is the expression returned by toAtom which, in turn, is the result of invoking infix_+. Whenever toAtom creates a new symbol we can attach the current SourceLocation to it. This way, (the result of) the infix_+ call is equiped with source location information which can be used when processing the IR subsequently.

Implicit parameters of type SourceLocation provide basic source information, such as line numbers. Since they are specialized for each invocation site, it is impossible to pass a given SourceLocation object to other methods using implicit parameters (since these methods would receive an object tailored to their invocation site, and not the given SourceLocation). For this reason, Virtualized Scala provides a sub class of SourceLocation called SourceContext which enables more flexible ways of passing source information using implicit parameters.

An implicit parameter of type SourceContext is generated just like a SourceLocation argument, except when there is already a value of type SourceContext, say ctx, in the implicit scope. In this case, the generated SourceContext has the form ctx.update(currentCtx) where currentCtx is the SourceContext generated for the current invocation. The following example illustrates this:

import scala.reflect.SourceContext

object Test extends App {
  def printInfo(ctx: SourceContext) {
    println("line: " + ctx.line)
    println("method name: " + ctx.methodName)
    if (!ctx.parent.isEmpty) {
      println("parent:")
      printInfo(ctx.parent.get)
    }
  }
  
  def inspectChained()(implicit ctx: SourceContext) {
    printInfo(ctx)
  }
  
  def inspect[T](x: T)(implicit ctx: SourceContext): T = {
    printInfo(ctx)
    inspectChained()
    x
  }
  
  val l = List(1, 2, 3)
  val x = inspect(l)
}

Running the above example produces the following output:

line: 24
method name: inspect
line: 19
method name: inspectChained
parent:
line: 24
method name: inspect

As you can see, the SourceContext passed to the inspectChained method has a parent source context (parent is of type Option[SourceContext]) which is the source context of the enclosing method invocation, namely the invocation of the inspect method. This chaining of parent contexts allows the DSL author to pass implicit parameters of type SourceContext without "losing" any information. As a result, the implementation of a DSL does not have to be cluttered with implicit SourceContext parameters.