#scala #metaprogramming #scala-macros
Вопрос:
Мне нужно реализовать функцию тестирования, которая проверяет информацию об ошибках во время компиляции для плагина «splain», часть этой функции должна преобразовать кодовый блок в строку, например:
def convert(fn: => Unit): String
// for testing
val code = convert {
object I extends Seq {}
}
assert(code == "object I extends Seq {}")
Возможно ли это с помощью стандартных функций scala? Большое спасибо за ваш совет.
Эта функция позволит проверять сообщения во время компиляции сложного кода, который часто нуждается в индексировании и рефакторинге в среде IDE
Комментарии:
1. Какая версия Scala? Может быть, вы сможете чего-то добиться с помощью макросов. Хотя я не уверен, что вы достаточно подробно рассказали о контексте того, почему вам нужно это делать ?
Ответ №1:
Да, это возможно.
Макрос Ли Хаоя Text
из исходного кода
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)
}
делает почти то, что ты хочешь:
def convert[A](fn: => Text[A]): String = fn.source
convert(10 20
30
)
//10 20
// 30
К несчастью,
если у вас несколько операторов в
{}
блоке,sourcecode.Text
будет сохранен только исходный код для последнего возвращаемого выражения.
И так { object I extends Seq {} }
как на самом деле { object I extends Seq {}; () }
макрос в этом случае работать не будет.
Итак, давайте напишем наш собственный простой макрос
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))
}
Использование:
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)
// }
Обратите внимание , что аргументы макросов def проверяются по типу перед расширением макросов (так convert { val i: Int = "a" }
, convert { object I extends XXX }
без определения XXX
и convert { (; }
т. Д. не будет компилироваться).
Комментарии:
1. Большое спасибо! Если мне повезет, это будет в тестовом коде следующей версии scalac
2. Это работает, но с оговоркой: код должен быть проанализирован и проверен на тип. Или ошибка будет выдана до того, как макрос lihaoyi будет вызван
3. @tribbloid В Scala все макросы def расширяются после проверки типов их аргументов.