Skip to content

Commit fd4eb69

Browse files
committed
Generate specialized Functional Interfaces for Function{0,1,2}
As exerciced in the test, a specialized (Int => Int) function can be implemented by extending the functional interface `JFunction1$mcII$sp`. Representative example of the generated code: ```java public interface JFunction0$mcV$sp extends JFunction0 { abstract void apply$mcV$sp(); default Object apply() { apply$mcV$sp(); return scala.runtime.BoxedUnit.UNIT; } } public interface JFunction0$mcZ$sp extends JFunction0 { abstract boolean apply$mcZ$sp(); default Object apply() { return (Boolean) apply$mcZ$sp(); } } public interface JFunction1$mcDD$sp extends JFunction1 { abstract double apply$mcDD$sp(double v1); default Object apply(Object t) { return (Double) apply$mcDD$sp((Double) t); } } public interface JFunction1$mcVD$sp extends JFunction1 { abstract void apply$mcVD$sp(double v1); default Object apply(Object t) { apply$mcVD$sp((Double) t); return scala.runtime.BoxedUnit.UNIT; } } public final class JFunction { private JFunction() {} public static <R> scala.Function0<R> func(JFunction0<R> f) { return f; } public static scala.Function0<BoxedUnit> proc(JProcedure0 p) { return p; } public static scala.Function0<BoxedUnit> procSpecialized(JFunction0$mcV$sp f) { return f; } public static scala.Function0<Byte> funcSpecialized(JFunction0$mcB$sp f) { return f; } public static scala.Function0<Short> funcSpecialized(JFunction0$mcS$sp f) { return f; } public static scala.Function0<Integer> funcSpecialized(JFunction0$mcI$sp f) { return f; } public static scala.Function0<Long> funcSpecialized(JFunction0$mcJ$sp f) { return f; } public static scala.Function0<Character> funcSpecialized(JFunction0$mcC$sp f) { return f; } public static scala.Function0<Float> funcSpecialized(JFunction0$mcF$sp f) { return f; } public static scala.Function0<Double> funcSpecialized(JFunction0$mcD$sp f) { return f; } public static scala.Function0<Boolean> funcSpecialized(JFunction0$mcZ$sp f) { return f; } public static <T1, R> scala.Function1<T1, R> func(JFunction1<T1, R> f) { return f; } public static <T1> scala.Function1<T1, BoxedUnit> proc(JProcedure1<T1> p) { return p; } public static scala.Function1<Integer, BoxedUnit> procSpecialized(JFunction1$mcVI$sp f) { return f; } public static scala.Function1<Integer, Boolean> funcSpecialized(JFunction1$mcZI$sp f) { return f; } ``` I've also modified the code generator to create `JProcedure0` (which was an oversight), and to switch to Scala 2.11.4.
1 parent 00ac271 commit fd4eb69

File tree

7 files changed

+250
-41
lines changed

7 files changed

+250
-41
lines changed

build.sbt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import com.typesafe.tools.mima.plugin.{MimaPlugin, MimaKeys}
22

33
scalaModuleSettings
44

5-
scalaVersion := "2.10.4"
5+
scalaVersion := "2.11.4"
66

7-
snapshotScalaBinaryVersion := "2.10.4"
7+
snapshotScalaBinaryVersion := "2.11.4"
88

99
// TODO this project can be cross build against 2.11 and 2.10, express that here.
1010

@@ -37,16 +37,23 @@ testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a")
3737

3838
sourceGenerators in Compile <+= sourceManaged in Compile map { dir =>
3939
def write(name: String, content: String) = {
40-
val f = dir / "java" / "scala" / "compat" / "java8" / s"${name}.java"
40+
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
4141
IO.write(f, content)
4242
f
4343
}
44-
Seq(write("JFunction", CodeGen.factory)) ++ (0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++ (1 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n)))
44+
(
45+
Seq(write("JFunction", CodeGen.factory)) ++
46+
(0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++
47+
(0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++
48+
CodeGen.specializedF0.map((write _).tupled) ++
49+
CodeGen.specializedF1.map((write _).tupled) ++
50+
CodeGen.specializedF2.map((write _).tupled)
51+
)
4552
}
4653

4754
sourceGenerators in Test <+= sourceManaged in Test map { dir =>
4855
def write(name: String, content: String) = {
49-
val f = dir / "java" / "scala" / "compat" / "java8" / s"${name}.java"
56+
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
5057
IO.write(f, content)
5158
f
5259
}
@@ -77,7 +84,7 @@ inConfig(JavaDoc)(Defaults.configSettings) ++ Seq(
7784
},
7885
javacOptions in JavaDoc := Seq(),
7986
artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"),
80-
libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" %% "genjavadoc-plugin" % "0.5" cross CrossVersion.full),
87+
libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" % "genjavadoc-plugin" % "0.8" cross CrossVersion.full),
8188
scalacOptions in Compile <+= target map (t => "-P:genjavadoc:out=" + (t / "java"))
8289
)
8390

project/CodeGen.scala

Lines changed: 191 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
* Copyright (C) 2012-2014 Typesafe Inc. <http://www.typesafe.com>
33
*/
44

5-
sealed abstract class Type(val code: Char, val prim: String, val ref: String)
5+
sealed abstract class Type(val code: Char, val prim: String, val ref: String) {
6+
def boxed: String = ref
7+
}
68
object Type {
79
case object Boolean extends Type('Z', "boolean", "Boolean")
810
case object Byte extends Type('B', "byte", "Byte")
@@ -12,7 +14,7 @@ object Type {
1214
case object Float extends Type('F', "float", "Float")
1315
case object Double extends Type('D', "double", "Double")
1416
case object Long extends Type('J', "long", "Long")
15-
case object Void extends Type('V', "void", "Void")
17+
case object Void extends Type('V', "void", "BoxedUnit")
1618
case object Object extends Type('L', "Object", "Object")
1719
}
1820

@@ -95,33 +97,106 @@ object CodeGen {
9597
val vparams = csv(n => s"T$n t$n")
9698
val vparamRefs = csv(n => s"t$n")
9799
val parent = "JFunction" + n
98-
s"""
99-
|$copyright
100-
|
101-
|$packaging
102-
|
103-
|import scala.runtime.BoxedUnit;
104-
|
105-
|@FunctionalInterface
106-
|public interface JProcedure${n}<${tparams}> extends ${parent}<$tparams, BoxedUnit> {
107-
| default void $initName() {
108-
| }
109-
|
110-
| void applyVoid($vparams);
111-
|
112-
| default BoxedUnit apply($vparams) {
113-
| applyVoid($vparamRefs);
114-
| return BoxedUnit.UNIT;
115-
| }
116-
|}
117-
|""".stripMargin
100+
if (n == 0)
101+
s"""
102+
|$copyright
103+
|
104+
|$packaging
105+
|
106+
|import scala.runtime.BoxedUnit;
107+
|
108+
|@FunctionalInterface
109+
|public interface JProcedure0 extends ${parent}<BoxedUnit> {
110+
| default void $initName() {
111+
| }
112+
|
113+
| void applyVoid();
114+
|
115+
| default BoxedUnit apply() {
116+
| applyVoid();
117+
| return BoxedUnit.UNIT;
118+
| }
119+
|}
120+
|""".stripMargin
121+
else
122+
s"""
123+
|$copyright
124+
|
125+
|$packaging
126+
|
127+
|import scala.runtime.BoxedUnit;
128+
|
129+
|@FunctionalInterface
130+
|public interface JProcedure${n}<${tparams}> extends ${parent}<$tparams, BoxedUnit> {
131+
| default void $initName() {
132+
| }
133+
|
134+
| void applyVoid($vparams);
135+
|
136+
| default BoxedUnit apply($vparams) {
137+
| applyVoid($vparamRefs);
138+
| return BoxedUnit.UNIT;
139+
| }
140+
|}
141+
|""".stripMargin
118142
}
119143

120144
def factory: String = {
121-
s"""
122-
|public static <$tparams, R> scala.Function$n<$tparams, R> func(JFunction$n<$tparams, R> f) { return f; }
123-
|public static <$tparams> scala.Function$n<$tparams, BoxedUnit> proc(JProcedure$n<$tparams> p) { return p; }
124-
|""".stripMargin.trim
145+
val specializedFactories = this.n match {
146+
case 0 =>
147+
val tparamNames = function0Spec.map(_._1)
148+
149+
def specFactory(tps: List[Type]) = {
150+
val List(r) = tps
151+
val suffix = specializedSuffix(tparamNames, tps)
152+
val name = (if (r == Type.Void) "proc" else "func") + "Specialized"
153+
s"public static scala.Function0<${r.ref}> $name(JFunction0$suffix f) { return f; }"
154+
}
155+
156+
for {
157+
variantTypes <- crossProduct(function0Spec.map(_._2))
158+
} yield specFactory(variantTypes)
159+
case 1 =>
160+
val tparamNames = function1Spec.map(_._1)
161+
162+
def specFactory(tps: List[Type]) = {
163+
val List(t, r) = tps
164+
val suffix = specializedSuffix(tparamNames, tps)
165+
val name = (if (r == Type.Void) "proc" else "func") + "Specialized"
166+
s"public static scala.Function1<${t.ref}, ${r.ref}> $name(JFunction1$suffix f) { return f; }"
167+
}
168+
169+
for {
170+
variantTypes <- crossProduct(function1Spec.map(_._2))
171+
} yield specFactory(variantTypes)
172+
case 2 =>
173+
val tparamNames = function2Spec.map(_._1)
174+
175+
def specFactory(tps: List[Type]) = {
176+
val List(t1, t2, r) = tps
177+
val suffix = specializedSuffix(tparamNames, tps)
178+
val name = (if (r == Type.Void) "proc" else "func") + "Specialized"
179+
s"public static scala.Function2<${t1.ref}, ${t2.ref}, ${r.ref}> $name(JFunction2$suffix f) { return f; }"
180+
}
181+
182+
for {
183+
variantTypes <- crossProduct(function2Spec.map(_._2))
184+
} yield specFactory(variantTypes)
185+
case _ =>
186+
Nil
187+
}
188+
if (n == 0)
189+
s"""
190+
|public static <R> scala.Function$n<R> func(JFunction$n<R> f) { return f; }
191+
|public static scala.Function$n<BoxedUnit> proc(JProcedure$n p) { return p; }
192+
|${specializedFactories.mkString("\n")}
193+
|""".stripMargin.trim
194+
else
195+
s"""
196+
|public static <$tparams, R> scala.Function$n<$tparams, R> func(JFunction$n<$tparams, R> f) { return f; }
197+
|public static <$tparams> scala.Function$n<$tparams, BoxedUnit> proc(JProcedure$n<$tparams> p) { return p; }
198+
|${specializedFactories.mkString("\n")}
199+
|""".stripMargin.trim
125200
}
126201

127202
def accept: String = {
@@ -138,6 +213,69 @@ object CodeGen {
138213
}
139214
}
140215

216+
def f0Specialized(tps: List[Type]): (String, String) = {
217+
val tparamNames = function0Spec.map(_._1)
218+
val suffix = specializedSuffix(tparamNames, tps)
219+
val List(r) = tps
220+
val applyMethodBody = if (r == Type.Void) s"apply$suffix(); return scala.runtime.BoxedUnit.UNIT;"
221+
else s"return (${r.ref}) apply$suffix();"
222+
val code = s"""
223+
|$copyright
224+
|
225+
|$packaging
226+
|
227+
|@FunctionalInterface
228+
|public interface JFunction0$suffix extends JFunction0 {
229+
| abstract ${r.prim} apply$suffix();
230+
|
231+
| default Object apply() { $applyMethodBody }
232+
|}
233+
|""".stripMargin
234+
(s"JFunction0$suffix", code)
235+
}
236+
237+
238+
def f1Specialized(tps: List[Type]): (String, String) = {
239+
val tparamNames = function1Spec.map(_._1)
240+
val suffix = specializedSuffix(tparamNames, tps)
241+
val List(t, r) = tps
242+
val applyMethodBody = if (r == Type.Void) s"apply$suffix((${t.ref}) t); return scala.runtime.BoxedUnit.UNIT;"
243+
else s"return (${r.ref}) apply$suffix((${t.ref}) t);"
244+
val code = s"""
245+
|$copyright
246+
|
247+
|$packaging
248+
|
249+
|@FunctionalInterface
250+
|public interface JFunction1$suffix extends JFunction1 {
251+
| abstract ${r.prim} apply$suffix(${t.prim} v1);
252+
|
253+
| default Object apply(Object t) { $applyMethodBody }
254+
|}
255+
|""".stripMargin
256+
(s"JFunction1$suffix", code)
257+
}
258+
259+
def f2Specialized(tps: List[Type]): (String, String) = {
260+
val tparamNames = function2Spec.map(_._1)
261+
val suffix = specializedSuffix(tparamNames, tps)
262+
val List(t1, t2, r) = tps
263+
val applyMethodBody = if (r == Type.Void) s"apply$suffix((${t1.ref}) v1, (${t2.ref}) v2); return scala.runtime.BoxedUnit.UNIT;"
264+
else s"return (${r.ref}) apply$suffix((${t1.ref}) v1, (${t2.ref}) v2);"
265+
val code = s"""
266+
|$copyright
267+
|
268+
|$packaging
269+
|
270+
|@FunctionalInterface
271+
|public interface JFunction2$suffix extends JFunction2 {
272+
| abstract ${r.prim} apply$suffix(${t1.prim} v1, ${t2.prim} v2);
273+
|
274+
| default Object apply(Object v1, Object v2) { $applyMethodBody }
275+
|}
276+
|""".stripMargin
277+
(s"JFunction2$suffix", code)
278+
}
141279

142280
private val initName = "$init$"
143281
private val function1ImplClass = "scala.Function1$class"
@@ -236,15 +374,17 @@ object CodeGen {
236374
indent(List(apply, curried, tupled).mkString("\n\n"))
237375
}
238376

377+
def specializedSuffix(tparamNames: List[String], tps: List[Type]): String = {
378+
val sorted = (tps zip tparamNames).sortBy(_._2).map(_._1) // as per scalac, sort by tparam name before assembling the code
379+
val code = sorted.map(_.code).mkString
380+
"$mc" + code + "$sp"
381+
}
382+
239383
private def specialized(name: String, tps: List[(String, List[Type])])(f: (String, List[Type]) => String): String = {
240384
val tparamNames = tps.map(_._1)
241-
def code(tps: List[Type]) = {
242-
val sorted = (tps zip tparamNames).sortBy(_._2).map(_._1) // as per scalac, sort by tparam name before assembling the code
243-
sorted.map(_.code).mkString
244-
}
245385
val ms = for {
246386
variantTypes <- crossProduct(tps.map(_._2))
247-
specName = name + "$mc" + code(variantTypes) + "$sp"
387+
specName = name + specializedSuffix(tparamNames, variantTypes)
248388
} yield f(specName, variantTypes)
249389
ms.mkString("\n")
250390
}
@@ -267,10 +407,29 @@ object CodeGen {
267407
List(header, specializedVariants, trailer).mkString
268408
}
269409

410+
def specializedF0: List[(String, String)] = {
411+
val tparamNames = function0Spec.map(_._1)
412+
for {
413+
variantTypes <- crossProduct(function0Spec.map(_._2))
414+
} yield f0Specialized(variantTypes)
415+
}
416+
def specializedF1: List[(String, String)] = {
417+
val tparamNames = function1Spec.map(_._1)
418+
for {
419+
variantTypes <- crossProduct(function1Spec.map(_._2))
420+
} yield f1Specialized(variantTypes)
421+
}
422+
def specializedF2: List[(String, String)] = {
423+
val tparamNames = function2Spec.map(_._1)
424+
for {
425+
variantTypes <- crossProduct(function2Spec.map(_._2))
426+
} yield f2Specialized(variantTypes)
427+
}
428+
270429
def pN(n: Int) = arity(n).pN
271430

272431
def factory: String = {
273-
val ms = (1 to 22).map(n => arity(n).factory).mkString("\n")
432+
val ms = (0 to 22).map(n => arity(n).factory).mkString("\n")
274433
s"""
275434
|$copyright
276435
|
@@ -280,7 +439,6 @@ object CodeGen {
280439
|
281440
|public final class JFunction {
282441
| private JFunction() {}
283-
| public static <R> scala.Function0<R> func(JFunction0<R> f) { return f; }
284442
|${indent(ms)}
285443
|}
286444
|

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=0.13.2-M2
1+
sbt.version=0.13.6

src/test/java/scala/compat/java8/LambdaTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
public class LambdaTest {
1313
@Test
14-
public static void lambdaDemo() {
14+
public void lambdaDemo() {
1515
// Not allowed with Scala 2.10 nor 2.11
1616
// "incompatible types: Function1 is not a functional interface"
1717
// scala.Function1<String, String> f = (String s) -> s;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package scala.compat.java8;
2+
3+
import org.junit.Test;
4+
import scala.runtime.BoxedUnit;
5+
6+
public class SpecializedFactoryTest {
7+
@Test public void intIntFunction() {
8+
scala.Function1<Integer, Integer> f1 = JFunction.funcSpecialized((int x) -> x);
9+
assert(f1 instanceof JFunction1$mcII$sp);
10+
11+
scala.Function1<Integer, BoxedUnit> f2 = JFunction.procSpecialized((int x) -> System.out.print(""));
12+
assert(f2 instanceof JFunction1$mcVI$sp);
13+
14+
scala.Function0<BoxedUnit> f3 = JFunction.procSpecialized(() -> System.out.print(""));
15+
assert (f3 instanceof JFunction0$mcV$sp);
16+
}
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.compat.java8
2+
3+
import org.junit.Test
4+
import scala.compat.java8.SpecializedTestSupport.IntIdentity
5+
6+
class SpecializedTest {
7+
@Test def specializationWorks() {
8+
val intIdentity: (Int => Int) = new IntIdentity().asInstanceOf[Int => Int]
9+
intIdentity(24) // this function checks that it was called via the specialized apply variant.
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package scala.compat.java8;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
import org.junit.Assert;
6+
7+
public class SpecializedTestSupport {
8+
public static class IntIdentity implements JFunction1$mcII$sp {
9+
public int apply$mcII$sp(int i) {
10+
List<StackTraceElement> stackTrace = Arrays.asList(Thread.currentThread().getStackTrace());
11+
long count = stackTrace.stream().filter(x -> x.getMethodName().equals("apply")).count();
12+
Assert.assertEquals("the unspecialized apply method should not have been called", 0, count);
13+
return i;
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)