Skip to content

Commit 5e7d194

Browse files
committed
[base] Allow fromSpliceUsingBuilder to bypass builder for single splice
Change single scalar argument to be a PartialFunction instead of an Option in case an optimization only applies to some possible scalars and to match the new single splice argument
1 parent 03de988 commit 5e7d194

File tree

4 files changed

+121
-52
lines changed

4 files changed

+121
-52
lines changed

Base/src/main/scala-2/typeclass/VersionSpecificRepeated.scala

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,25 @@ trait VersionSpecificRepeated {
7676
* @param ifZero the Expr to use when combining zero items together.
7777
* If None, `newAccumulator.result()` will be used.
7878
* If Some, should ideally be equivalent to but more efficient than the None expr.
79-
* @param ifOne the Expr to use when combining one scalar item together.
80-
* If None, `newAccumulator.+=(item).result()` will be used.
81-
* If Some, should ideally be equivalent to but more efficient than the None expr.
79+
* @param ifOneScalar the Expr to use when combining one scalar item together.
80+
* When not defined, `'{$newAccumulator.addOne($item).result()}` will be used.
81+
* The definition should be equivalent to but more efficient than the undefined expr.
82+
* @param ifOneSplice the Expr to use when combining one splice item together.
83+
* When not defined, `'{$newAccumulator.addAll($item).result()}` will be used.
84+
* The definition should be equivalent to but more efficient than the undefined expr.
8285
* @version 0.1.1
8386
*/
8487
def forContextFromSplicesUsingBuilder[A, Z](c:Context)(
8588
newAccumulator: c.Expr[Builder[A, Z]],
8689
ifZero: Option[() => c.Expr[Z]],
87-
ifOne: Option[(c.Expr[A]) => c.Expr[Z]],
90+
ifOneScalar: PartialFunction[c.Expr[A], c.Expr[Z]],
91+
ifOneSplice: PartialFunction[c.Expr[Iterable[A]], c.Expr[Z]],
8892
)(implicit
8993
accumulatorType: c.universe.TypeTag[Builder[A, Z]],
9094
zType: c.universe.TypeTag[Z],
9195
): Repeated[SplicePiece[c.Expr, A], c.Expr[Z]] = {
96+
// using default arguments confuses the typechecker (found `c.Expr` required `x$1.Expr`), so don't provide default arguments
97+
9298
import c.universe.Tree
9399
import c.universe.Quasiquote
94100

@@ -107,7 +113,8 @@ trait VersionSpecificRepeated {
107113

108114
sealed trait Acc
109115
final object AccZero extends Acc
110-
final class AccOne(val elem: c.Expr[A]) extends Acc
116+
final class AccOneScalar(val elem: c.Expr[A]) extends Acc
117+
final class AccOneSplice(val iter: c.Expr[Iterable[A]]) extends Acc
111118
final class AccMany extends Acc {
112119
val builder: Builder[Tree, c.Expr[Z]] = List.newBuilder.mapResult(stat =>
113120
c.Expr[Z](
@@ -124,27 +131,39 @@ trait VersionSpecificRepeated {
124131
case AccZero =>
125132
elem match {
126133
case _: SplicePiece.Zero[c.Expr] => AccZero
127-
case elemOne: SplicePiece.One[c.Expr, A] => new AccOne(elemOne.elem)
128-
case elemMany: SplicePiece.Many[c.Expr, A] => {
134+
case elemOne: SplicePiece.One[c.Expr, A] => new AccOneScalar(elemOne.elem)
135+
case elemMany: SplicePiece.Many[c.Expr, A] => new AccOneSplice(elemMany.iter)
136+
}
137+
case accOne: AccOneScalar => {
138+
elem match {
139+
case _: SplicePiece.Zero[c.Expr] => accOne
140+
case elemOne: SplicePiece.One[c.Expr, A] =>
129141
val retval = new AccMany()
130142
retval.builder += accumulatorValDef
143+
retval.builder += q"$accumulatorIdent.+=(${accOne.elem})"
144+
retval.builder += q"$accumulatorIdent.+=(${elemOne.elem})"
145+
retval
146+
case elemMany: SplicePiece.Many[c.Expr, A] =>
147+
val retval = new AccMany()
148+
retval.builder += accumulatorValDef
149+
retval.builder += q"$accumulatorIdent.+=(${accOne.elem})"
131150
retval.builder += q"$accumulatorIdent.++=(${elemMany.iter})"
132151
retval
133-
}
134152
}
135-
case accOne: AccOne => {
153+
}
154+
case accOne: AccOneSplice => {
136155
elem match {
137156
case _: SplicePiece.Zero[c.Expr] => accOne
138157
case elemOne: SplicePiece.One[c.Expr, A] =>
139158
val retval = new AccMany()
140159
retval.builder += accumulatorValDef
141-
retval.builder += q"$accumulatorIdent.+=(${accOne.elem})"
160+
retval.builder += q"$accumulatorIdent.++=(${accOne.iter})"
142161
retval.builder += q"$accumulatorIdent.+=(${elemOne.elem})"
143162
retval
144163
case elemMany: SplicePiece.Many[c.Expr, A] =>
145164
val retval = new AccMany()
146165
retval.builder += accumulatorValDef
147-
retval.builder += q"$accumulatorIdent.+=(${accOne.elem})"
166+
retval.builder += q"$accumulatorIdent.++=(${accOne.iter})"
148167
retval.builder += q"$accumulatorIdent.++=(${elemMany.iter})"
149168
retval
150169
}
@@ -165,7 +184,8 @@ trait VersionSpecificRepeated {
165184
def result(acc:Acc):c.Expr[Z] = {
166185
acc match {
167186
case AccZero => ifZero.map(_.apply()).getOrElse(c.Expr[Z](q"$newAccumulator.result()"))
168-
case accOne: AccOne => ifOne.map(_.apply(accOne.elem)).getOrElse(c.Expr[Z](q"$newAccumulator.+=(${accOne.elem}).result()"))
187+
case accOne: AccOneScalar => ifOneScalar.applyOrElse(accOne.elem, (e: c.Expr[A]) => c.Expr[Z](q"$newAccumulator.+=(${e}).result()"))
188+
case accOne: AccOneSplice => ifOneSplice.applyOrElse(accOne.iter, (es: c.Expr[Iterable[A]]) => c.Expr[Z](q"$newAccumulator.++=(${es}).result()"))
169189
case accMany: AccMany => accMany.builder.result()
170190
}
171191
}
@@ -185,7 +205,8 @@ trait VersionSpecificRepeated {
185205
forContextFromSplicesUsingBuilder[A, List[A]](c)(
186206
c.universe.reify(List.newBuilder),
187207
Option(() => c.universe.reify(List.empty)),
188-
Option((a: c.Expr[A]) => c.universe.reify(List(a.splice))),
208+
{case (a: c.Expr[A]) => c.universe.reify(List(a.splice))},
209+
{case (a: c.Expr[_]) if a.staticType <:< zType.tpe => c.Expr[List[A]](a.tree)},
189210
)
190211
}
191212
}

Base/src/main/scala-3/typeclass/VersionSpecificRepeated.scala

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,27 +61,34 @@ trait VersionSpecificRepeated {
6161
* @param ifZero the Expr to use when combining zero items together.
6262
* If None, `'{$newAccumulator.result()}` will be used.
6363
* If Some, should be equivalent to but more efficient than the None expr.
64-
* @param ifOne the Expr to use when combining one scalar item together.
65-
* If None, `'{$newAccumulator.addOne($item).result()}` will be used.
66-
* If Some, should be equivalent to but more efficient than the None expr.
64+
* @param ifOneScalar the Expr to use when combining one scalar item together.
65+
* When not defined, `'{$newAccumulator.addOne($item).result()}` will be used.
66+
* The definition should be equivalent to but more efficient than the undefined expr.
67+
* @param ifOneSplice the Expr to use when combining one splice item together.
68+
* When not defined, `'{$newAccumulator.addAll($item).result()}` will be used.
69+
* The definition should be equivalent to but more efficient than the undefined expr.
6770
* @version 0.1.1
6871
*/
6972
def quotedFromSplicesUsingBuilder[A, Z](
7073
newAccumulator: Expr[Builder[A, Z]],
7174
ifZero: Option[() => Expr[Z]] = None,
72-
ifOne: Option[(Expr[A]) => Expr[Z]] = None,
75+
ifOneScalar: PartialFunction[Expr[A], Expr[Z]] = PartialFunction.empty,
76+
ifOneSplice: PartialFunction[Expr[Iterable[A]], Expr[Z]] = PartialFunction.empty
7377
)(using Quotes, Type[A], Type[Z], Type[Builder[A, Z]],
7478
): Repeated[SplicePiece[Expr, A], Expr[Z]] = {
7579
final class FromSplicesUsingBuilder extends Repeated[SplicePiece[Expr, A], Expr[Z]] {
7680
sealed trait Acc
7781
object AccZero extends Acc
78-
final class AccOne(val elem: Expr[A]) extends Acc
82+
final class AccOneScalar(val elem: Expr[A]) extends Acc
83+
final class AccOneSplice(val iter: Expr[Iterable[A]]) extends Acc
7984
final class AccMany extends Acc {
8085
val builder: Builder[Expr[Builder[A, Z]] => Expr[_], Expr[Z]] = List.newBuilder.mapResult(parts =>
8186
'{
8287
val accumulator: Builder[A, Z] = ${newAccumulator}
83-
${Expr.block(parts.map(part => part('accumulator)), '{()})}
84-
accumulator.result()
88+
${Expr.block(
89+
parts.map(part => part('accumulator)),
90+
'{accumulator.result()}
91+
)}
8592
}
8693
)
8794
}
@@ -91,24 +98,35 @@ trait VersionSpecificRepeated {
9198
case AccZero =>
9299
elem match {
93100
case _: SplicePiece.Zero[Expr] => AccZero
94-
case elemOne: SplicePiece.One[Expr, A] => new AccOne(elemOne.elem)
95-
case elemMany: SplicePiece.Many[Expr, A] => {
101+
case elemOne: SplicePiece.One[Expr, A] => new AccOneScalar(elemOne.elem)
102+
case elemMany: SplicePiece.Many[Expr, A] => new AccOneSplice(elemMany.iter)
103+
}
104+
case accOne: AccOneScalar => {
105+
elem match {
106+
case _: SplicePiece.Zero[Expr] => accOne
107+
case elemOne: SplicePiece.One[Expr, A] =>
108+
val retval = new AccMany()
109+
retval.builder += {(acc) => '{$acc.addOne(${accOne.elem})}}
110+
retval.builder += {(acc) => '{$acc.addOne(${elemOne.elem})}}
111+
retval
112+
case elemMany: SplicePiece.Many[Expr, A] =>
96113
val retval = new AccMany()
114+
retval.builder += {(acc) => '{$acc.addOne(${accOne.elem})}}
97115
retval.builder += {(acc) => '{$acc.addAll(${elemMany.iter})}}
98116
retval
99-
}
100117
}
101-
case accOne: AccOne => {
118+
}
119+
case accOne: AccOneSplice => {
102120
elem match {
103121
case _: SplicePiece.Zero[Expr] => accOne
104122
case elemOne: SplicePiece.One[Expr, A] =>
105123
val retval = new AccMany()
106-
retval.builder += {(acc) => '{$acc.addOne(${accOne.elem})}}
124+
retval.builder += {(acc) => '{$acc.addAll(${accOne.iter})}}
107125
retval.builder += {(acc) => '{$acc.addOne(${elemOne.elem})}}
108126
retval
109127
case elemMany: SplicePiece.Many[Expr, A] =>
110128
val retval = new AccMany()
111-
retval.builder += {(acc) => '{$acc.addOne(${accOne.elem})}}
129+
retval.builder += {(acc) => '{$acc.addAll(${accOne.iter})}}
112130
retval.builder += {(acc) => '{$acc.addAll(${elemMany.iter})}}
113131
retval
114132
}
@@ -129,7 +147,8 @@ trait VersionSpecificRepeated {
129147
def result(acc:Acc):Expr[Z] = {
130148
acc match {
131149
case AccZero => ifZero.map(_.apply()).getOrElse('{$newAccumulator.result()})
132-
case accOne: AccOne => ifOne.map(_.apply(accOne.elem)).getOrElse('{$newAccumulator.addOne(${accOne.elem}).result()})
150+
case accOne: AccOneScalar => ifOneScalar.applyOrElse(accOne.elem, e => '{$newAccumulator.addOne(${e}).result()})
151+
case accOne: AccOneSplice => ifOneSplice.applyOrElse(accOne.iter, es => '{$newAccumulator.addAll(${es}).result()})
133152
case accMany: AccMany => accMany.builder.result()
134153
}
135154
}
@@ -145,7 +164,8 @@ trait VersionSpecificRepeated {
145164
quotedFromSplicesUsingBuilder[A, List[A]](
146165
'{ List.newBuilder },
147166
Option(() => '{ List.empty }),
148-
Option((a: Expr[A]) => '{ List($a) }),
167+
{(a: Expr[A]) => '{List($a)}},
168+
{case '{ $xs: List[A] } => xs},
149169
)
150170
}
151171

Base/src/test/scala-3/typeclass/VersonSpecificRepeatedTest.scala

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,53 @@ final class QuotedConcatenateStringTest extends munit.FunSuite {
3131
final class QuotedFromSplicesUsingBuilderTest extends munit.FunSuite {
3232
inline def assertParseSuccess[Z](
3333
newAcc: Builder[Int, Z],
34-
ifZeroCond: Boolean,
35-
ifZero: () => Z,
36-
ifOneCond: Boolean,
37-
ifOne: (Int) => Z,
34+
ifZeroDefined: Boolean,
35+
ifZeroApply: () => Z,
36+
ifOneScalarDefined: Boolean,
37+
ifOneScalarApply: Int => Z,
38+
ifOneSpliceDefined: Boolean,
39+
ifOneSpliceApply: IterableOnce[Int] => Z,
3840
inline elems: (Int | Seq[Int]) *)(
3941
expecting: Z)(
4042
using loc:Location
4143
):Unit = ${
4244
QuotedFromSplicesUsingBuilderTestImpls.assertParseSuccessImpl(
43-
'this, 'newAcc, 'ifZeroCond, 'ifZero, 'ifOneCond, 'ifOne, 'elems, 'expecting, 'loc)
45+
'this,
46+
'newAcc,
47+
'ifZeroDefined, 'ifZeroApply,
48+
'ifOneScalarDefined, 'ifOneScalarApply,
49+
'ifOneSpliceDefined, 'ifOneSpliceApply,
50+
'elems,
51+
'expecting,
52+
'loc
53+
)
4454
}
4555

4656
test ("0 items with ifZero") {
47-
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x)(-1)
57+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, true, x => x)(-1)
4858
}
4959
test ("0 items without ifZero") {
50-
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x)(Nil)
60+
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x, false, x => x)(Nil)
5161
}
52-
test ("1 items with ifOne") {
53-
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, 2)(2)
62+
test ("1 scalar with ifOneScalar") {
63+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, true, x => x, 2)(2)
5464
}
55-
test ("1 items without ifOne") {
56-
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x, 2)(2 :: Nil)
65+
test ("1 scalar without ifOneScalar") {
66+
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x, false, x => x, 2)(2 :: Nil)
5767
}
58-
test ("multiple individual items") {
59-
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x, 1,2,3)(1 :: 2 :: 3 :: Nil)
68+
test ("multiple scalars") {
69+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, true, x => x, 1,2,3)(1 :: 2 :: 3 :: Nil)
6070
}
61-
test ("Splice items") {
62-
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, Seq(1,2))(1 :: 2 :: Nil)
71+
test ("1 splice with ifOneSplice") {
72+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, true, x => x.iterator.mkString, Seq(1,2))("12")
73+
}
74+
test ("1 splice without ifOneSplice") {
75+
assertParseSuccess(List.newBuilder, false, () => -1, false, x => x, false, x => x.iterator.mkString, Seq(1,2))(1 :: 2 :: Nil)
76+
}
77+
test ("multiple splices") {
78+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, true, x => x, Seq(1,2), Seq(3,4))(1 :: 2 :: 3 :: 4 :: Nil)
79+
}
80+
test ("mix") {
81+
assertParseSuccess(List.newBuilder, true, () => -1, true, x => x, true, x => x, 42, Seq(1,2), 151)(42 :: 1 :: 2 :: 151 :: Nil)
6382
}
6483
}

Base/src/test/scala-3/typeclass/VersonSpecificRepeatedTestImpls.scala

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ object QuotedFromSplicesUsingBuilderTestImpls {
3535
def assertParseSuccessImpl[Int, Z](
3636
self: Expr[munit.FunSuite],
3737
newAcc: Expr[Builder[Int,Z]],
38-
ifZeroCondExpr: Expr[Boolean],
39-
ifZero: Expr[() => Z],
40-
ifOneCondExpr: Expr[Boolean],
41-
ifOne: Expr[(Int) => Z],
38+
ifZeroDefined: Expr[Boolean],
39+
ifZeroApply: Expr[() => Z],
40+
ifOneScalarDefined: Expr[Boolean],
41+
ifOneScalarApply: Expr[(Int) => Z],
42+
ifOneSpliceDefined: Expr[Boolean],
43+
ifOneSpliceApply: Expr[(IterableOnce[Int]) => Z],
4244
elems: Expr[Seq[Int | Seq[Int]]],
4345
expecting: Expr[Z],
4446
loc: Expr[Location])(
@@ -49,13 +51,20 @@ object QuotedFromSplicesUsingBuilderTestImpls {
4951
case '{ $x: Int } => SplicePiece.One[Expr, Int](x)
5052
case '{ $xs: Seq[Int] } => SplicePiece.Many[Expr, Int](xs)
5153

52-
val ifZeroCond = ifZeroCondExpr.valueOrAbort
53-
val ifOneCond = ifOneCondExpr.valueOrAbort
54+
val ifZeroDefinedAt2 = ifZeroDefined.valueOrAbort
55+
val ifZero = Option.when(ifZeroDefinedAt2)({() => '{${ifZeroApply}()}})
5456

55-
val ifZero2 = Option.when(ifZeroCond)({() => '{${ifZero}()}})
56-
val ifOne2 = Option.when(ifOneCond)({(a:Expr[Int]) => '{${ifOne}(${a})}})
57+
val ifOneScalar = new PartialFunction[Expr[Int], Expr[Z]] {
58+
def isDefinedAt(x:Expr[Int]):Boolean = ifOneScalarDefined.valueOrAbort
59+
def apply(x:Expr[Int]):Expr[Z] = '{${ifOneScalarApply}($x)}
60+
}
61+
62+
val ifOneSplice = new PartialFunction[Expr[IterableOnce[Int]], Expr[Z]] {
63+
def isDefinedAt(x:Expr[IterableOnce[Int]]):Boolean = ifOneSpliceDefined.valueOrAbort
64+
def apply(x:Expr[IterableOnce[Int]]):Expr[Z] = '{${ifOneSpliceApply}($x)}
65+
}
5766

58-
val dut = Repeated.quotedFromSplicesUsingBuilder(newAcc, ifZero2, ifOne2)
67+
val dut = Repeated.quotedFromSplicesUsingBuilder(newAcc, ifZero, ifOneScalar, ifOneSplice)
5968

6069
val actual = dut.result(elems3.foldLeft(dut.init())((acc, elem) => dut.append(acc, elem)))
6170

0 commit comments

Comments
 (0)