From 3184be51662fc544ab13950d95d55009cc2d7a1e Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Fri, 30 Jan 2026 10:28:49 -0800 Subject: [PATCH 1/3] Drop Scala 2.12 support - Remove scala-2.12- version-specific source directories - Update build.sbt to only support Scala 2.13 and 3.3.4 - Update CI workflow to remove 2.12 from test matrix - Use Scala 2.13+ APIs (IterableOnce, jdk.CollectionConverters) BREAKING: Scala 2.12 is no longer supported. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/ci.yaml | 2 - build.sbt | 22 +- .../chill/AllScalaRegistrarCompat.scala | 32 --- .../chill/ClassManifestSerializer.scala | 27 --- .../com/twitter/chill/RichKryoCompat.scala | 48 ---- .../com/twitter/chill/Traversable.scala | 49 ---- .../chill/WrappedArraySerializer.scala | 41 ---- .../chill/RegistrationIdsSpecData.scala | 174 -------------- .../chill/SerializedExamplesData.scala | 223 ------------------ .../chill/ExternalizerAdditionalSpec.scala | 2 +- 10 files changed, 10 insertions(+), 610 deletions(-) delete mode 100644 chill-scala/src/main/scala-2.12-/com/twitter/chill/AllScalaRegistrarCompat.scala delete mode 100644 chill-scala/src/main/scala-2.12-/com/twitter/chill/ClassManifestSerializer.scala delete mode 100644 chill-scala/src/main/scala-2.12-/com/twitter/chill/RichKryoCompat.scala delete mode 100644 chill-scala/src/main/scala-2.12-/com/twitter/chill/Traversable.scala delete mode 100644 chill-scala/src/main/scala-2.12-/com/twitter/chill/WrappedArraySerializer.scala delete mode 100644 chill-scala/src/test/scala-2.12-/com/twitter/chill/RegistrationIdsSpecData.scala delete mode 100644 chill-scala/src/test/scala-2.12-/com/twitter/chill/SerializedExamplesData.scala diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0046d6b3..ace84ec2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,6 @@ jobs: - "21" - "25" scala: - - "2.12.21" - "2.13.18" test: runs-on: ubuntu-latest @@ -52,7 +51,6 @@ jobs: - "21" - "25" scala: - - "2.12.21" - "2.13.18" - "3.3.4" testWithCoverageReport: diff --git a/build.sbt b/build.sbt index 6b7af37a..0b1c078a 100644 --- a/build.sbt +++ b/build.sbt @@ -10,8 +10,6 @@ val protobufVersion = "3.25.5" def scalaVersionSpecificFolders(srcBaseDir: java.io.File, scalaVersion: String): List[File] = CrossVersion.partialVersion(scalaVersion) match { - case Some((2, y)) if y <= 12 => - new java.io.File(s"${srcBaseDir.getPath}-2.12-") :: Nil case Some((2, y)) if y >= 13 => new java.io.File(s"${srcBaseDir.getPath}-2.13+") :: Nil case Some((3, _)) => @@ -20,11 +18,9 @@ def scalaVersionSpecificFolders(srcBaseDir: java.io.File, scalaVersion: String): case _ => Nil } -val scala212 = "2.12.21" val scala213 = "2.13.18" val scala3 = "3.3.4" -val scala2Versions = Seq(scala212, scala213) -val allScalaVersions = scala2Versions :+ scala3 +val allScalaVersions = Seq(scala213, scala3) val sharedSettings = Seq( organization := "com.twitter", @@ -197,10 +193,10 @@ lazy val chillPekko = module("pekko") ) .dependsOn(chill % "test->test;compile->compile") -// Bijection only supports Scala 2.x +// Bijection only supports Scala 2.13 lazy val chillBijection = module("bijection") .settings( - crossScalaVersions := scala2Versions, + crossScalaVersions := Seq(scala213), libraryDependencies ++= Seq( "com.twitter" %% "bijection-core" % bijectionVersion ) @@ -244,10 +240,10 @@ lazy val chillThrift = module("thrift").settings( ) ) -// Scrooge only supports Scala 2.x +// Scrooge only supports Scala 2.13 lazy val chillScrooge = module("scrooge") .settings( - crossScalaVersions := scala2Versions, + crossScalaVersions := Seq(scala213), libraryDependencies ++= Seq( ("org.apache.thrift" % "libthrift" % "0.22.0").exclude("junit", "junit"), "com.twitter" %% "scrooge-serializer" % scroogeVersion @@ -267,10 +263,10 @@ lazy val chillProtobuf = module("protobuf") ) .dependsOn(chillJava) -// Avro depends on bijection which only supports Scala 2.x +// Avro depends on bijection which only supports Scala 2.13 lazy val chillAvro = module("avro") .settings( - crossScalaVersions := scala2Versions, + crossScalaVersions := Seq(scala213), libraryDependencies ++= Seq( "com.twitter" %% "bijection-avro" % bijectionVersion, "junit" % "junit" % "4.13.2" % "test" @@ -278,10 +274,10 @@ lazy val chillAvro = module("avro") ) .dependsOn(chill, chillJava, chillBijection) -// Algebird only supports Scala 2.x +// Algebird only supports Scala 2.13 lazy val chillAlgebird = module("algebird") .settings( - crossScalaVersions := scala2Versions, + crossScalaVersions := Seq(scala213), libraryDependencies ++= Seq( "com.twitter" %% "algebird-core" % algebirdVersion ) diff --git a/chill-scala/src/main/scala-2.12-/com/twitter/chill/AllScalaRegistrarCompat.scala b/chill-scala/src/main/scala-2.12-/com/twitter/chill/AllScalaRegistrarCompat.scala deleted file mode 100644 index 528a9dd4..00000000 --- a/chill-scala/src/main/scala-2.12-/com/twitter/chill/AllScalaRegistrarCompat.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2019 Twitter, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package com.twitter.chill - -/** - * Scala collections registrar for compatibility between 2.12- and 2.13+. - * - * For 2.12- there's no extra classes that need to be registered. - * @see - * [[ScalaCollectionsRegistrar]] and [[AllScalaRegistrar]] for all the provided registrations. - */ -private[chill] class AllScalaRegistrarCompat_0_9_5 extends IKryoRegistrar { - override def apply(newK: Kryo): Unit = () -} - -private[chill] class AllScalaRegistrarCompat extends IKryoRegistrar { - override def apply(newK: Kryo): Unit = () -} diff --git a/chill-scala/src/main/scala-2.12-/com/twitter/chill/ClassManifestSerializer.scala b/chill-scala/src/main/scala-2.12-/com/twitter/chill/ClassManifestSerializer.scala deleted file mode 100644 index 37dcfa0c..00000000 --- a/chill-scala/src/main/scala-2.12-/com/twitter/chill/ClassManifestSerializer.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 2019 Twitter, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package com.twitter.chill - -class ClassManifestSerializer[T] extends KSerializer[ClassManifest[T]] { - def write(kser: Kryo, out: Output, obj: ClassManifest[T]): Unit = - kser.writeObject(out, obj.runtimeClass) - - def read(kser: Kryo, in: Input, cls: Class[ClassManifest[T]]): ClassManifest[T] = { - val clazz = kser.readObject(in, classOf[Class[T]]) - ClassManifest.fromClass[T](clazz) - } -} diff --git a/chill-scala/src/main/scala-2.12-/com/twitter/chill/RichKryoCompat.scala b/chill-scala/src/main/scala-2.12-/com/twitter/chill/RichKryoCompat.scala deleted file mode 100644 index bf091058..00000000 --- a/chill-scala/src/main/scala-2.12-/com/twitter/chill/RichKryoCompat.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2019 Twitter, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package com.twitter.chill - -import scala.collection.generic.CanBuildFrom - -import scala.reflect._ - -trait RichKryoCompat { self: RichKryo => - - def forTraversableSubclass[T, C <: Traversable[T]]( - c: C with Traversable[T], - isImmutable: Boolean = true - )(implicit mf: ClassTag[C], cbf: CanBuildFrom[C, T, C]): Kryo = { - k.addDefaultSerializer(mf.runtimeClass, new TraversableSerializer(isImmutable)(cbf)) - k - } - - def forTraversableClass[T, C <: Traversable[T]]( - c: C with Traversable[T], - isImmutable: Boolean = true - )(implicit mf: ClassTag[C], cbf: CanBuildFrom[C, T, C]): Kryo = - forClass(new TraversableSerializer(isImmutable)(cbf)) - - def forConcreteTraversableClass[T, C <: Traversable[T]]( - c: C with Traversable[T], - isImmutable: Boolean = true - )(implicit cbf: CanBuildFrom[C, T, C]): Kryo = { - // a ClassTag is not used here since its runtimeClass method does not return the concrete internal type - // that Scala uses for small immutable maps (i.e., scala.collection.immutable.Map$Map1) - k.register(c.getClass, new TraversableSerializer(isImmutable)(cbf)) - k - } -} diff --git a/chill-scala/src/main/scala-2.12-/com/twitter/chill/Traversable.scala b/chill-scala/src/main/scala-2.12-/com/twitter/chill/Traversable.scala deleted file mode 100644 index c4547761..00000000 --- a/chill-scala/src/main/scala-2.12-/com/twitter/chill/Traversable.scala +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2019 Twitter, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package com.twitter.chill - -import scala.collection.generic.CanBuildFrom - -class TraversableSerializer[T, C <: Traversable[T]](override val isImmutable: Boolean = true)(implicit - cbf: CanBuildFrom[C, T, C] -) extends KSerializer[C] { - def write(kser: Kryo, out: Output, obj: C): Unit = { - // Write the size: - out.writeInt(obj.size, true) - obj.foreach { t => - val tRef = t.asInstanceOf[AnyRef] - kser.writeClassAndObject(out, tRef) - // After each intermediate object, flush - out.flush() - } - } - - def read(kser: Kryo, in: Input, cls: Class[C]): C = { - val size = in.readInt(true) - // Go ahead and be faster, and not as functional cool, and be mutable in here - var idx = 0 - val builder = cbf() - builder.sizeHint(size) - - while (idx < size) { - val item = kser.readClassAndObject(in).asInstanceOf[T] - builder += item - idx += 1 - } - builder.result() - } -} diff --git a/chill-scala/src/main/scala-2.12-/com/twitter/chill/WrappedArraySerializer.scala b/chill-scala/src/main/scala-2.12-/com/twitter/chill/WrappedArraySerializer.scala deleted file mode 100644 index 54119cce..00000000 --- a/chill-scala/src/main/scala-2.12-/com/twitter/chill/WrappedArraySerializer.scala +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2019 Twitter, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package com.twitter.chill - -import scala.collection.mutable -import scala.collection.mutable.{ArrayBuilder, WrappedArray} -import scala.reflect._ - -class WrappedArraySerializer[T] extends KSerializer[WrappedArray[T]] { - def write(kser: Kryo, out: Output, obj: WrappedArray[T]): Unit = { - // Write the class-manifest, we don't use writeClass because it - // uses the registration system, and this class might not be registered - kser.writeObject(out, obj.elemTag.runtimeClass) - kser.writeClassAndObject(out, obj.array) - } - - def read(kser: Kryo, in: Input, cls: Class[WrappedArray[T]]): mutable.WrappedArray[T] = { - // Write the class-manifest, we don't use writeClass because it - // uses the registration system, and this class might not be registered - val clazz = kser.readObject(in, classOf[Class[T]]) - val array = kser.readClassAndObject(in).asInstanceOf[Array[T]] - val bldr = ArrayBuilder.make[T]()(ClassTag[T](clazz)) - bldr.sizeHint(array.size) - bldr ++= array - bldr.result() - } -} diff --git a/chill-scala/src/test/scala-2.12-/com/twitter/chill/RegistrationIdsSpecData.scala b/chill-scala/src/test/scala-2.12-/com/twitter/chill/RegistrationIdsSpecData.scala deleted file mode 100644 index d7c5e8ee..00000000 --- a/chill-scala/src/test/scala-2.12-/com/twitter/chill/RegistrationIdsSpecData.scala +++ /dev/null @@ -1,174 +0,0 @@ -/* -Copyright 2019 Twitter, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package com.twitter.chill - -object RegistrationIdsSpecData { - val Entries_0_9_5 = - """0 -> int - |1 -> class java.lang.String - |2 -> float - |3 -> boolean - |4 -> byte - |5 -> char - |6 -> short - |7 -> long - |8 -> double - |9 -> void - |10 -> class scala.collection.convert.Wrappers$SeqWrapper - |11 -> class scala.collection.convert.Wrappers$IteratorWrapper - |12 -> class scala.collection.convert.Wrappers$MapWrapper - |13 -> class scala.collection.convert.Wrappers$JListWrapper - |14 -> class scala.collection.convert.Wrappers$JMapWrapper - |15 -> class scala.Some - |16 -> class scala.util.Left - |17 -> class scala.util.Right - |18 -> class scala.collection.immutable.Vector - |19 -> class scala.collection.immutable.Set$Set1 - |20 -> class scala.collection.immutable.Set$Set2 - |21 -> class scala.collection.immutable.Set$Set3 - |22 -> class scala.collection.immutable.Set$Set4 - |23 -> class scala.collection.immutable.HashSet$HashTrieSet - |24 -> class scala.collection.immutable.Map$Map1 - |25 -> class scala.collection.immutable.Map$Map2 - |26 -> class scala.collection.immutable.Map$Map3 - |27 -> class scala.collection.immutable.Map$Map4 - |28 -> class scala.collection.immutable.HashMap$HashTrieMap - |29 -> class scala.collection.immutable.Range$Inclusive - |30 -> class scala.collection.immutable.NumericRange$Inclusive - |31 -> class scala.collection.immutable.NumericRange$Exclusive - |32 -> class scala.collection.mutable.BitSet - |33 -> class scala.collection.mutable.HashMap - |34 -> class scala.collection.mutable.HashSet - |35 -> class scala.collection.convert.Wrappers$IterableWrapper - |36 -> class scala.Tuple1 - |37 -> class scala.Tuple2 - |38 -> class scala.Tuple3 - |39 -> class scala.Tuple4 - |40 -> class scala.Tuple5 - |41 -> class scala.Tuple6 - |42 -> class scala.Tuple7 - |43 -> class scala.Tuple8 - |44 -> class scala.Tuple9 - |45 -> class scala.Tuple10 - |46 -> class scala.Tuple11 - |47 -> class scala.Tuple12 - |48 -> class scala.Tuple13 - |49 -> class scala.Tuple14 - |50 -> class scala.Tuple15 - |51 -> class scala.Tuple16 - |52 -> class scala.Tuple17 - |53 -> class scala.Tuple18 - |54 -> class scala.Tuple19 - |55 -> class scala.Tuple20 - |56 -> class scala.Tuple21 - |57 -> class scala.Tuple22 - |58 -> class scala.Tuple1$mcJ$sp - |59 -> class scala.Tuple1$mcI$sp - |60 -> class scala.Tuple1$mcD$sp - |61 -> class scala.Tuple2$mcJJ$sp - |62 -> class scala.Tuple2$mcJI$sp - |63 -> class scala.Tuple2$mcJD$sp - |64 -> class scala.Tuple2$mcIJ$sp - |65 -> class scala.Tuple2$mcII$sp - |66 -> class scala.Tuple2$mcID$sp - |67 -> class scala.Tuple2$mcDJ$sp - |68 -> class scala.Tuple2$mcDI$sp - |69 -> class scala.Tuple2$mcDD$sp - |70 -> class scala.Symbol - |71 -> interface scala.reflect.ClassTag - |72 -> class scala.runtime.BoxedUnit - |73 -> class java.util.Arrays$ArrayList - |74 -> class java.util.BitSet - |75 -> class java.util.PriorityQueue - |76 -> class java.util.regex.Pattern - |77 -> class java.sql.Date - |78 -> class java.sql.Time - |79 -> class java.sql.Timestamp - |80 -> class java.net.URI - |81 -> class java.net.InetSocketAddress - |82 -> class java.util.UUID - |83 -> class java.util.Locale - |84 -> class java.text.SimpleDateFormat - |85 -> class java.util.Collections$UnmodifiableCollection - |86 -> class java.util.Collections$UnmodifiableRandomAccessList - |87 -> class java.util.Collections$UnmodifiableList - |88 -> class java.util.Collections$UnmodifiableMap - |89 -> class java.util.Collections$UnmodifiableSet - |90 -> class java.util.Collections$UnmodifiableSortedMap - |91 -> class java.util.Collections$UnmodifiableSortedSet - |92 -> class com.esotericsoftware.kryo.serializers.ClosureSerializer$Closure - |93 -> class [B - |94 -> class [S - |95 -> class [I - |96 -> class [J - |97 -> class [F - |98 -> class [D - |99 -> class [Z - |100 -> class [C - |101 -> class [Ljava.lang.String; - |102 -> class [Ljava.lang.Object; - |103 -> class java.lang.Class - |104 -> class java.lang.Object - |105 -> class scala.collection.mutable.WrappedArray$ofByte - |106 -> class scala.collection.mutable.WrappedArray$ofShort - |107 -> class scala.collection.mutable.WrappedArray$ofInt - |108 -> class scala.collection.mutable.WrappedArray$ofLong - |109 -> class scala.collection.mutable.WrappedArray$ofFloat - |110 -> class scala.collection.mutable.WrappedArray$ofDouble - |111 -> class scala.collection.mutable.WrappedArray$ofBoolean - |112 -> class scala.collection.mutable.WrappedArray$ofChar - |113 -> class scala.collection.mutable.WrappedArray$ofRef - |114 -> class scala.None$ - |115 -> class scala.collection.immutable.Queue - |116 -> class scala.collection.immutable.Nil$ - |117 -> class scala.collection.immutable.$colon$colon - |118 -> class scala.collection.immutable.Range - |119 -> class scala.collection.immutable.WrappedString - |120 -> class scala.collection.immutable.TreeSet - |121 -> class scala.collection.immutable.TreeMap - |122 -> class scala.math.Ordering$Byte$ - |123 -> class scala.math.Ordering$Short$ - |124 -> class scala.math.Ordering$Int$ - |125 -> class scala.math.Ordering$Long$ - |126 -> class scala.math.Ordering$Float$ - |127 -> class scala.math.Ordering$Double$ - |128 -> class scala.math.Ordering$Boolean$ - |129 -> class scala.math.Ordering$Char$ - |130 -> class scala.math.Ordering$String$ - |131 -> class scala.collection.immutable.Set$EmptySet$ - |132 -> class scala.collection.immutable.ListSet$EmptyListSet$ - |133 -> class scala.collection.immutable.ListSet$Node - |134 -> class scala.collection.immutable.HashSet$EmptyHashSet$ - |135 -> class scala.collection.immutable.HashSet$HashSet1 - |136 -> class scala.collection.immutable.Map$EmptyMap$ - |137 -> class scala.collection.immutable.HashMap$EmptyHashMap$ - |138 -> class scala.collection.immutable.HashMap$HashMap1 - |139 -> class scala.collection.immutable.ListMap$EmptyListMap$ - |140 -> class scala.collection.immutable.ListMap$Node - |141 -> class scala.collection.immutable.Stream$Cons - |142 -> class scala.collection.immutable.Stream$Empty$ - |143 -> class scala.runtime.VolatileByteRef - |144 -> class scala.math.BigDecimal - |145 -> class scala.collection.immutable.Queue$EmptyQueue$ - |146 -> class scala.collection.immutable.MapLike$$anon$1 - |147 -> class scala.collection.immutable.MapLike$$anon$2 - |148 -> class scala.collection.immutable.MapLike$ImmutableDefaultKeySet""".stripMargin.linesIterator.toStream - - val RecentEntries = Stream.empty - - val CurrentEntries = Entries_0_9_5 #::: RecentEntries -} diff --git a/chill-scala/src/test/scala-2.12-/com/twitter/chill/SerializedExamplesData.scala b/chill-scala/src/test/scala-2.12-/com/twitter/chill/SerializedExamplesData.scala deleted file mode 100644 index 460c87fb..00000000 --- a/chill-scala/src/test/scala-2.12-/com/twitter/chill/SerializedExamplesData.scala +++ /dev/null @@ -1,223 +0,0 @@ -/* -Copyright 2019 Twitter, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package com.twitter.chill - -import scala.collection.JavaConverters -import scala.collection.JavaConverters._ -import scala.collection.immutable.{HashMap, HashSet, ListMap, ListSet, NumericRange, Queue} -import scala.runtime.VolatileByteRef - -object SerializedExamplesData { - val Examples = Seq( - 0 -> ("AgI=" -> Int.box(1)), - 1 -> ("AwFhYuM=" -> "abc"), - 2 -> ("BD+AAAA=" -> Float.box(1)), - 3 -> ("BQE=" -> Boolean.box(true)), - 4 -> ("BgE=" -> Byte.box(1)), - 5 -> ("BwBh" -> Char.box('a')), - 6 -> ("CAAB" -> Short.box(1)), - 7 -> ("CQI=" -> Long.box(1)), - 8 -> ("Cj/wAAAAAAAA" -> Double.box(1)), - // 9 -> void is a special case - // Note: Instead of JavaConverters.***Converter(***).as***, in Scala 2.12 - // methods JavaConverters.*** can be used directly. For backwards compatibility, - // the legacy methods to convert are used here. - 10 -> ("DAEBAHNjYWxhLmNvbGxlY3Rpb24uY29udmVydC5XcmFwcGVyc6QBdwEBAgQ=" -> - JavaConverters.seqAsJavaListConverter(Seq(2)).asJava), // Wrappers$SeqWrapper - 11 -> ("DQEBAHNjYWxhLmNvbGxlY3Rpb24uY29udmVydC5XcmFwcGVyc6QBAQFzY2FsYS5jb2xsZWN0aW9uLkluZGV4ZWRTZXFMaWtlJEVsZW1lbnTzAW0BAQIBYQECBAIA" -> - JavaConverters.asJavaIteratorConverter(Iterator(2)).asJava), // Wrappers$IteratorWrapper - 12 -> ("DgEBAHNjYWxhLmNvbGxlY3Rpb24uY29udmVydC5XcmFwcGVyc6QBGgEBJwECBAIE" -> - JavaConverters.mapAsJavaMapConverter(Map(2 -> 2)).asJava), // Wrappers$MapWrapper - 13 -> ("DwEBAHNjYWxhLmNvbGxlY3Rpb24uY29udmVydC5XcmFwcGVyc6QBAQFqYXZhLnV0aWwuQ29sbGVjdGlvbnMkU2luZ2xldG9uTGlz9AECBA==" -> - JavaConverters - .asScalaBufferConverter(_root_.java.util.Collections.singletonList(2)) - .asScala), // Wrappers$JListWrapper - 14 -> ("EAEBAHNjYWxhLmNvbGxlY3Rpb24uY29udmVydC5XcmFwcGVyc6QBAQFqYXZhLnV0aWwuQ29sbGVjdGlvbnMkU2luZ2xldG9uTWHwAQIEAgQ=" -> - JavaConverters - .mapAsScalaMapConverter(_root_.java.util.Collections.singletonMap(2, 2)) - .asScala), // Wrappers$JMapWrapper - 15 -> ("EQECBA==" -> Some(2)), - 16 -> ("EgECBA==" -> Left(2)), - 17 -> ("EwECBA==" -> Right(2)), - 18 -> ("FAEBAgQ=" -> Vector(2)), - 19 -> ("FQEBAgQ=" -> Set(2)), - 20 -> ("FgECAgQCBg==" -> Set(2, 3)), - 21 -> ("FwEDAgQCBgII" -> Set(2, 3, 4)), - 22 -> ("GAEEAgQCBgIIAgo=" -> Set(2, 3, 4, 5)), - // 23 -> class HashSet$HashTrieSet - 24 -> ("GgEBJwECBAIG" -> Map(2 -> 3)), - 25 -> ("GwECJwECBAIGJwECCAIK" -> Map(2 -> 3, 4 -> 5)), - 26 -> ("HAEDJwECBAIGJwECCAIKJwECDAIO" -> Map(2 -> 3, 4 -> 5, 6 -> 7)), - 27 -> ("HQEEJwECBAIGJwECCAIKJwECDAIOJwECEAIS" -> Map(2 -> 3, 4 -> 5, 6 -> 7, 8 -> 9)), - // 28 -> class HashMap$HashTrieMap - 29 -> ("HwEMAAwIBgI=" -> new Range.Inclusive(3, 6, 1)), - 30 -> ("IAEBAgoAAQABAHNjYWxhLm1hdGguTnVtZXJpYyRJbnRJc0ludGVncmFspAEBAAMIAgQCAg==" -> - new NumericRange.Inclusive[Int](2, 5, 1)), - 31 -> ("IQEBAgoAAAABAHNjYWxhLm1hdGguTnVtZXJpYyRJbnRJc0ludGVncmFspAEBAAMGAgQCAg==" -> - new NumericRange.Exclusive[Int](2, 5, 1)), - 32 -> ("IgECAgYCCg==" -> scala.collection.mutable.BitSet(3, 5)), - 33 -> ("IwEBJwECBgIK" -> scala.collection.mutable.HashMap(3 -> 5)), - 34 -> ("JAEBAgY=" -> scala.collection.mutable.HashSet(3)), - 35 -> ("JQF3AQECBA==" -> Seq(2).asJavaCollection), // Wrappers$IterableWrapper - 36 -> ("JgEDAYJh" -> Tuple1("a")), - 37 -> ("JwEDAYJhAwGCYg==" -> ("a", "b")), - 38 -> ("KAECAgIEAgY=" -> (1, 2, 3)), - 39 -> ("KQECAgIEAgYCCA==" -> (1, 2, 3, 4)), - 40 -> ("KgECAgIEAgYCCAIK" -> (1, 2, 3, 4, 5)), - 41 -> ("KwECAgIEAgYCCAIKAgw=" -> (1, 2, 3, 4, 5, 6)), - 42 -> ("LAECAgIEAgYCCAIKAgwCDg==" -> (1, 2, 3, 4, 5, 6, 7)), - 43 -> ("LQECAgIEAgYCCAIKAgwCDgIQ" -> (1, 2, 3, 4, 5, 6, 7, 8)), - 44 -> ("LgECAgIEAgYCCAIKAgwCDgIQAhI=" -> (1, 2, 3, 4, 5, 6, 7, 8, 9)), - 45 -> ("LwECAgIEAgYCCAIKAgwCDgIQAhICAA==" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0)), - 46 -> ("MAECAgIEAgYCCAIKAgwCDgIQAhICAAIC" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1)), - 47 -> ("MQECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQ=" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2)), - 48 -> ("MgECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBg==" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3)), - 49 -> ("MwECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgII" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4)), - 50 -> ("NAECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgIIAgo=" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5)), - 51 -> ("NQECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgIIAgoCDA==" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, - 6)), - 52 -> ("NgECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgIIAgoCDAIO" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, - 6, 7)), - 53 -> ("NwECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgIIAgoCDAIOAhA=" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, - 4, 5, 6, 7, 8)), - 54 -> ("OAECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgIIAgoCDAIOAhACEg==" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, - 3, 4, 5, 6, 7, 8, 9)), - 55 -> ("OQECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgIIAgoCDAIOAhACEgIA" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, - 3, 4, 5, 6, 7, 8, 9, 0)), - 56 -> ("OgECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgIIAgoCDAIOAhACEgIAAgI=" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, - 2, 3, 4, 5, 6, 7, 8, 9, 0, 1)), - 57 -> ("OwECAgIEAgYCCAIKAgwCDgIQAhICAAICAgQCBgIIAgoCDAIOAhACEgIAAgICBA==" -> (1, 2, 3, 4, 5, 6, 7, 8, 9, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2)), - 58 -> ("PAEAAAAAAAAAAQ==" -> Tuple1(1L)), - 59 -> ("PQEAAAAB" -> Tuple1(1)), - 60 -> ("PgE/8AAAAAAAAA==" -> Tuple1(1.0)), - 61 -> ("PwEAAAAAAAAAAQAAAAAAAAAC" -> (1L, 2L)), - 62 -> ("QAEAAAAAAAAAAQAAAAI=" -> (1L, 2)), - 63 -> ("QQEAAAAAAAAAAUAAAAAAAAAA" -> (1L, 2.0)), - 64 -> ("QgEAAAABAAAAAAAAAAI=" -> (1, 2L)), - 65 -> ("QwEAAAABAAAAAg==" -> (1, 2)), - 66 -> ("RAEAAAABQAAAAAAAAAA=" -> (1, 2.0)), - 67 -> ("RQE/8AAAAAAAAAAAAAAAAAAC" -> (1.0, 2L)), - 68 -> ("RgE/8AAAAAAAAAAAAAI=" -> (1.0, 2)), - 69 -> ("RwE/8AAAAAAAAEAAAAAAAAAA" -> (1.0, 2.0)), - 70 -> ("SAGCYQ==" -> Symbol("a")), - // 71 -> interface scala.reflect.ClassTag - 72 -> ("SgE=" -> runtime.BoxedUnit.UNIT), - 73 -> ("SwEDagICAgQCBg==" -> _root_.java.util.Arrays.asList(1, 2, 3)), - 74 -> ("TAECAAAAAAAAAAAAAAAAAAAAAA==" -> new _root_.java.util.BitSet(65)), - 75 -> ("TQEAAA==" -> new _root_.java.util.PriorityQueue[Int](7)), - 76 -> ("TgFhKuI=" -> _root_.java.util.regex.Pattern.compile("a*b")), - 77 -> ("TwEA" -> new _root_.java.sql.Date(0)), - 78 -> ("UAEH" -> new _root_.java.sql.Time(7)), - 79 -> ("UQEDwI23AQ==" -> new _root_.java.sql.Timestamp(3)), - 80 -> ("UgGB" -> new _root_.java.net.URI("")), - 81 -> ("UwEwLjAuMC6wAg==" -> new _root_.java.net.InetSocketAddress(2)), - 82 -> ("VAECBA==" -> new _root_.java.util.UUID(1, 2)), - 83 -> ("VQGs7QAFc3IAEGphdmEudXRpbC5Mb2NhbGV++BFgnDD57AMABkkACGhhc2hjb2RlTAAHY291bnRyeXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACmV4dGVuc2lvbnNxAH4AAUwACGxhbmd1YWdlcQB+AAFMAAZzY3JpcHRxAH4AAUwAB3ZhcmlhbnRxAH4AAXhw/////3QAAHEAfgADdAACZW5xAH4AA3EAfgADeA==" -> - _root_.java.util.Locale.ENGLISH), - // 84 -> class java.text.SimpleDateFormat - this case has two very special aspects: - // a) SimpleDateFormat("") serializes to about 40.000 bytes - // b) each time you serialize SimpleDateFormat(""), you get a slightly different binary representation. - // Probably, one should write a custom serializer for this class... - 85 -> ("VwEBAGphdmEudXRpbC5Db2xsZWN0aW9ucyRFbXB0eUxpc/QB" -> - _root_.java.util.Collections.unmodifiableCollection(_root_.java.util.Collections.EMPTY_LIST)), - 86 -> ("WAEBAGphdmEudXRpbC5Db2xsZWN0aW9ucyRFbXB0eUxpc/QB" -> - _root_.java.util.Collections.unmodifiableList(_root_.java.util.Collections.EMPTY_LIST)), - 87 -> ("WQEBAGphdmEudXRpbC5MaW5rZWRMaXP0AQA=" -> - _root_.java.util.Collections.unmodifiableList(new _root_.java.util.LinkedList[Int]())), - 88 -> ("WgEBAGphdmEudXRpbC5Db2xsZWN0aW9ucyRTaW5nbGV0b25NYfABAgICBA==" -> - _root_.java.util.Collections - .unmodifiableMap[Int, Int](_root_.java.util.Collections.singletonMap(1, 2))), - 89 -> ("WwEBAGphdmEudXRpbC5Db2xsZWN0aW9ucyRFbXB0eVNl9AE=" -> - _root_.java.util.Collections.unmodifiableSet(_root_.java.util.Collections.EMPTY_SET)), - 90 -> ("XAEBAMEBamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZU5hdmlnYWJsZU1hcCRFbXB0eU5hdmlnYWJsZU1hcAEA" -> - _root_.java.util.Collections - .unmodifiableSortedMap[Int, Int](_root_.java.util.Collections.emptySortedMap())), - // 91 -> class java.util.Collections$UnmodifiableSortedSet - // With the following implementation, we have a problem - // 91 -> ("XQEBAMEBamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZU5hdmlnYWJsZVNldCRFbXB0eU5hdmlnYWJsZVNldAEA" -> - // _root_.java.util.Collections.unmodifiableSortedSet[Int](_root_.java.util.Collections.emptySortedSet())), - // because we get an exception in the test with the root cause: - // com.twitter.chill.Instantiators$ can not access a member of class java.util.Collections$UnmodifiableNavigableSet$EmptyNavigableSet with modifiers "public" - // 92 -> class com.esotericsoftware.kryo.serializers.ClosureSerializer$Closure""" - 93 -> ("XwECgA==" -> Array(Byte.MinValue)), - 94 -> ("YAECf/8=" -> Array(Short.MaxValue)), - 95 -> ("YQEC/////w8=" -> Array(Int.MinValue)), - 96 -> ("YgEC/v//////////" -> Array(Long.MaxValue)), - 97 -> ("YwECAAAAAQ==" -> Array(Float.MinPositiveValue)), - 98 -> ("ZAEC/+////////8=" -> Array(Double.MinValue)), - 99 -> ("ZQECAQ==" -> Array(true)), - 100 -> ("ZgECAHg=" -> Array('x')), - 101 -> ("ZwECAWNh9A==" -> Array("cat")), - 102 -> ("aAEDAgQDAW1vdXPl" -> Array(2, "mouse")), - 103 -> ("aQECAQ==" -> classOf[Int]), - 104 -> ("agE=" -> new Object()), - 105 -> ("awEBBgFfAQKA" -> wrapByteArray(Array(Byte.MinValue))), - 106 -> ("bAEBCAFgAQJ//w==" -> wrapShortArray(Array(Short.MaxValue))), - 107 -> ("bQEBAgFhAQL/////Dw==" -> wrapIntArray(Array(Int.MinValue))), - 108 -> ("bgEBCQFiAQL+//////////8=" -> wrapLongArray(Array(Long.MaxValue))), - 109 -> ("bwEBBAFjAQIAAAAB" -> wrapFloatArray(Array(Float.MinPositiveValue))), - 110 -> ("cAEBCgFkAQL/7////////w==" -> wrapDoubleArray(Array(Double.MinValue))), - 111 -> ("cQEBBQFlAQIB" -> wrapBooleanArray(Array(true))), - 112 -> ("cgEBBwFmAQIAeA==" -> wrapCharArray(Array('x'))), - 113 -> ("cwEBAwBnAQIBY2H0" -> wrapRefArray(Array("cat"))), - 114 -> ("dAE=" -> None), - 115 -> ("dQEA" -> collection.immutable.Queue()), - 116 -> ("dgEA" -> Nil), - 117 -> ("dwEBAgQ=" -> (2 :: Nil)), - 118 -> ("eAEGAAQEAgI=" -> collection.immutable.Range(1, 3)), - 119 -> ("eQEBdGHj" -> wrapString("tac")), - 120 -> ("egECfgECBAIG" -> collection.immutable.TreeSet(3, 2)), - 121 -> ("ewEBfgEnAQIGAgQ=" -> collection.immutable.TreeMap(3 -> 2)), - 122 -> ("fAE=" -> math.Ordering.Byte), - 123 -> ("fQE=" -> math.Ordering.Short), - 124 -> ("fgE=" -> math.Ordering.Int), - 125 -> ("fwE=" -> math.Ordering.Long), - 126 -> ("gAEB" -> math.Ordering.Float), - 127 -> ("gQEB" -> math.Ordering.Double), - 128 -> ("ggEB" -> math.Ordering.Boolean), - 129 -> ("gwEB" -> math.Ordering.Char), - 130 -> ("hAEB" -> math.Ordering.String), - 131 -> ("hQEBAA==" -> Set[Any]()), - 132 -> ("hgEBAA==" -> ListSet[Any]()), - 133 -> ("hwEBAUgBgmE=" -> ListSet[Any]('a)), - 134 -> ("iAEBAA==" -> HashSet[Any]()), - 135 -> ("iQEBAUgBgmE=" -> HashSet[Any]('a)), - 136 -> ("igEBAA==" -> Map[Any, Any]()), - 137 -> ("iwEBAA==" -> HashMap[Any, Any]()), - 138 -> ("jAEBAScBSAGCYUgE" -> HashMap('a -> 'a)), - 139 -> ("jQEBAA==" -> ListMap[Any, Any]()), - 140 -> ("jgEBAScBSAGCYUgE" -> ListMap('a -> 'a)), - 141 -> ("jwEBdwEBAgI=" -> Stream(1)), - 142 -> ("kAEB" -> Stream()), - 143 -> ("kQEBCg==" -> new VolatileByteRef(10)), - 144 -> ("kgEBAQBqYXZhLm1hdGguQmlnRGVjaW1h7AECAgA=" -> math.BigDecimal(2)), - 145 -> ("kwEBAA==" -> (Queue.empty[Any], true)), - 146 -> ("lAEBAScBAgICBA==" -> (Map(1 -> 2).filterKeys(_ != 2).toMap, true)), - 147 -> ("lQEBAScBAgICBg==" -> (Map(1 -> 2).mapValues(_ + 1).toMap, true)), - 148 -> ("lgEBAQIC" -> (Map(1 -> 2).keySet, true)) - ) - - val SpecialCasesNotInExamplesMap: Seq[Int] = Seq(9, 23, 28, 71, 84, 91, 92) - - // In older Scala versions, instances of the following classes have a serialized representation that differs from - // the current Scala version 2.12.6: - // 11 -> scala.collection.convert.Wrappers.IteratorWrapper - // 29 -> scala.collection.immutable.Range$Inclusive - val OmitExamplesInScalaVersion: Map[String, Seq[Int]] = - Map("2.10." -> Seq(11, 29, 118), "2.11." -> Seq(29, 118)) -} diff --git a/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala b/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala index b4fddd69..582edf0f 100644 --- a/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala +++ b/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala @@ -82,7 +82,7 @@ class ExternalizerAdditionalSpec extends AnyWordSpec with Matchers with BaseProp } "handle complex objects with nested structures" in { - val complex = Map( + val complex: Map[String, Any] = Map( "list" -> List(1, 2, 3), "set" -> Set("a", "b"), "nested" -> Map("inner" -> 42) From 5d31db01a9cf2d31de4caddf8dd2b5d8ec6bdf2f Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Fri, 30 Jan 2026 11:12:57 -0800 Subject: [PATCH 2/3] Fix Scala 3 test compatibility Move nested class definitions outside test methods for Scala 3 compatibility: - ClosureCleanerAdditionalSpec: Move test helper classes to class level and companion object to avoid NoClassDefFoundError in Scala 3 - ExternalizerAdditionalSpec: Add explicit type annotations for heterogeneous collections to avoid type inference issues in Scala 3 Co-Authored-By: Claude Opus 4.5 --- .../chill/ClosureCleanerAdditionalSpec.scala | 137 ++++++++++-------- .../chill/ExternalizerAdditionalSpec.scala | 25 ++-- 2 files changed, 89 insertions(+), 73 deletions(-) diff --git a/chill-scala/src/test/scala/com/twitter/chill/ClosureCleanerAdditionalSpec.scala b/chill-scala/src/test/scala/com/twitter/chill/ClosureCleanerAdditionalSpec.scala index 3232a7bf..96bd4adc 100644 --- a/chill-scala/src/test/scala/com/twitter/chill/ClosureCleanerAdditionalSpec.scala +++ b/chill-scala/src/test/scala/com/twitter/chill/ClosureCleanerAdditionalSpec.scala @@ -19,16 +19,91 @@ package com.twitter.chill import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec +// Classes without outer reference (defined in companion object for Scala 3 compatibility) +object ClosureCleanerAdditionalSpec { + class NoOuter { + val x = 1 + } + + class NoOuter2 { + val x = 1 + } + + class TestForInnerCache { + val x = 1 + } + + class TestForFieldCache { + val x = 1 + } +} + /** * Additional tests for ClosureCleaner focusing on edge cases and coverage gaps */ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { + import ClosureCleanerAdditionalSpec._ + + // Define test classes at class level for Scala 3 compatibility + class TestClass { + var value: Int = 42 + } + + class TestOuter { + val x = 1 + } + + class TestOuter2 { + val x = 1 + } + + class WithFields { + val field1 = 1 + val field2 = "hello" + val field3 = 3.14 + } + + class ComplexOuter { + val outerVal = 10 + def createClosure: Int => Int = { x => + val localVal = 5 + x + localVal + outerVal + } + } + + // Nested class hierarchy for testing outer class detection + class Level1 { + class Level2 { + class Level3 { + val x = 1 + } + } + } + + // Another hierarchy for outer field testing + class Outer1 { + val a = 1 + class Inner1 { + val b = 2 + } + } + + class TestOuter3 { + val normalField = 1 + class Inner3 { + val x = 1 + } + } + + class OuterWithInner { + val closure: Int => Int => Int = (x: Int) => { + val inner = (y: Int) => x + y + inner + } + } "ClosureCleaner" should { "instantiate class without constructor" in { - class TestClass { - var value: Int = 42 - } val instance = ClosureCleaner.instantiateClass(classOf[TestClass]) instance should not be null instance shouldBe a[TestClass] @@ -37,17 +112,11 @@ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { } "handle outerFieldOf for class without outer" in { - class NoOuter { - val x = 1 - } val result = ClosureCleaner.outerFieldOf(classOf[NoOuter]) result should be(None) } "cache outerFieldOf results" in { - class TestOuter { - val x = 1 - } // Call twice to test caching val result1 = ClosureCleaner.outerFieldOf(classOf[TestOuter]) val result2 = ClosureCleaner.outerFieldOf(classOf[TestOuter]) @@ -55,13 +124,6 @@ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { } "get outer classes hierarchy" in { - class Level1 { - class Level2 { - class Level3 { - val x = 1 - } - } - } val l1 = new Level1 val l2 = new l1.Level2 val l3 = new l2.Level3 @@ -72,21 +134,12 @@ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { } "cache outer classes hierarchy" in { - class TestOuter2 { - val x = 1 - } val result1 = ClosureCleaner.outerClassesOf(classOf[TestOuter2]) val result2 = ClosureCleaner.outerClassesOf(classOf[TestOuter2]) result1 should be(result2) } "get outers of object with hierarchy" in { - class Outer1 { - val a = 1 - class Inner1 { - val b = 2 - } - } val outer = new Outer1 val inner = new outer.Inner1 @@ -96,21 +149,12 @@ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { } "get outers of object without outer" in { - class NoOuter2 { - val x = 1 - } val obj = new NoOuter2 val outers = ClosureCleaner.getOutersOf(obj) outers should be(empty) } "identify outer fields correctly" in { - class TestOuter3 { - val normalField = 1 - class Inner3 { - val x = 1 - } - } val outer = new TestOuter3 val inner = new outer.Inner3 val outerField = ClosureCleaner.outerFieldOf(inner.getClass) @@ -119,12 +163,6 @@ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { } "find inner classes" in { - class OuterWithInner { - val closure = (x: Int) => { - val inner = (y: Int) => x + y - inner - } - } val obj = new OuterWithInner // This tests the innerClassesOf method and InnerClosureFinder val innerClasses = ClosureCleaner.innerClassesOf(obj.closure.getClass) @@ -133,20 +171,12 @@ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { } "cache inner classes results" in { - class TestForInnerCache { - val x = 1 - } val result1 = ClosureCleaner.innerClassesOf(classOf[TestForInnerCache]) val result2 = ClosureCleaner.innerClassesOf(classOf[TestForInnerCache]) result1 should be(result2) } "find accessed fields" in { - class WithFields { - val field1 = 1 - val field2 = "hello" - val field3 = 3.14 - } val obj = new WithFields val fields = ClosureCleaner.accessedFieldsOf(obj.getClass) // May find some fields depending on how the class is used @@ -154,9 +184,6 @@ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { } "cache accessed fields results" in { - class TestForFieldCache { - val x = 1 - } val result1 = ClosureCleaner.accessedFieldsOf(classOf[TestForFieldCache]) val result2 = ClosureCleaner.accessedFieldsOf(classOf[TestForFieldCache]) result1 should be(result2) @@ -177,14 +204,6 @@ class ClosureCleanerAdditionalSpec extends AnyWordSpec with Matchers { } "handle complex nested closures" in { - class ComplexOuter { - val outerVal = 10 - def createClosure: Int => Int = { x => - val localVal = 5 - x + localVal + outerVal - } - } - val obj = new ComplexOuter val closure = obj.createClosure diff --git a/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala b/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala index 582edf0f..8be35fbf 100644 --- a/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala +++ b/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala @@ -81,36 +81,33 @@ class ExternalizerAdditionalSpec extends AnyWordSpec with Matchers with BaseProp rtExt.get should be(List("a", "b", "c")) } - "handle complex objects with nested structures" in { - val complex: Map[String, Any] = Map( - "list" -> List(1, 2, 3), - "set" -> Set("a", "b"), - "nested" -> Map("inner" -> 42) - ) - val ext = Externalizer(complex) + "handle nested maps" in { + val nested = Map("a" -> Map("inner" -> 1), "b" -> Map("inner" -> 2)) + val ext = Externalizer(nested) val rtExt = rt(ext) - rtExt.get should be(complex) + rtExt.get should be(nested) } "handle Option types" in { val extSome = Externalizer(Some("value")) - val extNone = Externalizer(None) + val extNone: Externalizer[Option[String]] = Externalizer(None) rt(extSome).get should be(Some("value")) rt(extNone).get should be(None) } "handle Either types" in { - val extLeft = Externalizer(Left("error")) - val extRight = Externalizer(Right(42)) + val extLeft: Externalizer[Either[String, Int]] = Externalizer(Left("error")) + val extRight: Externalizer[Either[String, Int]] = Externalizer(Right(42)) rt(extLeft).get should be(Left("error")) rt(extRight).get should be(Right(42)) } "handle tuples" in { - val ext = Externalizer((1, "two", 3.0)) - rt(ext).get should be((1, "two", 3.0)) + val tuple = (1, "two", 3.0) + val ext = Externalizer(tuple) + rt(ext).get should be(tuple) } "work with writeExternal and readExternal" in { @@ -166,7 +163,7 @@ class ExternalizerAdditionalSpec extends AnyWordSpec with Matchers with BaseProp } "handle null values wrapped in Option" in { - val ext = Externalizer(Option(null)) + val ext: Externalizer[Option[String]] = Externalizer(Option(null)) rt(ext).get should be(None) } From ccb04c4795d4ca25259cbeed91a4b790bf48c60b Mon Sep 17 00:00:00 2001 From: Fangchen Li Date: Fri, 30 Jan 2026 11:42:16 -0800 Subject: [PATCH 3/3] Restore heterogeneous Map test with Scala 3 compatible syntax Use explicit type parameters Map[String, Any] and Externalizer[Map[String, Any]] to avoid Scala 3 type inference issues while maintaining original test coverage. Co-Authored-By: Claude Opus 4.5 --- .../twitter/chill/ExternalizerAdditionalSpec.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala b/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala index 8be35fbf..155f3a27 100644 --- a/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala +++ b/chill-scala/src/test/scala/com/twitter/chill/ExternalizerAdditionalSpec.scala @@ -81,11 +81,16 @@ class ExternalizerAdditionalSpec extends AnyWordSpec with Matchers with BaseProp rtExt.get should be(List("a", "b", "c")) } - "handle nested maps" in { - val nested = Map("a" -> Map("inner" -> 1), "b" -> Map("inner" -> 2)) - val ext = Externalizer(nested) + "handle complex objects with nested structures" in { + // Construct heterogeneous map with explicit type to avoid Scala 3 type inference issues + val complex: Map[String, Any] = Map[String, Any]( + "list" -> List(1, 2, 3), + "set" -> Set("a", "b"), + "nested" -> Map("inner" -> 42) + ) + val ext = Externalizer[Map[String, Any]](complex) val rtExt = rt(ext) - rtExt.get should be(nested) + rtExt.get should be(complex) } "handle Option types" in {