Skip to content

Commit

Permalink
Merge pull request #594 from mkurz/resultType_localDef
Browse files Browse the repository at this point in the history
Allow defining result type for local methods (needed for Scala 3 implicits)
  • Loading branch information
mkurz authored Feb 26, 2023
2 parents 4c28847 + a9abeb2 commit 5c85092
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,14 @@ object TwirlCompiler {
t.params.pos
) :+ ":" :+ resultType :+ " = {_display_(" :+ templateCode(t, resultType) :+ ")};"
}
case Def(name, params, block) => {
case Def(name, params, resultType, block) => {
Nil :+ (if (name.str.startsWith("implicit")) "implicit def " else "def ") :+ Source(
name.str,
name.pos
) :+ Source(
params.str,
params.pos
) :+ " = {" :+ block.code :+ "};"
) :+ resultType.map(":" + _.str).getOrElse("") :+ " = {" :+ block.code :+ "};"
}
}

Expand Down
19 changes: 19 additions & 0 deletions compiler/src/test/resources/localDef.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@****************************************************************************************************************************************************
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com> *
****************************************************************************************************************************************************@

@()

@person(firstname: String, lastname: String) = @{
s"$firstname-$lastname"
}
@country(city: String, country: String): String = @{
s"$city-$country"
}
@region(continent: String):String=@{
s"$continent"
}
@slogan()=@{ s"The High Velocity Web Framework For Java and Scala" }
@year=@{ s"2023" }

@person("Play", "Framework")-@country("Vienna", "Austria")-@region("Europe")-@slogan()-@year
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,15 @@ class CompilerSpec extends AnyWordSpec with Matchers {
hello.static("twirl", "something-else").toString.trim must include("""<header class="twirl">""")
}

"compile successfully (local definitions)" in {
val helper = newCompilerHelper
val hello =
helper.compile[(() => Html)]("localDef.scala.html", "html.localDef")
hello.static().toString.trim must be(
"Play-Framework-Vienna-Austria-Europe-The High Velocity Web Framework For Java and Scala-2023"
)
}

"compile successfully (block with tuple)" in {
val helper = newCompilerHelper
val hello = helper.compile[(Seq[(String, String)] => Html)]("blockWithTuple.scala.html", "html.blockWithTuple")
Expand Down
12 changes: 6 additions & 6 deletions parser/src/main/scala/play/twirl/parser/TreeNodes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ object TreeNodes {
case class PosString(str: String) extends Positional {
override def toString: String = str
}
case class Def(name: PosString, params: PosString, code: Simple) extends Positional
case class Plain(text: String) extends TemplateTree with Positional
case class Display(exp: ScalaExp) extends TemplateTree with Positional
case class Comment(msg: String) extends TemplateTree with Positional
case class ScalaExp(parts: collection.Seq[ScalaExpPart]) extends TemplateTree with Positional
case class Simple(code: String) extends ScalaExpPart with Positional
case class Def(name: PosString, params: PosString, resultType: Option[PosString], code: Simple) extends Positional
case class Plain(text: String) extends TemplateTree with Positional
case class Display(exp: ScalaExp) extends TemplateTree with Positional
case class Comment(msg: String) extends TemplateTree with Positional
case class ScalaExp(parts: collection.Seq[ScalaExpPart]) extends TemplateTree with Positional
case class Simple(code: String) extends ScalaExpPart with Positional
case class Block(whitespace: String, args: Option[PosString], content: collection.Seq[TemplateTree])
extends ScalaExpPart
with Positional
Expand Down
44 changes: 37 additions & 7 deletions parser/src/main/scala/play/twirl/parser/TwirlParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,41 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) {
val templDecl = templateDeclaration()
if (templDecl != null) {
anyUntil(c => c != ' ' && c != '\t', inclusive = false)
if (check("=")) {
anyUntil(c => c != ' ' && c != '\t', inclusive = false)
val code = scalaBlock()
if (code != null) {
result = Def(templDecl._1, templDecl._2, code)
var next = ""
if (check(":")) {
next = ":"
} else if (check("=")) {
next = "="
}
if (next == ":" || next == "=") {
var resultType: Option[PosString] = None
if (next == ":") {
anyUntil(c => c != ' ' && c != '\t', inclusive = false)
val resultTypePos = input.offset()
val rt = identifier() match {
case null => null
case id => id
}
if (rt != null) {
val types = Option(squareBrackets()).getOrElse("")
resultType = Some(position(PosString(rt + types), resultTypePos))

anyUntil(c => c != ' ' && c != '\t', inclusive = false)
if (check("=")) {
next = "="
} else {
next = ""
}
} else {
next = ""
}
}
if (next == "=") {
anyUntil(c => c != ' ' && c != '\t', inclusive = false)
val code = scalaBlock()
if (code != null) {
result = Def(templDecl._1, templDecl._2, resultType, code)
}
}
}
}
Expand Down Expand Up @@ -886,9 +916,9 @@ class TwirlParser(val shouldParseInclusiveDot: Boolean) {

if (name != null) {
val paramspos = input.offset()
val types = Option(squareBrackets()).getOrElse(PosString(""))
val types = Option(squareBrackets()).getOrElse("")
val args = several[String, ArrayBuffer[String]] { () => parentheses() }
val params = position(PosString(types.toString + args.mkString), paramspos)
val params = position(PosString(types + args.mkString), paramspos)
if (params != null)
return (name, params)
} else input.regress(1) // don't consume @
Expand Down
134 changes: 134 additions & 0 deletions parser/src/test/scala/play/twirl/parser/test/ParserSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,140 @@ class ParserSpec extends AnyWordSpec with Matchers with Inside {
}
}

"handle local definitions" when {
"resultType is given" in {
val tmpl = parseTemplateString(
"""@implicitField: FieldConstructor = @{ FieldConstructor(myFieldConstructorTemplate.f) }"""
)
val localDef = tmpl.defs(0)

localDef.name.str mustBe "implicitField"
localDef.name.pos.line mustBe 1
localDef.name.pos.column mustBe 2

val resultType = localDef.resultType.get
resultType.str mustBe "FieldConstructor"
resultType.pos.line mustBe 1
resultType.pos.column mustBe 17

localDef.params.str mustBe ""

localDef.code.code mustBe "{ FieldConstructor(myFieldConstructorTemplate.f) }"
}
"resultType is given without implicit prefixed" in {
val tmpl = parseTemplateString(
"""@field: FieldConstructor = @{ FieldConstructor(myFieldConstructorTemplate.f) }"""
)
val localDef = tmpl.defs(0)

localDef.name.str mustBe "field"
localDef.name.pos.line mustBe 1
localDef.name.pos.column mustBe 2

val resultType = localDef.resultType.get
resultType.str mustBe "FieldConstructor"
resultType.pos.line mustBe 1
resultType.pos.column mustBe 9

localDef.params.str mustBe ""

localDef.code.code mustBe "{ FieldConstructor(myFieldConstructorTemplate.f) }"
}
"resultType with type is given" in {
val tmpl = parseTemplateString(
"""@implicitField: FieldConstructor[FooType] = @{ FieldConstructor(myFieldConstructorTemplate.f) }"""
)
val localDef = tmpl.defs(0)

localDef.name.str mustBe "implicitField"
localDef.name.pos.line mustBe 1
localDef.name.pos.column mustBe 2

val resultType = localDef.resultType.get
resultType.str mustBe "FieldConstructor[FooType]"
resultType.pos.line mustBe 1
resultType.pos.column mustBe 17

localDef.params.str mustBe ""

localDef.code.code mustBe "{ FieldConstructor(myFieldConstructorTemplate.f) }"
}
"resultType is given without spaces" in {
val tmpl = parseTemplateString(
"""@implicitField:FieldConstructor=@{ FieldConstructor(myFieldConstructorTemplate.f) }"""
)
val localDef = tmpl.defs(0)

localDef.name.str mustBe "implicitField"
localDef.name.pos.line mustBe 1
localDef.name.pos.column mustBe 2

val resultType = localDef.resultType.get
resultType.str mustBe "FieldConstructor"
resultType.pos.line mustBe 1
resultType.pos.column mustBe 16

localDef.params.str mustBe ""

localDef.code.code mustBe "{ FieldConstructor(myFieldConstructorTemplate.f) }"
}
"resultType and params are given" in {
val tmpl = parseTemplateString(
"""@implicitField(foo: String, bar: Int): FieldConstructor = @{ FieldConstructor(myFieldConstructorTemplate.f) }"""
)
val localDef = tmpl.defs(0)

localDef.name.str mustBe "implicitField"
localDef.name.pos.line mustBe 1
localDef.name.pos.column mustBe 2

val resultType = localDef.resultType.get
resultType.str mustBe "FieldConstructor"
resultType.pos.line mustBe 1
resultType.pos.column mustBe 40

localDef.params.str mustBe "(foo: String, bar: Int)"
localDef.params.pos.line mustBe 1
localDef.params.pos.column mustBe 15

localDef.code.code mustBe "{ FieldConstructor(myFieldConstructorTemplate.f) }"
}
"no resultType and no params are given" in {
val tmpl = parseTemplateString(
"""@implicitField = @{ FieldConstructor(myFieldConstructorTemplate.f) }"""
)
val localDef = tmpl.defs(0)

localDef.name.str mustBe "implicitField"
localDef.name.pos.line mustBe 1
localDef.name.pos.column mustBe 2

localDef.resultType mustBe None

localDef.params.str mustBe ""

localDef.code.code mustBe "{ FieldConstructor(myFieldConstructorTemplate.f) }"
}
"no resultType but params are given" in {
val tmpl = parseTemplateString(
"""@implicitField(foo: String, bar: Int) = @{ FieldConstructor(myFieldConstructorTemplate.f) }"""
)
val localDef = tmpl.defs(0)

localDef.name.str mustBe "implicitField"
localDef.name.pos.line mustBe 1
localDef.name.pos.column mustBe 2

localDef.resultType mustBe None

localDef.params.str mustBe "(foo: String, bar: Int)"
localDef.params.pos.line mustBe 1
localDef.params.pos.column mustBe 15

localDef.code.code mustBe "{ FieldConstructor(myFieldConstructorTemplate.f) }"
}
}

"handle string literals within parentheses" when {
"with left parenthesis" in {
parseStringSuccess("""@foo("(")""")
Expand Down

0 comments on commit 5c85092

Please sign in to comment.