Skip to content

Commit d95266a

Browse files
Allow to generate more convenient setters for option fields (#19)
Use like ```scala @DaTa(optionSetters = true) class Foo(n: Option[Int] = None) Foo().withN(2) // wrapping in Some(…) is optional ```
1 parent e41a2a1 commit d95266a

File tree

5 files changed

+100
-7
lines changed

5 files changed

+100
-7
lines changed

src/main/scala/dataclass/Macros.scala

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ private[dataclass] class Macros(val c: Context) extends ImplTransformers {
1313
.map(_.toBoolean)
1414
.getOrElse(java.lang.Boolean.getBoolean("dataclass.macros.debug"))
1515

16-
private class Transformer(generateApplyMethods: Boolean)
17-
extends ImplTransformer {
16+
private class Transformer(
17+
generateApplyMethods: Boolean,
18+
generateOptionSetters: Boolean
19+
) extends ImplTransformer {
1820
override def transformClass(
1921
cdef: ClassDef,
2022
mdef: ModuleDef
@@ -92,7 +94,7 @@ private[dataclass] class Macros(val c: Context) extends ImplTransformers {
9294

9395
val setters = paramss.zipWithIndex.flatMap {
9496
case (l, groupIdx) =>
95-
l.zipWithIndex.map {
97+
l.zipWithIndex.flatMap {
9698
case (p, idx) =>
9799
val namedArgs0 =
98100
namedArgs.updated(
@@ -101,7 +103,34 @@ private[dataclass] class Macros(val c: Context) extends ImplTransformers {
101103
)
102104
val fn = p.name.decodedName.toString.capitalize
103105
val withDefIdent = TermName(s"with$fn")
104-
q"def $withDefIdent(${p.name}: ${p.tpt}) = new $tpname[..$tparamsRef](...$namedArgs0)"
106+
107+
val extraMethods =
108+
if (generateOptionSetters) {
109+
val wrappedOptionTpe = p.tpt match {
110+
case AppliedTypeTree(
111+
Ident(TypeName("Option")),
112+
List(wrapped)
113+
) =>
114+
Seq(wrapped)
115+
case _ => Nil
116+
}
117+
118+
wrappedOptionTpe.map { tpe0 =>
119+
val namedArgs0 =
120+
namedArgs.updated(
121+
groupIdx,
122+
namedArgs(groupIdx).updated(
123+
idx,
124+
q"${p.name}=_root_.scala.Some(${p.name})"
125+
)
126+
)
127+
q"def $withDefIdent(${p.name}: $tpe0) = new $tpname[..$tparamsRef](...$namedArgs0)"
128+
}
129+
} else
130+
Nil
131+
132+
q"def $withDefIdent(${p.name}: ${p.tpt}) = new $tpname[..$tparamsRef](...$namedArgs0)" +:
133+
extraMethods
105134
}
106135
}
107136

@@ -422,7 +451,14 @@ private[dataclass] class Macros(val c: Context) extends ImplTransformers {
422451
case _ => true
423452
}
424453

425-
annottees.transformAnnottees(new Transformer(generateApplyMethods))
454+
val generateOptionSetters = params.exists {
455+
case q"optionSetters=true" => true
456+
case _ => false
457+
}
458+
459+
annottees.transformAnnottees(
460+
new Transformer(generateApplyMethods, generateOptionSetters)
461+
)
426462
}
427463

428464
}

src/main/scala/dataclass/data.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import scala.annotation.{StaticAnnotation, compileTimeOnly}
44
import scala.language.experimental.macros
55

66
@compileTimeOnly("enable macro paradise to expand macro annotations")
7-
class data(apply: Boolean = true, publicConstructor: Boolean = true)
8-
extends StaticAnnotation {
7+
class data(
8+
apply: Boolean = true,
9+
publicConstructor: Boolean = true,
10+
/** Whether to generate `withFoo(foo: Foo)` methods for fields like `foo: Option[Foo]`) */
11+
optionSetters: Boolean = false
12+
) extends StaticAnnotation {
913
def macroTransform(annottees: Any*): Any = macro Macros.impl
1014
}

src/test/scala/dataclass/MoreFieldsTests.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,23 @@ object MoreFieldsTests extends TestSuite {
2020
assert(t == (1, "a", false, 1.2))
2121
}
2222

23+
"option setters" - {
24+
@data(optionSetters = true) class Bar(
25+
n: Int,
26+
s: String,
27+
b: Option[Boolean] = None,
28+
d: Double = 1.0
29+
)
30+
val foo = Bar(1, "a")
31+
val foo2 = foo.withB(true)
32+
val foo3 = foo.withB(Some(false))
33+
val foo4 = foo2.withB(None)
34+
assert(foo.b == None)
35+
assert(foo2.b == Some(true))
36+
assert(foo3.b == Some(false))
37+
assert(foo4.b == None)
38+
}
39+
2340
"since annotation" - {
2441
* - {
2542
illTyped(

src/test/scala/dataclass/OneFieldTests.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ object OneFieldTests extends TestSuite {
4949
assert(foo.count == 1)
5050
assert(foo2.count == 2)
5151
}
52+
"option setter" - {
53+
@data(optionSetters = true) class Bar(count: Option[Int] = None)
54+
val bar = Bar()
55+
val bar2 = bar.withCount(2)
56+
assert(bar.count == None)
57+
assert(bar2.count == Some(2))
58+
}
59+
"no option setter" - {
60+
@data class Bar(count: Option[Int] = None)
61+
val bar = Bar()
62+
illTyped("""
63+
val bar2 = bar.withCount(2)
64+
""", "type mismatch;.*")
65+
assert(bar.count == None)
66+
}
5267

5368
"tuple" - {
5469
@data class Foo0(a: Int) {

src/test/scala/dataclass/TwoFieldsTests.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,27 @@ object TwoFieldsTests extends TestSuite {
6262
assert(foo4.a == 2)
6363
assert(foo4.other == "u")
6464
}
65+
"option setters" - {
66+
@data(optionSetters = true) class Bar(
67+
a: Int,
68+
other: Option[String] = None
69+
)
70+
val bar = Bar(1)
71+
val bar2 = bar.withA(2)
72+
val bar3 = bar.withOther("t")
73+
val bar4 = bar2.withOther(Some("u"))
74+
val bar5 = bar2.withOther(None)
75+
assert(bar.a == 1)
76+
assert(bar.other == None)
77+
assert(bar2.a == 2)
78+
assert(bar2.other == None)
79+
assert(bar3.a == 1)
80+
assert(bar3.other == Some("t"))
81+
assert(bar4.a == 2)
82+
assert(bar4.other == Some("u"))
83+
assert(bar5.a == 2)
84+
assert(bar5.other == None)
85+
}
6586

6687
"tuple" - {
6788
@data class Foo0(a: Int, other: String) {

0 commit comments

Comments
 (0)