SourceLocation and SourceContext
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.