Почему scala.meta.Term.Param#toString удаляет модификаторы?
Я пытаюсь переименовать параметр анонимной функции, используя семантический плагин scalafix. Соответствующий код выглядит так:
case Term.Apply(func, args) =>
args.collect { case Term.Block(List(Term.Function(List(arg), _))) =>
Patch.replaceTree(arg, arg.copy(name = Term.Name("components")).toString())
Проблема в том, что это меняется
{ implicit foo =>
к
{ components =>
(т.е. он сбрасывает модификатор). Сначала я подумал, что метод по какой-то причине отбрасывает его, но я добавил несколько s, но это не так: модификатор существует в копии, но просто не включается в вывод. Кто-нибудь знает, что здесь происходит? И как я могу включить его в вывод?
println
с:
println("***********ORIGINAL***********")
println("toString:\t" + arg.toString())
println("name:\t\t" + arg.name)
println("modifiers:\t" + arg.mods)
println("syntax:\t\t" + arg.syntax)
println("structure:\t" + arg.structure)
println("***********COPY***********")
val copy = arg.copy(name = Term.Name("components"))
println("toString:\t" + copy.toString())
println("name:\t\t" + copy.name)
println("modifiers:\t" + copy.mods)
println("syntax:\t\t" + copy.syntax)
println("structure:\t" + copy.structure)
выход:
***********ORIGINAL***********
toString: implicit app
name: app
modifiers: List(implicit)
syntax: implicit app
structure: Term.Param(List(Mod.Implicit), Term.Name("app"), None, None)
***********COPY***********
toString: components
name: components
modifiers: List(implicit)
syntax: components
structure: Term.Param(List(Mod.Implicit), Term.Name("components"), None, None)
(обратите внимание, что
copy
имеет
implicit
в его списке модификаторов, но он не отображается в выводах
toString
или же
syntax
)
1 ответ
Дело в том, что когда Scalameta (4.5.13) печатает a, она пропускает Mod.Implicit
иMod.Using
case t: Term.Param =>
// NOTE: `implicit/using` in parameters is skipped as it applies to whole list
printParam(t, t.mods.filterNot(x => x.is[Mod.Implicit] || x.is[Mod.Using]))
Затем он печатаетList[List[Term.Param]]
правильно
implicit def syntaxParamss: Syntax[List[List[Term.Param]]] = Syntax { paramss =>
def usingImplicit(params: List[Term.Param]): Show.Result = {
if (params.exists(_.mods.exists(_.is[Mod.Using])))
s("using ", r(params, ", "))
else
w("implicit ", r(params, ", "), params.exists(_.mods.exists(_.is[Mod.Implicit])))
}
r(
paramss.map(params => {
s(
"(",
usingImplicit(params),
")"
)
}),
""
)
}
но это нам не поможет.
Самое простое исправление - просто добавить при необходимости
doc.tree.collect {
case Term.Apply(func, args) =>
args.collect {
case Term.Block(List(Term.Function(List(arg), _))) =>
val res = arg.copy(name = Term.Name("components"))
val prefix = if (res.mods.exists(_.is[Mod.Implicit])) "implicit " else ""
Patch.replaceTree(arg, prefix + res.toString)
}.asPatch
}.asPatch
почему в оригинале написано, а в копии нет
Потому что Scalameta по-разному печатает вновь проанализированные деревья и трансформированные/сгенерированные деревья. Для первого он сохраняет их исходное строковое представление с исходным форматированием. Для последнего он печатает их с соответствующим экземпляромscala.meta.prettyprinters.Show
то есть пропускаетimplicit
для параметра и т. д.
arg.toString
звонкиscala.meta.internal.prettyprinters.TreeSyntax.apply[Term.Param](Scala213).apply(arg)
.
Метод _ TreeSyntax.apply
является
def apply[T <: Tree](dialect: Dialect): Syntax[T] = {
// NOTE: This is the current state of the art of smart prettyprinting.
// If we prettyprint a tree that's just been parsed with the same dialect,
// then we retain formatting. Otherwise, we don't, even in the tiniest.
// I expect to improve on this in the nearest future, because we had it much better until recently.
Syntax { (x: T) =>
x.origin match {
// NOTE: Options don't really matter,
// because if we've parsed a tree, it's not gonna contain lazy seqs anyway.
// case Origin.Parsed(_, originalDialect, _) if dialect == originalDialect && options == Options.Eager =>
case o @ Origin.Parsed(_, `dialect`, _) => s(o.position.text)
case _ => reprint(x)(dialect)
}
}
}
Здесь в сопоставлении с образцом дляOrigin.Parsed
(источник вновь проанализированного дерева) метод возвращаетResult.Str
, дляOrigin.None
(происхождение преобразованного/сгенерированного дерева) он возвращаетResult.Sequence
.
println(arg) // implicit y: Boolean
println(arg.structure) // Term.Param(List(Mod.Implicit), Term.Name("y"), Some(Type.Name("Boolean")), None)
println(arg.getClass) // class scala.meta.Term$Param$TermParamImpl
println(arg.origin) // Parsed(Input.VirtualFile("fix/Scalafixdemo.scala", "... implicit y: Boolean => ..."),Scala213,TokenStreamPosition(45,51))
println(TreeSyntax.apply[Term.Param](Scala213).apply(arg).getClass)
// class scala.meta.prettyprinters.Show$Str
val res = arg.copy(name = Term.Name("components"))
println(res) // components: Boolean
println(res.structure) // Term.Param(List(Mod.Implicit), Term.Name("components"), Some(Type.Name("Boolean")), None)
println(res.getClass) // class scala.meta.Term$Param$TermParamImpl
println(res.origin) // None
println(TreeSyntax.apply[Term.Param](Scala213).apply(res).getClass)
// class scala.meta.prettyprinters.Show$Sequence
Методscala.meta.internal.trees.InternalTree#origin
являетсяprivate[meta]
так что если вы играете с ним, поместите свое правило в пакетscala.meta
.
Term.Param
не является классом case и.copy
не является методом case-класса.arg
иres
на самом деле являются экземплярами сгенерированного макросом классаTerm.Param.TermParamImpl
.