I need to implement a testing function which checks compile-time error information for the "splain" plugin, part of this function needs to convert a codeblock into a string, e.g.:
def convert(fn: => Unit): String
// for testing
val code = convert {
object I extends Seq {}
}
assert(code == "object I extends Seq {}")
Is this possible using standard scala features? Thanks a lot for your advice.
This function will enable verifications of compile-time messages of complex code that needs to be indexed and refactored by IDE often
CodePudding user response:
Yes, it's possible.
Li Haoyi's macro Text
from sourcecode
def text[T: c.WeakTypeTag](c: Compat.Context)(v: c.Expr[T]): c.Expr[sourcecode.Text[T]] = {
import c.universe._
val fileContent = new String(v.tree.pos.source.content)
val start = v.tree.collect {
case treeVal => treeVal.pos match {
case NoPosition ⇒ Int.MaxValue
case p ⇒ p.startOrPoint
}
}.min
val g = c.asInstanceOf[reflect.macros.runtime.Context].global
val parser = g.newUnitParser(fileContent.drop(start))
parser.expr()
val end = parser.in.lastOffset
val txt = fileContent.slice(start, start end)
val tree = q"""${c.prefix}(${v.tree}, $txt)"""
c.Expr[sourcecode.Text[T]](tree)
}
does almost what you want:
def convert[A](fn: => Text[A]): String = fn.source
convert(10 20
30
)
//10 20
// 30
Unfortunately,
if you have multiple statements in a
{}
block,sourcecode.Text
will only capture the source code for the last expression that gets returned.
And since { object I extends Seq {} }
is actually { object I extends Seq {}; () }
the macro will not work in this case.
So let's write our own simple macro
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def convert(fn: => Any): String = macro convertImpl
def convertImpl(c: blackbox.Context)(fn: c.Tree): c.Tree = {
import c.universe._
val pos = fn.pos
val res = new String(pos.source.content).slice(pos.start, pos.end)
Literal(Constant(res))
}
Usage:
trait Seq
convert({
val i: Int = 1
object I extends Seq {}
10 20 30
convert(1)
})
//{
// val i: Int = 1
// object I extends Seq {}
// 10 20 30
// convert(1)
// }
Notice that arguments of def macros are typechecked before macro expansion (so convert { val i: Int = "a" }
, convert { object I extends XXX }
without defined XXX
, convert { (; }
etc. will not compile).
CodePudding user response:
Is this possible using standard scala features?
No
In scala, the object I extends Seq {}
part will be executed the result will be passed on argument to method covert
.
You can do give a shot to https://docs.scala-lang.org/tour/annotations.html
Curious, why can't you pass object I extends Seq {}
as " object I extends Seq {}"
?