У меня есть собственный класс, A
, и я определил некоторые операции внутри класса следующим образом:
def +(that: A) = ...
def -(that: A) = ...
def *(that: A) = ...
def +(that: Double) = ...
def -(that: Double) = ...
def *(that: Double) = ...
Для того, чтобы что-то вроде 2.0 + x
имело смысл, когда x
имеет тип A
, я определил следующий неявный класс:
object A {
implicit class Ops (lhs: Double) {
def +(rhs: A) = ...
def -(rhs: A) = ...
def *(rhs: A) = ...
}
}
Это все нормально работает. Теперь я представляю плагин компилятора с TypingTransformer
, который выполняет некоторые оптимизации. В частности, скажем, у меня есть ValDef
:
val x = y + a * z
, где x
, y
и z
имеют тип A
, а a
представляет собой Double
. Обычно это компилируется нормально. Я поставил его через оптимизатор, который использует квазицитаты, чтобы изменить y + a * z
на что-то еще. НО в этом конкретном примере выражение остается неизменным (оптимизация не выполняется). Внезапно компилятор больше не выполняет неявное преобразование для a * z
.
Подводя итог, у меня есть плагин компилятора, который принимает выражение, к которому обычно применяются неявные преобразования. Он создает новое выражение с помощью квазиквот, которое синтаксически выглядит так же, как старое выражение. Но для этого нового выражения компилятору не удалось выполнить неявное преобразование.
Итак, мой вопрос - как компилятор определяет необходимость неявного преобразования? Есть ли в AST определенный флаг или что-то, что нужно установить, а квазицитаты не могут быть установлены?
UPDATE
Фаза плагина выглядит примерно так:
override def transform(tree: Tree) = tree match {
case ClassDef(classmods, classname, classtparams, impl) if classname.toString == "Module" => {
var implStatements: List[Tree] = List()
for (node <- impl.body) node match {
case DefDef(mods, name, tparams, vparamss, tpt, body) if name.toString == "loop" => {
var statements: List[Tree] = List()
for (statement <- body.children.dropRight(1)) statement match {
case Assign(opd, rhs) => {
val optimizedRHS = optimizeStatement(rhs)
statements = statements ++ List(Assign(opd, optimizedRHS))
}
case ValDef(mods, opd, tpt, rhs) => {
val optimizedRHS = optimizeStatement(rhs)
statements = statements ++
List(ValDef(mods, opd, tpt, optimizedRHS))
}
case Apply(Select(src1, op), List(src2)) if op.toString == "push" => {
val optimizedSrc2 = optimizeStatement(src2)
statements = statements ++
List(Apply(Select(src1, op), List(optimizedSrc2)))
}
case _ => statements = statements ++ List(statement)
}
val newBody = Block(statements, body.children.last)
implStatements = implStatements ++
List(DefDef(mods, name, tparams, vparamss, tpt, newBody))
}
case _ => implStatements = implStatements ++ List(node)
}
val newImpl = Template(impl.parents, impl.self, implStatements)
ClassDef(classmods, classname, classtparams, newImpl)
}
case _ => super.transform(tree)
}
def optimizeStatement(tree: Tree): Tree = {
// some logic that transforms
// 1.0 * x + 2.0 * (x + y)
// into
// 3.0 * x + 2.0 * y
// (i.e. distribute multiplication & collect like terms)
//
// returned trees are always newly created
// returned trees are create w/ quasiquotes
// something like
// 1.0 * x + 2.0 * y
// will return
// 1.0 * x + 2.0 * y
// (i.e. syntactically unchanged)
}
ОБНОВЛЕНИЕ 2
Пожалуйста, обратитесь к этому репозиторию GitHub для минимального рабочего примера: https://github.com/darsnack/compiler-plugin-demo
Проблема в том, что a * z
превращается в a.<$times: error>(z)
после того, как я оптимизирую оператор.