Skip to content

Commit 90270f4

Browse files
authored
Restore backwards compatibility in Scala 2.12.0-RC1 (#78)
- The various JFunction and JProcedure sources that were removed from Scala in scala/scala#5298 are now included here when building for Scala 2.12.x (except M5). - Code that relies on specialized JFunction types being subtypes of the generic JFunction types will inevitably break. This is unavoidable. The specialized types are still part of Scala but they extend Scala function types directly. - All JFunctionN types and JFunction.func methods are deprecated. They are provided for source compatibility between 2.11 and 2.12 only. - The JProcedureN classes, JFunction.funcSpecialized methods and JFunction.proc methods are still useful in 2.12 for easy interoperability between Scala specialization and Java’s primitive types from Java code. - Generate JFunction and JProcedure sources: Just like for 2.11, we generate the sources again for 2.12 (after M5). I wrote a new code generator which is much simpler than the old one (because there is less and simpler code to generate). We can remove the old one when we drop support for Scala 2.11. - Move code back from scala.runtime.java8 to scala.compat.java8: The standard library contains some specialized JFunction* classes in scala.runtime.java8, so we cannot use the same package here if we ever want this to work with Java 9 modules. Therefore everything is moved back to scala.compat.java8 where it was in 0.7. We have to drop compatibility with Scala 2.12.0-M5 which contains these classes as part of scala-library in scala.runtime.java8. - Update to proper Scala 2.12.0-RC so we can build a release
1 parent 239d7f6 commit 90270f4

File tree

8 files changed

+269
-138
lines changed

8 files changed

+269
-138
lines changed

Diff for: build.sbt

+13-12
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ val disableDocs = sys.props("nodocs") == "true"
22

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

5-
def jwrite(dir: java.io.File)(name: String, content: String) = {
6-
val f = dir / "scala" / "runtime" / "java8" / s"${name}.java"
5+
def jwrite(dir: java.io.File, pck: String = "scala/compat/java8")(name: String, content: String) = {
6+
val f = dir / pck / s"${name}.java"
77
IO.write(f, content)
88
f
99
}
@@ -16,8 +16,8 @@ def osgiExport(scalaVersion: String, version: String) = {
1616
}
1717

1818
lazy val commonSettings = Seq(
19-
scalaVersion := "2.11.8",
20-
crossScalaVersions := List("2.11.8", "2.12.0-M5"),
19+
crossScalaVersions := List("2.12.0-RC1", "2.11.8"),
20+
scalaVersion := crossScalaVersions.value.head,
2121
organization := "org.scala-lang.modules",
2222
version := "0.8.0-SNAPSHOT"
2323
)
@@ -69,15 +69,16 @@ lazy val root = (project in file(".")).
6969
}.taskValue,
7070

7171
sourceGenerators in Compile <+= (sourceManaged in Compile, scalaVersion) map { (dir, v) =>
72+
val write = jwrite(dir) _
7273
if(v.startsWith("2.11.")) {
73-
val write = jwrite(dir) _
7474
Seq(write("JFunction", CodeGen.factory)) ++
75-
(0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++
76-
(0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++
77-
CodeGen.specializedF0.map(write.tupled) ++
78-
CodeGen.specializedF1.map(write.tupled) ++
79-
CodeGen.specializedF2.map(write.tupled)
80-
} else Seq.empty
75+
(0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++
76+
(0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++
77+
CodeGen.specializedF0.map(write.tupled) ++
78+
CodeGen.specializedF1.map(write.tupled) ++
79+
CodeGen.specializedF2.map(write.tupled) ++
80+
CodeGen.packageDummy.map((jwrite(dir, "java/runtime/java8") _).tupled)
81+
} else CodeGen.create212.map(write.tupled)
8182
},
8283

8384
sourceGenerators in Test <+= sourceManaged in Test map { dir =>
@@ -86,7 +87,7 @@ lazy val root = (project in file(".")).
8687

8788
initialize := {
8889
// Run previously configured inialization...
89-
initialize.value
90+
val _ = initialize.value
9091
// ... and then check the Java version.
9192
val specVersion = sys.props("java.specification.version")
9293
if (Set("1.5", "1.6", "1.7") contains specVersion)

Diff for: fnGen/WrapFnGen.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/*
2-
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
2+
* Copyright (C) 2015-2016 Lightbend Inc. <https://www.lightbend.com>
33
*/
44

55
object WrapFnGen {
66
val copyright =
77
s"""
88
|/*
9-
| * Copyright (C) 2015, Typesafe Inc. <http://www.typesafe.com>
9+
| * Copyright (C) 2015-2016, Lightbend Inc. <https://www.lightbend.com>
1010
| * This file auto-generated by WrapFnGen.scala. Do not modify directly.
1111
| */
1212
|""".stripMargin

Diff for: project/CodeGen.scala

+100-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2012-2014 Typesafe Inc. <http://www.typesafe.com>
2+
* Copyright (C) 2012-2016 Lightbend Inc. <https://www.lightbend.com>
33
*/
44

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

2727
object CodeGen {
28-
def packaging = "package scala.runtime.java8;"
28+
def packaging = "package scala.compat.java8;"
2929
case class arity(n: Int) {
3030
val ns = (1 to n).toList
3131

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

294294
private def function0SpecMethods = {
@@ -433,4 +433,101 @@ object CodeGen {
433433
}
434434

435435
def indent(s: String) = s.linesIterator.map(" " + _).mkString("\n")
436+
437+
/** Create a dummy class to put into scala.runtime.java8 for Scala 2.11 so that wildcard imports from the
438+
* package won't fail. This allows importing both `scala.runtime.java8.*` and `scala.compat.java8.*` for
439+
* source compatibility between 2.11 and 2.12.
440+
*/
441+
def packageDummy: Seq[(String, String)] = Seq(
442+
( "PackageDummy",
443+
s"""$copyright
444+
|
445+
|package scala.runtime.java8;
446+
|
447+
|public final class PackageDummy {
448+
| private PackageDummy() {}
449+
|}
450+
""".stripMargin)
451+
)
452+
453+
/** Create the simpler JFunction and JProcedure sources for Scala 2.12+ */
454+
def create212: Seq[(String, String)] = {
455+
val blocks = for(i <- 0 to 22) yield {
456+
val ts = (1 to i).map(i => s"T$i").mkString(", ")
457+
val tsComma = if(ts.isEmpty) "" else s"$ts,"
458+
val tsAngled = if(ts.isEmpty) "" else s"<$ts>"
459+
val paramTs = (1 to i).map(i => s"T$i t$i").mkString(", ")
460+
val argTs = (1 to i).map(i => s"t$i").mkString(", ")
461+
462+
(
463+
( s"JFunction$i",
464+
s"""$copyright
465+
|$packaging
466+
|
467+
|/** @deprecated Use scala.Function$i in Scala 2.12 */
468+
|@Deprecated
469+
|@FunctionalInterface
470+
|public interface JFunction$i<$tsComma R> extends scala.Function$i<$tsComma R>, java.io.Serializable {}
471+
""".stripMargin),
472+
( s"JProcedure$i",
473+
s"""$copyright
474+
|$packaging
475+
|
476+
|import scala.runtime.BoxedUnit;
477+
|
478+
|@FunctionalInterface
479+
|public interface JProcedure$i$tsAngled extends scala.Function$i<$tsComma BoxedUnit> {
480+
| void applyVoid($paramTs);
481+
| default BoxedUnit apply($paramTs) { applyVoid($argTs); return BoxedUnit.UNIT; }
482+
|}
483+
""".stripMargin),
484+
s""" /** @deprecated Not needed anymore in Scala 2.12 */
485+
| @Deprecated
486+
| public static <$tsComma R> scala.Function$i<$tsComma R> func(scala.Function$i<$tsComma R> f) { return f; }
487+
| public static $tsAngled scala.Function$i<$tsComma BoxedUnit> proc(JProcedure$i$tsAngled p) { return p; }
488+
""".stripMargin
489+
)
490+
}
491+
492+
def specialize(args: String): List[(Int, String, String)] = {
493+
def combinations(l: List[String]): List[List[Char]] =
494+
l.foldRight(List(Nil: List[Char])) { (s, z) => s.toList.flatMap(c => z.map(c :: _)) }
495+
val split = args.split(",")
496+
combinations(split.toList).map { s =>
497+
val types = s.map {
498+
case 'B' => "Byte"
499+
case 'S' => "Short"
500+
case 'V' => "BoxedUnit"
501+
case 'I' => "Integer"
502+
case 'J' => "Long"
503+
case 'C' => "Character"
504+
case 'F' => "Float"
505+
case 'D' => "Double"
506+
case 'Z' => "Boolean"
507+
}
508+
(split.length-1, (types.tail :+ types.head).mkString(", "), "$mc" + s.mkString + "$sp")
509+
}
510+
}
511+
512+
val specialized =
513+
List("V", "V,IJFD", "V,IJD,IJD").flatMap(specialize).map { case (i, a, sp) =>
514+
s" public static scala.Function$i<$a> procSpecialized(JFunction$i$sp f) { return f; }" } ++
515+
List("BSIJCFDZ", "ZIFJD,IJFD", "ZIFJD,IJD,IJD").flatMap(specialize).map { case (i, a, sp) =>
516+
s" public static scala.Function$i<$a> funcSpecialized(JFunction$i$sp f) { return f; }" }
517+
518+
(blocks.map(_._1) ++ blocks.map(_._2)) :+
519+
( "JFunction",
520+
s"""$copyright
521+
|$packaging
522+
|
523+
|import scala.runtime.BoxedUnit;
524+
|import scala.runtime.java8.*;
525+
|
526+
|public final class JFunction {
527+
| private JFunction() {}
528+
|${specialized.mkString("\n")}
529+
|${blocks.map(_._3).mkString("\n")}
530+
|}
531+
""".stripMargin)
532+
}
436533
}

Diff for: src/test/java/scala/runtime/java8/BoxingTest.java renamed to src/test/java/scala/compat/java8/BoxingTest.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
/*
22
* Copyright (C) 2012-2015 Typesafe Inc. <http://www.typesafe.com>
33
*/
4-
package scala.runtime.java8;
4+
package scala.compat.java8;
55

66
import org.junit.Test;
7+
import scala.runtime.java8.*;
78

89
public class BoxingTest {
910
@Test
1011
public void nullBoxesInterpretedAsZeroF1() {
11-
JFunction1$mcII$sp jFunction1 = new JFunction1$mcII$sp() {
12+
scala.Function1<Integer, Integer> jFunction1 = new JFunction1$mcII$sp() {
1213
@Override
1314
public int apply$mcII$sp(int v1) {
1415
return v1 + 1;
@@ -20,7 +21,7 @@ public void nullBoxesInterpretedAsZeroF1() {
2021

2122
@Test
2223
public void nullBoxesInterpretedAsZeroF2() {
23-
JFunction2$mcIII$sp jFunction2 = new JFunction2$mcIII$sp() {
24+
scala.Function2<Integer, Integer, Integer> jFunction2 = new JFunction2$mcIII$sp() {
2425
@Override
2526
public int apply$mcIII$sp(int v1, int v2) {
2627
return v1 + v2 + 1;

Diff for: src/test/java/scala/compat/java8/LambdaTest.java

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright (C) 2012-2015 Typesafe Inc. <http://www.typesafe.com>
3+
*/
4+
package scala.compat.java8;
5+
6+
import org.apache.commons.lang3.SerializationUtils;
7+
import scala.runtime.*;
8+
9+
import static junit.framework.Assert.assertEquals;
10+
import static scala.compat.java8.JFunction.*;
11+
import static scala.compat.java8.TestAPI.*;
12+
13+
import org.junit.Test;
14+
15+
16+
public class LambdaTest {
17+
@Test
18+
public void lambdaDemo() {
19+
// Scala 2.12+ only:
20+
//scala.Function1<String, String> f1 = (String s) -> s;
21+
22+
// Not allowed with Scala 2.10 nor 2.11
23+
// "incompatible types: Function1 is not a functional interface"
24+
// scala.Function1<String, String> f = (String s) -> s;
25+
26+
// Function1 is not a functional interface because it has abstract
27+
// methods in addition to apply, namely `compose` and `andThen`
28+
// (which are implemented in Scala-derived subclasses with mixin
29+
// inheritance), and the specialized variants of apply (also provided
30+
// by scalac.)
31+
32+
// That's a pity, but we can get pretty close with this library!
33+
34+
// We have to tell javac to use `JFunction1` as the functional interface.
35+
// Scala 2.12 does not have or need JFunction anymore. We provide it as a
36+
// deprecated stub for backwards compatibility. Use `scala.Function1` for
37+
// code that targets Scala 2.12+ exclusively.
38+
JFunction1<String, String> f1 = (String s) -> s;
39+
40+
// That's more or less equivalent to the old, anonymous class syntax:
41+
new JFunction1<String, String>() {
42+
public String apply(String s) { return s; }
43+
};
44+
45+
// You might have seen this form before:
46+
new AbstractFunction1<String, String>() {
47+
public String apply(String s) { return s; }
48+
};
49+
50+
// However, we can't use `AbstractFunction1` as a functional interface
51+
// as it is a class. Further
52+
53+
// F1 is a subclass of Function1:
54+
scala.Function1<String, String> f2 = f1;
55+
56+
// Factory methods in `JFunction` can reduce the verbosity a little:
57+
// `func` is actually just an identity method; it only exists to
58+
// trigger lambda creation using the `JFunction1` functional interface.
59+
scala.Function1<String, String> f3 = func((String s) -> s);
60+
61+
// Note that javac's type inference can infer the parameter type here,
62+
// based on the acribed type of `f4`.
63+
scala.Function1<String, String> f4 = func(s -> s);
64+
65+
// f1.apply("");
66+
67+
// Specialized variants of the `apply` method are provided but implementing a specialized
68+
// Scala function in this straight-forward way results in boxing and unboxing because the
69+
// Java lambda operates on boxed types:
70+
JFunction1<Integer, Integer> f5 = (i) -> -i;
71+
assert(f5.apply(1) == -1);
72+
assert(f5.apply$mcII$sp(1) == -1);
73+
74+
// We provide `JFunction.funcSpecialized` and `JFunction.procSpecialized` methods to avoid
75+
// boxing:
76+
scala.Function1<Integer, Integer> f5b = funcSpecialized((int i) -> -i);
77+
assert(f5b.apply(1) == -1);
78+
assert(f5b.apply$mcII$sp(1) == -1);
79+
80+
// as are `curried`, `tupled`, `compose`, `andThen`.
81+
f3.compose(f3).andThen(f3).apply("");
82+
scala.Function2<String, String, String> f6 = func((s1, s2) -> join(s1, s2));
83+
assert(f6.curried().apply("1").apply("2").equals("12"));
84+
85+
// Functions returning unit can use the `JProcedure1`, ... functional interfaces
86+
// in order to convert a void lamdba return to Scala's Unit:
87+
JProcedure1<String> f7b = s -> sideEffect();
88+
scala.Function1<String, BoxedUnit> f7c = f7b;
89+
90+
// The easiest way to do this is via `JFunction.proc`, ....
91+
//
92+
// Note that the lambda has a return type of `void` if the last
93+
// statement is a call to a `void` returning method, or if it is
94+
// a `return`.
95+
scala.Function1<String, BoxedUnit> f7 = proc(s -> sideEffect());
96+
scala.Function1<String, BoxedUnit> f8 = proc(s -> {s.toUpperCase(); return;});
97+
98+
// Function0 is also available
99+
scala.Function0<String> f9 = func(() -> "42");
100+
assert(f9.apply().equals("42"));
101+
102+
// You can go up to 22 (the highest arity function defined in the Scala standard library.)
103+
assert(acceptFunction1(func(v1 -> v1.toUpperCase())).equals("1"));
104+
acceptFunction1Unit(proc(v1 -> sideEffect()));
105+
acceptFunction1Unit(proc(v1 -> {v1.toUpperCase(); return;}));
106+
107+
assert(acceptFunction2(func((v1, v2) -> join(v1, v2))).equals("12"));
108+
acceptFunction2Unit(proc((v1, v2) -> {v1.toUpperCase(); return;}));
109+
110+
assert(acceptFunction3(func((v1, v2, v3) -> join(v1, v2, v3))).equals("123"));
111+
acceptFunction3Unit(proc((v1, v2, v3) -> {v1.toUpperCase(); return;}));
112+
113+
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"));
114+
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;}));
115+
}
116+
117+
@Test
118+
public void isSerializable() {
119+
JFunction0<String> f0 = () -> "foo";
120+
assertEquals("foo", SerializationUtils.clone(f0).apply());
121+
122+
JFunction1<String, String> f1 = (a) -> a.toUpperCase();
123+
assertEquals("FOO", SerializationUtils.clone(f1).apply("foo"));
124+
125+
JFunction2<String, String, String> f2 = (a, b) -> a + b;
126+
assertEquals("foobar", SerializationUtils.clone(f2).apply("foo", "bar"));
127+
128+
JFunction3<String, String, String, String> f3 = (a, b, c) -> a + b + c;
129+
assertEquals("foobarbaz", SerializationUtils.clone(f3).apply("foo", "bar", "baz"));
130+
}
131+
132+
private static scala.concurrent.Future<Integer> futureExample(
133+
scala.concurrent.Future<String> future, scala.concurrent.ExecutionContext ec) {
134+
return future.map(func(s -> s.toUpperCase()), ec).map(func(s -> s.length()), ec);
135+
}
136+
137+
private static void sideEffect() {
138+
}
139+
140+
private static String join(String... ss) {
141+
String result = "";
142+
for (String s : ss) {
143+
result = result + s;
144+
}
145+
return result;
146+
}
147+
}

Diff for: src/test/java/scala/compat/java8/SpecializedTest.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
package scala.compat.java8
55

66
import org.junit.Test
7-
import scala.runtime.java8.SpecializedTestSupport.IntIdentity
7+
import SpecializedTestSupport.IntIdentity
88

99
class SpecializedTest {
1010
@Test def specializationWorks() {

Diff for: src/test/java/scala/runtime/java8/SpecializedTestSupport.java renamed to src/test/java/scala/compat/java8/SpecializedTestSupport.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/*
22
* Copyright (C) 2012-2015 Typesafe Inc. <http://www.typesafe.com>
33
*/
4-
package scala.runtime.java8;
4+
package scala.compat.java8;
55

66
import java.util.Arrays;
77
import java.util.List;
88
import org.junit.Assert;
9+
import scala.runtime.java8.*;
910

1011
public class SpecializedTestSupport {
1112
public static class IntIdentity implements JFunction1$mcII$sp {

0 commit comments

Comments
 (0)