Skip to content

Commit

Permalink
refs #18: unapply support
Browse files Browse the repository at this point in the history
  • Loading branch information
carymrobbins committed Apr 20, 2018
1 parent f193d2a commit 6e01784
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
}
}

val (optimizeOps, debug, debugRaw) = c.prefix.tree match {
val (optimizeOps, unapply, debug, debugRaw) = c.prefix.tree match {
case q"new ${`macroName`}(..$args)" =>
(
args.collectFirst { case q"optimizeOps = false" => }.isEmpty,
args.collectFirst { case q"unapply = true" => }.isDefined,
args.collectFirst { case q"debug = true" => }.isDefined,
args.collectFirst { case q"debugRaw = true" => }.isDefined
)
case _ => (true, false, false)
case _ => (true, false, false, false)
}

def fail(msg: String) = c.abort(c.enclosingPosition, msg)
Expand Down Expand Up @@ -121,6 +122,7 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
val companionExtraDefs =
generateClassTag(classTagName, valDef, tparamsNoVar, tparamNames, subtype) ::
maybeGenerateApplyMethod(clsDef, valDef, tparamsNoVar, tparamNames) :::
maybeGenerateUnapplyMethod(clsDef, valDef, tparamsNoVar, tparamNames) :::
maybeGenerateOpsDef(clsDef, valDef, tparamsNoVar, tparamNames) :::
generateCoercibleInstances(tparamsNoVar, tparamNames, tparamsWild) :::
generateDerivingMethods(tparamsNoVar, tparamNames, tparamsWild)
Expand Down Expand Up @@ -184,6 +186,23 @@ private[macros] class NewTypeMacros(val c: blackbox.Context)
)
}

def maybeGenerateUnapplyMethod(
clsDef: ClassDef, valDef: ValDef, tparamsNoVar: List[TypeDef], tparamNames: List[TypeName]
): List[Tree] = {
if (!unapply) Nil else {
// Note that our unapply method should Some since its isEmpty/get is constant.
List(
if (tparamsNoVar.isEmpty) {
q"""def unapply(x: ${clsDef.name}): Some[${valDef.tpt}] =
Some(x.asInstanceOf[${valDef.tpt}])"""
} else {
q"""def unapply[..$tparamsNoVar](x: ${clsDef.name}[..$tparamNames]): Some[${valDef.tpt}] =
Some(x.asInstanceOf[${valDef.tpt}])"""
}
)
}
}

// We should expose the constructor argument as an extension method only if
// it was defined as a public param.
def shouldGenerateValMethod(clsDef: ClassDef, valDef: ValDef): Boolean = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.annotation.StaticAnnotation

class newsubtype(
optimizeOps: Boolean = true,
unapply: Boolean = false,
debug: Boolean = false,
debugRaw: Boolean = false
) extends StaticAnnotation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.annotation.StaticAnnotation

class newtype(
optimizeOps: Boolean = true,
unapply: Boolean = false,
debug: Boolean = false,
debugRaw: Boolean = false
) extends StaticAnnotation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,36 @@ class NewTypeMacrosTest extends FlatSpec with Matchers {
val x = List(Option(1))
val y = x.coerce[List[Option[Foo]]]
}

"unapply = true" should "generate an unapply method" in {
@newtype (unapply = true) case class X0(private val x: String)
@newtype (unapply = true) case class X1[A](private val x: A)
@newsubtype(unapply = true) case class Y0(private val x: String)
@newsubtype(unapply = true) case class Y1[A](private val x: A)

// Note that we're using (x0: String) to assert the type of x0 at compile time.
// Also checking that unapply doesn't compile for ill-typed expressions.

val x0 = X0("x") match { case X0(x) => x }
(x0: String) shouldBe "x"
assertTypeError(""" "x" match { case X0(x) => x }""")
assertTypeError(""" 1 match { case X0(x) => x }""")

val x1 = X1("x") match { case X1(x) => x }
(x1: String) shouldBe "x"
assertTypeError(""" "x" match { case X1(x) => x }""")
assertTypeError(""" 1 match { case X1(x) => x }""")

val y0 = Y0("y") match { case Y0(x) => x }
(y0: String) shouldBe "y"
assertTypeError(""" "x" match { case Y0(x) => x }""")
assertTypeError(""" 1 match { case Y0(x) => x }""")

val y1 = Y1("y") match { case Y1(x) => x }
(y1: String) shouldBe "y"
assertTypeError(""" "x" match { case Y1(x) => x }""")
assertTypeError(""" 1 match { case Y1(x) => x }""")
}
}

object NewTypeMacrosTest {
Expand Down

0 comments on commit 6e01784

Please sign in to comment.