Surprising behavior with Appendable subclasses.
This is a custom format that escapes | as bar.
src/main/scala/example/Example.scala
package example
import play.twirl.api._
import scala.collection.immutable
sealed trait Example extends Appendable[Example] {
val contentType = "text/example"
def appendTo(sb: StringBuilder)
override def toString = {
val sb = new StringBuilder
appendTo(sb)
sb.toString
}
}
object ExampleFormat extends Format[Example] {
val empty = raw("")
def escape(text: String) = raw(text.replace("|", "bar"))
def fill(elements: immutable.Seq[Example]) = new Example {
def appendTo(sb: StringBuilder): Unit = elements.foreach(_.appendTo(sb))
}
def raw(text: String) = new Example {
def appendTo(sb: StringBuilder): Unit = sb ++= text
}
}
src/main/twirl/example/my.ex
@(text: String)
| @text |
The result of example.ex.my("bar") is bar bar bar instead of | bar |.
The template text is unexpectedly being escaped. This is because the runtime type is not Example and https://github.com/playframework/twirl/blob/master/api/src/main/scala/play/twirl/api/BaseScalaTemplate.scala#L20.
This could be improved to be T or a subtype, instead of only T.
(More generally, I feel like the runtime type branching in BaseScalaTemplate could be eliminated.)
I just ran into the same issue with HtmlFormat. Here's a simple reproduction:
@{(new play.twirl.api.Html("<foo>")): play.twirl.api.HtmlFormat.Appendable}
@{(new play.twirl.api.Html("<foo>") {}): play.twirl.api.HtmlFormat.Appendable}
renders as:
<foo>
<foo>
This makes it impossible to implement any meaningful subclasses of Html for custom rendering.
It's not clear to me why it doesn't pick the correct static overload in the first place but at least the dynamic version should behave the same (i.e. allows subtypes).