Skip to content

Restore backwards compatibility in Scala 2.12.0-RC1 #78

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
Sep 9, 2016
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
25 changes: 13 additions & 12 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ val disableDocs = sys.props("nodocs") == "true"

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

def jwrite(dir: java.io.File)(name: String, content: String) = {
val f = dir / "scala" / "runtime" / "java8" / s"${name}.java"
def jwrite(dir: java.io.File, pck: String = "scala/compat/java8")(name: String, content: String) = {
val f = dir / pck / s"${name}.java"
IO.write(f, content)
f
}

lazy val commonSettings = Seq(
scalaVersion := "2.11.8",
crossScalaVersions := List("2.11.8", "2.12.0-M5"),
crossScalaVersions := List("2.12.0-RC1", "2.11.8"),
scalaVersion := crossScalaVersions.value.head,
organization := "org.scala-lang.modules",
version := "0.8.0-SNAPSHOT"
)
Expand Down Expand Up @@ -62,15 +62,16 @@ lazy val root = (project in file(".")).
}.taskValue,

sourceGenerators in Compile <+= (sourceManaged in Compile, scalaVersion) map { (dir, v) =>
val write = jwrite(dir) _
if(v.startsWith("2.11.")) {
val write = jwrite(dir) _
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)
} else Seq.empty
(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) ++
CodeGen.packageDummy.map((jwrite(dir, "java/runtime/java8") _).tupled)
} else CodeGen.create212.map(write.tupled)
},

sourceGenerators in Test <+= sourceManaged in Test map { dir =>
Expand All @@ -79,7 +80,7 @@ lazy val root = (project in file(".")).

initialize := {
// Run previously configured inialization...
initialize.value
val _ = initialize.value
// ... and then check the Java version.
val specVersion = sys.props("java.specification.version")
if (Set("1.5", "1.6", "1.7") contains specVersion)
Expand Down
4 changes: 2 additions & 2 deletions fnGen/WrapFnGen.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2015-2016 Lightbend Inc. <https://www.lightbend.com>
*/

object WrapFnGen {
val copyright =
s"""
|/*
| * Copyright (C) 2015, Typesafe Inc. <http://www.typesafe.com>
| * Copyright (C) 2015-2016, Lightbend Inc. <https://www.lightbend.com>
| * This file auto-generated by WrapFnGen.scala. Do not modify directly.
| */
|""".stripMargin
Expand Down
103 changes: 100 additions & 3 deletions project/CodeGen.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2014 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2012-2016 Lightbend Inc. <https://www.lightbend.com>
*/

sealed abstract class Type(val code: Char, val prim: String, val ref: String) {
Expand All @@ -25,7 +25,7 @@ object Type {
}

object CodeGen {
def packaging = "package scala.runtime.java8;"
def packaging = "package scala.compat.java8;"
case class arity(n: Int) {
val ns = (1 to n).toList

Expand Down Expand Up @@ -288,7 +288,7 @@ object CodeGen {
private val copyright =
"""
|/*
| * Copyright (C) 2012-2015 Typesafe Inc. <http://www.typesafe.com>
| * Copyright (C) 2012-2016 Lightbend Inc. <https://www.lightbend.com>
| */""".stripMargin.trim

private def function0SpecMethods = {
Expand Down Expand Up @@ -433,4 +433,101 @@ object CodeGen {
}

def indent(s: String) = s.linesIterator.map(" " + _).mkString("\n")

/** Create a dummy class to put into scala.runtime.java8 for Scala 2.11 so that wildcard imports from the
* package won't fail. This allows importing both `scala.runtime.java8.*` and `scala.compat.java8.*` for
* source compatibility between 2.11 and 2.12.
*/
def packageDummy: Seq[(String, String)] = Seq(
( "PackageDummy",
s"""$copyright
|
|package scala.runtime.java8;
|
|public final class PackageDummy {
| private PackageDummy() {}
|}
""".stripMargin)
)

/** Create the simpler JFunction and JProcedure sources for Scala 2.12+ */
def create212: Seq[(String, String)] = {
val blocks = for(i <- 0 to 22) yield {
val ts = (1 to i).map(i => s"T$i").mkString(", ")
val tsComma = if(ts.isEmpty) "" else s"$ts,"
val tsAngled = if(ts.isEmpty) "" else s"<$ts>"
val paramTs = (1 to i).map(i => s"T$i t$i").mkString(", ")
val argTs = (1 to i).map(i => s"t$i").mkString(", ")

(
( s"JFunction$i",
s"""$copyright
|$packaging
|
|/** @deprecated Use scala.Function$i in Scala 2.12 */
|@Deprecated
|@FunctionalInterface
|public interface JFunction$i<$tsComma R> extends scala.Function$i<$tsComma R>, java.io.Serializable {}
""".stripMargin),
( s"JProcedure$i",
s"""$copyright
|$packaging
|
|import scala.runtime.BoxedUnit;
|
|@FunctionalInterface
|public interface JProcedure$i$tsAngled extends scala.Function$i<$tsComma BoxedUnit> {
| void applyVoid($paramTs);
| default BoxedUnit apply($paramTs) { applyVoid($argTs); return BoxedUnit.UNIT; }
|}
""".stripMargin),
s""" /** @deprecated Not needed anymore in Scala 2.12 */
| @Deprecated
| public static <$tsComma R> scala.Function$i<$tsComma R> func(scala.Function$i<$tsComma R> f) { return f; }
| public static $tsAngled scala.Function$i<$tsComma BoxedUnit> proc(JProcedure$i$tsAngled p) { return p; }
""".stripMargin
)
}

def specialize(args: String): List[(Int, String, String)] = {
def combinations(l: List[String]): List[List[Char]] =
l.foldRight(List(Nil: List[Char])) { (s, z) => s.toList.flatMap(c => z.map(c :: _)) }
val split = args.split(",")
combinations(split.toList).map { s =>
val types = s.map {
case 'B' => "Byte"
case 'S' => "Short"
case 'V' => "BoxedUnit"
case 'I' => "Integer"
case 'J' => "Long"
case 'C' => "Character"
case 'F' => "Float"
case 'D' => "Double"
case 'Z' => "Boolean"
}
(split.length-1, (types.tail :+ types.head).mkString(", "), "$mc" + s.mkString + "$sp")
}
}

val specialized =
List("V", "V,IJFD", "V,IJD,IJD").flatMap(specialize).map { case (i, a, sp) =>
s" public static scala.Function$i<$a> procSpecialized(JFunction$i$sp f) { return f; }" } ++
List("BSIJCFDZ", "ZIFJD,IJFD", "ZIFJD,IJD,IJD").flatMap(specialize).map { case (i, a, sp) =>
s" public static scala.Function$i<$a> funcSpecialized(JFunction$i$sp f) { return f; }" }

(blocks.map(_._1) ++ blocks.map(_._2)) :+
( "JFunction",
s"""$copyright
|$packaging
|
|import scala.runtime.BoxedUnit;
|import scala.runtime.java8.*;
|
|public final class JFunction {
| private JFunction() {}
|${specialized.mkString("\n")}
|${blocks.map(_._3).mkString("\n")}
|}
""".stripMargin)
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/*
* Copyright (C) 2012-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package scala.runtime.java8;
package scala.compat.java8;

import org.junit.Test;
import scala.runtime.java8.*;

public class BoxingTest {
@Test
public void nullBoxesInterpretedAsZeroF1() {
JFunction1$mcII$sp jFunction1 = new JFunction1$mcII$sp() {
scala.Function1<Integer, Integer> jFunction1 = new JFunction1$mcII$sp() {
@Override
public int apply$mcII$sp(int v1) {
return v1 + 1;
Expand All @@ -20,7 +21,7 @@ public void nullBoxesInterpretedAsZeroF1() {

@Test
public void nullBoxesInterpretedAsZeroF2() {
JFunction2$mcIII$sp jFunction2 = new JFunction2$mcIII$sp() {
scala.Function2<Integer, Integer, Integer> jFunction2 = new JFunction2$mcIII$sp() {
@Override
public int apply$mcIII$sp(int v1, int v2) {
return v1 + v2 + 1;
Expand Down
147 changes: 147 additions & 0 deletions src/test/java/scala/compat/java8/LambdaTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (C) 2012-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package scala.compat.java8;

import org.apache.commons.lang3.SerializationUtils;
import scala.runtime.*;

import static junit.framework.Assert.assertEquals;
import static scala.compat.java8.JFunction.*;
import static scala.compat.java8.TestAPI.*;

import org.junit.Test;


public class LambdaTest {
@Test
public void lambdaDemo() {
// Scala 2.12+ only:
//scala.Function1<String, String> f1 = (String s) -> s;

// 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;

// Function1 is not a functional interface because it has abstract
// methods in addition to apply, namely `compose` and `andThen`
// (which are implemented in Scala-derived subclasses with mixin
// inheritance), and the specialized variants of apply (also provided
// by scalac.)

// That's a pity, but we can get pretty close with this library!

// We have to tell javac to use `JFunction1` as the functional interface.
// Scala 2.12 does not have or need JFunction anymore. We provide it as a
// deprecated stub for backwards compatibility. Use `scala.Function1` for
// code that targets Scala 2.12+ exclusively.
JFunction1<String, String> f1 = (String s) -> s;

// That's more or less equivalent to the old, anonymous class syntax:
new JFunction1<String, String>() {
public String apply(String s) { return s; }
};

// You might have seen this form before:
new AbstractFunction1<String, String>() {
public String apply(String s) { return s; }
};

// However, we can't use `AbstractFunction1` as a functional interface
// as it is a class. Further

// F1 is a subclass of Function1:
scala.Function1<String, String> f2 = f1;

// Factory methods in `JFunction` can reduce the verbosity a little:
// `func` is actually just an identity method; it only exists to
// trigger lambda creation using the `JFunction1` functional interface.
scala.Function1<String, String> f3 = func((String s) -> s);

// Note that javac's type inference can infer the parameter type here,
// based on the acribed type of `f4`.
scala.Function1<String, String> f4 = func(s -> s);

// f1.apply("");

// Specialized variants of the `apply` method are provided but implementing a specialized
// Scala function in this straight-forward way results in boxing and unboxing because the
// Java lambda operates on boxed types:
JFunction1<Integer, Integer> f5 = (i) -> -i;
assert(f5.apply(1) == -1);
assert(f5.apply$mcII$sp(1) == -1);

// We provide `JFunction.funcSpecialized` and `JFunction.procSpecialized` methods to avoid
// boxing:
scala.Function1<Integer, Integer> f5b = funcSpecialized((int i) -> -i);
assert(f5b.apply(1) == -1);
assert(f5b.apply$mcII$sp(1) == -1);

// as are `curried`, `tupled`, `compose`, `andThen`.
f3.compose(f3).andThen(f3).apply("");
scala.Function2<String, String, String> f6 = func((s1, s2) -> join(s1, s2));
assert(f6.curried().apply("1").apply("2").equals("12"));

// Functions returning unit can use the `JProcedure1`, ... functional interfaces
// in order to convert a void lamdba return to Scala's Unit:
JProcedure1<String> f7b = s -> sideEffect();
scala.Function1<String, BoxedUnit> f7c = f7b;

// The easiest way to do this is via `JFunction.proc`, ....
//
// Note that the lambda has a return type of `void` if the last
// statement is a call to a `void` returning method, or if it is
// a `return`.
scala.Function1<String, BoxedUnit> f7 = proc(s -> sideEffect());
scala.Function1<String, BoxedUnit> f8 = proc(s -> {s.toUpperCase(); return;});

// Function0 is also available
scala.Function0<String> f9 = func(() -> "42");
assert(f9.apply().equals("42"));

// You can go up to 22 (the highest arity function defined in the Scala standard library.)
assert(acceptFunction1(func(v1 -> v1.toUpperCase())).equals("1"));
acceptFunction1Unit(proc(v1 -> sideEffect()));
acceptFunction1Unit(proc(v1 -> {v1.toUpperCase(); return;}));

assert(acceptFunction2(func((v1, v2) -> join(v1, v2))).equals("12"));
acceptFunction2Unit(proc((v1, v2) -> {v1.toUpperCase(); return;}));

assert(acceptFunction3(func((v1, v2, v3) -> join(v1, v2, v3))).equals("123"));
acceptFunction3Unit(proc((v1, v2, v3) -> {v1.toUpperCase(); return;}));

assert(acceptFunction22(func((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -> join(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22))).equals("12345678910111213141516171819202122"));
acceptFunction22Unit( proc((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -> {v1.toUpperCase(); return;}));
}

@Test
public void isSerializable() {
JFunction0<String> f0 = () -> "foo";
assertEquals("foo", SerializationUtils.clone(f0).apply());

JFunction1<String, String> f1 = (a) -> a.toUpperCase();
assertEquals("FOO", SerializationUtils.clone(f1).apply("foo"));

JFunction2<String, String, String> f2 = (a, b) -> a + b;
assertEquals("foobar", SerializationUtils.clone(f2).apply("foo", "bar"));

JFunction3<String, String, String, String> f3 = (a, b, c) -> a + b + c;
assertEquals("foobarbaz", SerializationUtils.clone(f3).apply("foo", "bar", "baz"));
}

private static scala.concurrent.Future<Integer> futureExample(
scala.concurrent.Future<String> future, scala.concurrent.ExecutionContext ec) {
return future.map(func(s -> s.toUpperCase()), ec).map(func(s -> s.length()), ec);
}

private static void sideEffect() {
}

private static String join(String... ss) {
String result = "";
for (String s : ss) {
result = result + s;
}
return result;
}
}
2 changes: 1 addition & 1 deletion src/test/java/scala/compat/java8/SpecializedTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
package scala.compat.java8

import org.junit.Test
import scala.runtime.java8.SpecializedTestSupport.IntIdentity
import SpecializedTestSupport.IntIdentity

class SpecializedTest {
@Test def specializationWorks() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/*
* Copyright (C) 2012-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package scala.runtime.java8;
package scala.compat.java8;

import java.util.Arrays;
import java.util.List;
import org.junit.Assert;
import scala.runtime.java8.*;

public class SpecializedTestSupport {
public static class IntIdentity implements JFunction1$mcII$sp {
Expand Down
Loading