playframework / twirl

Twirl is Play's default template engine
Apache License 2.0
545 stars 108 forks source link

Surprising behavior with Appendable subclasses. #84

Open pauldraper opened 9 years ago

pauldraper commented 9 years ago

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.)

szeiger commented 8 years ago

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>
&lt;foo&gt;

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).