Skip to content

Generate specialized Functional Interfaces for Function{0,1,2} #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 17, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
target
.cache
.classpath
.project
.settings/
.target/
bin/
/.cache
/.classpath
/.project
/.settings
/.target
/bin
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ script:
- sbt ++$TRAVIS_SCALA_VERSION clean test publishLocal
scala:
- 2.10.4
- 2.11.0-RC3
- 2.11.4
jdk:
- oraclejdk8
notifications:
Expand Down
27 changes: 19 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import com.typesafe.tools.mima.plugin.{MimaPlugin, MimaKeys}

scalaModuleSettings

scalaVersion := "2.10.4"
scalaVersion := "2.11.4"

snapshotScalaBinaryVersion := "2.10.4"
snapshotScalaBinaryVersion := "2.11.4"

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

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

sourceGenerators in Compile <+= sourceManaged in Compile map { dir =>
def write(name: String, content: String) = {
val f = dir / "java" / "scala" / "compat" / "java8" / s"${name}.java"
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
IO.write(f, content)
f
}
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)))
(
Seq(write("JFunction", CodeGen.factory)) ++
(0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++
(0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++
CodeGen.specializedF0.map((write _).tupled) ++
CodeGen.specializedF1.map((write _).tupled) ++
CodeGen.specializedF2.map((write _).tupled)
)
}

sourceGenerators in Test <+= sourceManaged in Test map { dir =>
def write(name: String, content: String) = {
val f = dir / "java" / "scala" / "compat" / "java8" / s"${name}.java"
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
IO.write(f, content)
f
}
Expand All @@ -62,24 +69,28 @@ initialize := {
sys.error("Java 8 or higher is required for this project.")
}

val disableDocs = sys.props("nodocs") == "true"

publishArtifact in packageDoc := !disableDocs

lazy val JavaDoc = config("genjavadoc") extend Compile

sources in (Compile, doc) := {
val orig = (sources in (Compile, doc)).value
orig.filterNot(_.getName.endsWith(".java")) // raw types not cooked by scaladoc: https://issues.scala-lang.org/browse/SI-8449
}

inConfig(JavaDoc)(Defaults.configSettings) ++ Seq(
inConfig(JavaDoc)(Defaults.configSettings) ++ (if (disableDocs) Nil else Seq(
packageDoc in Compile <<= packageDoc in JavaDoc,
sources in JavaDoc <<= (target, compile in Compile, sources in Compile) map {(t, c, s) =>
val allJavaSources = (t / "java" ** "*.java").get ++ s.filter(_.getName.endsWith(".java"))
allJavaSources.filterNot(_.getName.contains("FuturesConvertersImpl.java")) // this file triggers bugs in genjavadoc
},
javacOptions in JavaDoc := Seq(),
artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"),
libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" %% "genjavadoc-plugin" % "0.5" cross CrossVersion.full),
libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" % "genjavadoc-plugin" % "0.8" cross CrossVersion.full),
scalacOptions in Compile <+= target map (t => "-P:genjavadoc:out=" + (t / "java"))
)
))

initialCommands :=
"""|import scala.concurrent._
Expand Down
224 changes: 191 additions & 33 deletions project/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
* Copyright (C) 2012-2014 Typesafe Inc. <http://www.typesafe.com>
*/

sealed abstract class Type(val code: Char, val prim: String, val ref: String)
sealed abstract class Type(val code: Char, val prim: String, val ref: String) {
def boxed: String = ref
}
object Type {
case object Boolean extends Type('Z', "boolean", "Boolean")
case object Byte extends Type('B', "byte", "Byte")
Expand All @@ -12,7 +14,7 @@ object Type {
case object Float extends Type('F', "float", "Float")
case object Double extends Type('D', "double", "Double")
case object Long extends Type('J', "long", "Long")
case object Void extends Type('V', "void", "Void")
case object Void extends Type('V', "void", "BoxedUnit")
case object Object extends Type('L', "Object", "Object")
}

Expand Down Expand Up @@ -95,33 +97,106 @@ object CodeGen {
val vparams = csv(n => s"T$n t$n")
val vparamRefs = csv(n => s"t$n")
val parent = "JFunction" + n
s"""
|$copyright
|
|$packaging
|
|import scala.runtime.BoxedUnit;
|
|@FunctionalInterface
|public interface JProcedure${n}<${tparams}> extends ${parent}<$tparams, BoxedUnit> {
| default void $initName() {
| }
|
| void applyVoid($vparams);
|
| default BoxedUnit apply($vparams) {
| applyVoid($vparamRefs);
| return BoxedUnit.UNIT;
| }
|}
|""".stripMargin
if (n == 0)
s"""
|$copyright
|
|$packaging
|
|import scala.runtime.BoxedUnit;
|
|@FunctionalInterface
|public interface JProcedure0 extends ${parent}<BoxedUnit> {
| default void $initName() {
| }
|
| void applyVoid();
|
| default BoxedUnit apply() {
| applyVoid();
| return BoxedUnit.UNIT;
| }
|}
|""".stripMargin
else
s"""
|$copyright
|
|$packaging
|
|import scala.runtime.BoxedUnit;
|
|@FunctionalInterface
|public interface JProcedure${n}<${tparams}> extends ${parent}<$tparams, BoxedUnit> {
| default void $initName() {
| }
|
| void applyVoid($vparams);
|
| default BoxedUnit apply($vparams) {
| applyVoid($vparamRefs);
| return BoxedUnit.UNIT;
| }
|}
|""".stripMargin
}

def factory: String = {
s"""
|public static <$tparams, R> scala.Function$n<$tparams, R> func(JFunction$n<$tparams, R> f) { return f; }
|public static <$tparams> scala.Function$n<$tparams, BoxedUnit> proc(JProcedure$n<$tparams> p) { return p; }
|""".stripMargin.trim
val specializedFactories = this.n match {
case 0 =>
val tparamNames = function0Spec.map(_._1)

def specFactory(tps: List[Type]) = {
val List(r) = tps
val suffix = specializedSuffix(tparamNames, tps)
val name = (if (r == Type.Void) "proc" else "func") + "Specialized"
s"public static scala.Function0<${r.ref}> $name(JFunction0$suffix f) { return f; }"
}

for {
variantTypes <- crossProduct(function0Spec.map(_._2))
} yield specFactory(variantTypes)
case 1 =>
val tparamNames = function1Spec.map(_._1)

def specFactory(tps: List[Type]) = {
val List(t, r) = tps
val suffix = specializedSuffix(tparamNames, tps)
val name = (if (r == Type.Void) "proc" else "func") + "Specialized"
s"public static scala.Function1<${t.ref}, ${r.ref}> $name(JFunction1$suffix f) { return f; }"
}

for {
variantTypes <- crossProduct(function1Spec.map(_._2))
} yield specFactory(variantTypes)
case 2 =>
val tparamNames = function2Spec.map(_._1)

def specFactory(tps: List[Type]) = {
val List(t1, t2, r) = tps
val suffix = specializedSuffix(tparamNames, tps)
val name = (if (r == Type.Void) "proc" else "func") + "Specialized"
s"public static scala.Function2<${t1.ref}, ${t2.ref}, ${r.ref}> $name(JFunction2$suffix f) { return f; }"
}

for {
variantTypes <- crossProduct(function2Spec.map(_._2))
} yield specFactory(variantTypes)
case _ =>
Nil
}
if (n == 0)
s"""
|public static <R> scala.Function$n<R> func(JFunction$n<R> f) { return f; }
|public static scala.Function$n<BoxedUnit> proc(JProcedure$n p) { return p; }
|${specializedFactories.mkString("\n")}
|""".stripMargin.trim
else
s"""
|public static <$tparams, R> scala.Function$n<$tparams, R> func(JFunction$n<$tparams, R> f) { return f; }
|public static <$tparams> scala.Function$n<$tparams, BoxedUnit> proc(JProcedure$n<$tparams> p) { return p; }
|${specializedFactories.mkString("\n")}
|""".stripMargin.trim
}

def accept: String = {
Expand All @@ -138,6 +213,69 @@ object CodeGen {
}
}

def f0Specialized(tps: List[Type]): (String, String) = {
val tparamNames = function0Spec.map(_._1)
val suffix = specializedSuffix(tparamNames, tps)
val List(r) = tps
val applyMethodBody = if (r == Type.Void) s"apply$suffix(); return scala.runtime.BoxedUnit.UNIT;"
else s"return (${r.ref}) apply$suffix();"
val code = s"""
|$copyright
|
|$packaging
|
|@FunctionalInterface
|public interface JFunction0$suffix extends JFunction0 {
| abstract ${r.prim} apply$suffix();
|
| default Object apply() { $applyMethodBody }
|}
|""".stripMargin
(s"JFunction0$suffix", code)
}


def f1Specialized(tps: List[Type]): (String, String) = {
val tparamNames = function1Spec.map(_._1)
val suffix = specializedSuffix(tparamNames, tps)
val List(t, r) = tps
val applyMethodBody = if (r == Type.Void) s"apply$suffix((${t.ref}) t); return scala.runtime.BoxedUnit.UNIT;"
else s"return (${r.ref}) apply$suffix((${t.ref}) t);"
val code = s"""
|$copyright
|
|$packaging
|
|@FunctionalInterface
|public interface JFunction1$suffix extends JFunction1 {
| abstract ${r.prim} apply$suffix(${t.prim} v1);
|
| default Object apply(Object t) { $applyMethodBody }
|}
|""".stripMargin
(s"JFunction1$suffix", code)
}

def f2Specialized(tps: List[Type]): (String, String) = {
val tparamNames = function2Spec.map(_._1)
val suffix = specializedSuffix(tparamNames, tps)
val List(t1, t2, r) = tps
val applyMethodBody = if (r == Type.Void) s"apply$suffix((${t1.ref}) v1, (${t2.ref}) v2); return scala.runtime.BoxedUnit.UNIT;"
else s"return (${r.ref}) apply$suffix((${t1.ref}) v1, (${t2.ref}) v2);"
val code = s"""
|$copyright
|
|$packaging
|
|@FunctionalInterface
|public interface JFunction2$suffix extends JFunction2 {
| abstract ${r.prim} apply$suffix(${t1.prim} v1, ${t2.prim} v2);
|
| default Object apply(Object v1, Object v2) { $applyMethodBody }
|}
|""".stripMargin
(s"JFunction2$suffix", code)
}

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

def specializedSuffix(tparamNames: List[String], tps: List[Type]): String = {
val sorted = (tps zip tparamNames).sortBy(_._2).map(_._1) // as per scalac, sort by tparam name before assembling the code
val code = sorted.map(_.code).mkString
"$mc" + code + "$sp"
}

private def specialized(name: String, tps: List[(String, List[Type])])(f: (String, List[Type]) => String): String = {
val tparamNames = tps.map(_._1)
def code(tps: List[Type]) = {
val sorted = (tps zip tparamNames).sortBy(_._2).map(_._1) // as per scalac, sort by tparam name before assembling the code
sorted.map(_.code).mkString
}
val ms = for {
variantTypes <- crossProduct(tps.map(_._2))
specName = name + "$mc" + code(variantTypes) + "$sp"
specName = name + specializedSuffix(tparamNames, variantTypes)
} yield f(specName, variantTypes)
ms.mkString("\n")
}
Expand All @@ -267,10 +407,29 @@ object CodeGen {
List(header, specializedVariants, trailer).mkString
}

def specializedF0: List[(String, String)] = {
val tparamNames = function0Spec.map(_._1)
for {
variantTypes <- crossProduct(function0Spec.map(_._2))
} yield f0Specialized(variantTypes)
}
def specializedF1: List[(String, String)] = {
val tparamNames = function1Spec.map(_._1)
for {
variantTypes <- crossProduct(function1Spec.map(_._2))
} yield f1Specialized(variantTypes)
}
def specializedF2: List[(String, String)] = {
val tparamNames = function2Spec.map(_._1)
for {
variantTypes <- crossProduct(function2Spec.map(_._2))
} yield f2Specialized(variantTypes)
}

def pN(n: Int) = arity(n).pN

def factory: String = {
val ms = (1 to 22).map(n => arity(n).factory).mkString("\n")
val ms = (0 to 22).map(n => arity(n).factory).mkString("\n")
s"""
|$copyright
|
Expand All @@ -280,7 +439,6 @@ object CodeGen {
|
|public final class JFunction {
| private JFunction() {}
| public static <R> scala.Function0<R> func(JFunction0<R> f) { return f; }
|${indent(ms)}
|}
|
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.2-M2
sbt.version=0.13.6
2 changes: 1 addition & 1 deletion src/test/java/scala/compat/java8/LambdaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

public class LambdaTest {
@Test
public static void lambdaDemo() {
public void lambdaDemo() {
// Not allowed with Scala 2.10 nor 2.11
// "incompatible types: Function1 is not a functional interface"
// scala.Function1<String, String> f = (String s) -> s;
Expand Down
Loading