Skip to content

Commit d077bbd

Browse files
Initial steps to generate TASTy for the 2.13 library (#17526)
The aim is to have a project that compiles the 2.13 library using Scala 3 to generate the TASTy. This will require a special compilation mode to align with Scala 2 semantics. Then in a later step, we can package the TASTy files in a JAR that can be loaded with the class file JAR of the Scala 2 standard library. ### `stdlib-bootstrapped` project This PR changes the purpose of `stdlib-bootstrapped`. Now the project compiles the Scala 2.13 library (only) sources using `-Yscala2-stdlib`. With this flag, the compiler will generate code that aligns with the Scala 2 version of the library. The main purpose is to have TASTy that contains signatures that align with the Scala 2 library bytecode. Under `-Yscala2-stdlib` we currently * change the signature of the case class `unapply` methods, * do not emit mirrors, * and inline definitions case class `_N`. We add MiMa tests to this project to have a better view of the differences between the Scala 2 generated bytecode and the one generated in this PR. The bytecode differences are a useful guide of differences between the TASTy of the library and how applications will link to it. [skip community_build]
2 parents 53723a3 + 8c58cbf commit d077bbd

File tree

9 files changed

+354
-46
lines changed

9 files changed

+354
-46
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ jobs:
253253

254254
- name: MiMa
255255
run: |
256-
./project/scripts/sbt ";scala3-interfaces/mimaReportBinaryIssues ;scala3-library-bootstrapped/mimaReportBinaryIssues ;scala3-library-bootstrappedJS/mimaReportBinaryIssues; tasty-core-bootstrapped/mimaReportBinaryIssues"
256+
./project/scripts/sbt ";scala3-interfaces/mimaReportBinaryIssues ;scala3-library-bootstrapped/mimaReportBinaryIssues ;scala3-library-bootstrappedJS/mimaReportBinaryIssues; tasty-core-bootstrapped/mimaReportBinaryIssues; stdlib-bootstrapped/mimaReportBinaryIssues"
257257
258258
community_build_a:
259259
runs-on: [self-hosted, Linux]

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,10 @@ object desugar {
637637
// new C[...](p1, ..., pN)(moreParams)
638638
val (caseClassMeths, enumScaffolding) = {
639639
def syntheticProperty(name: TermName, tpt: Tree, rhs: Tree) =
640-
DefDef(name, Nil, tpt, rhs).withMods(synthetic)
640+
val mods =
641+
if ctx.settings.Yscala2Stdlib.value then synthetic | Inline
642+
else synthetic
643+
DefDef(name, Nil, tpt, rhs).withMods(mods)
641644

642645
def productElemMeths =
643646
val caseParams = derivedVparamss.head.toArray
@@ -735,13 +738,25 @@ object desugar {
735738
.withMods(appMods) :: Nil
736739
}
737740
val unapplyMeth = {
741+
def scala2LibCompatUnapplyRhs(unapplyParamName: Name) =
742+
assert(arity <= Definitions.MaxTupleArity, "Unexpected case class with tuple larger than 22: "+ cdef.show)
743+
if arity == 1 then Apply(scalaDot(nme.Option), Select(Ident(unapplyParamName), nme._1))
744+
else
745+
val tupleApply = Select(Ident(nme.scala), s"Tuple$arity".toTermName)
746+
val members = List.tabulate(arity) { n => Select(Ident(unapplyParamName), s"_${n+1}".toTermName) }
747+
Apply(scalaDot(nme.Option), Apply(tupleApply, members))
748+
738749
val hasRepeatedParam = constrVparamss.head.exists {
739750
case ValDef(_, tpt, _) => isRepeated(tpt)
740751
}
741752
val methName = if (hasRepeatedParam) nme.unapplySeq else nme.unapply
742753
val unapplyParam = makeSyntheticParameter(tpt = classTypeRef)
743-
val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name)
754+
val unapplyRHS =
755+
if (arity == 0) Literal(Constant(true))
756+
else if ctx.settings.Yscala2Stdlib.value then scala2LibCompatUnapplyRhs(unapplyParam.name)
757+
else Ident(unapplyParam.name)
744758
val unapplyResTp = if (arity == 0) Literal(Constant(true)) else TypeTree()
759+
745760
DefDef(
746761
methName,
747762
joinParams(derivedTparams, (unapplyParam :: Nil) :: Nil),

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ private sealed trait YSettings:
367367
val YfromTastyIgnoreList: Setting[List[String]] = MultiStringSetting("-Yfrom-tasty-ignore-list", "file", "List of `tasty` files in jar files that will not be loaded when using -from-tasty")
368368
val YnoExperimental: Setting[Boolean] = BooleanSetting("-Yno-experimental", "Disable experimental language features")
369369
val YlegacyLazyVals: Setting[Boolean] = BooleanSetting("-Ylegacy-lazy-vals", "Use legacy (pre 3.3.0) implementation of lazy vals")
370+
val Yscala2Stdlib: Setting[Boolean] = BooleanSetting("-Yscala2-stdlib", "Used when compiling the Scala 2 standard library")
370371

371372
val YprofileEnabled: Setting[Boolean] = BooleanSetting("-Yprofile-enabled", "Enable profiling.")
372373
val YprofileDestination: Setting[String] = StringSetting("-Yprofile-destination", "file", "Where to send profiling output - specify a file, default is to the console.", "")

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ object StdNames {
177177
final val typeTag: N = "typeTag"
178178
final val Expr: N = "Expr"
179179
final val String: N = "String"
180+
final val Option: N = "Option"
180181
final val Annotation: N = "Annotation"
181182

182183
// fictions we use as both types and terms

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
153153
assert(moduleRoot.isTerm)
154154

155155
checkVersion(using ictx)
156+
checkScala2Stdlib(using ictx)
156157

157158
private val loadingMirror = defn(using ictx) // was: mirrorThatLoaded(classRoot)
158159

@@ -239,6 +240,9 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
239240
" in " + source)
240241
}
241242

243+
private def checkScala2Stdlib(using Context): Unit =
244+
assert(!ctx.settings.Yscala2Stdlib.value, "No Scala 2 libraries should be unpickled under -Yscala2-stdlib")
245+
242246
/** The `decls` scope associated with given symbol */
243247
protected def symScope(sym: Symbol): Scope = symScopes.getOrElseUpdate(sym, newScope(0))
244248

compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,8 +639,9 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
639639
val clazz = ctx.owner.asClass
640640
val syntheticMembers = serializableObjectMethod(clazz) ::: serializableEnumValueMethod(clazz) ::: caseAndValueMethods(clazz)
641641
checkInlining(syntheticMembers)
642-
addMirrorSupport(
643-
cpy.Template(impl)(body = syntheticMembers ::: impl.body))
642+
val impl1 = cpy.Template(impl)(body = syntheticMembers ::: impl.body)
643+
if ctx.settings.Yscala2Stdlib.value then impl1
644+
else addMirrorSupport(impl1)
644645
}
645646

646647
private def checkInlining(syntheticMembers: List[Tree])(using Context): Unit =

project/Build.scala

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -922,25 +922,22 @@ object Build {
922922
javaOptions := (`scala3-compiler-bootstrapped` / javaOptions).value
923923
)
924924

925-
/** Scala library compiled by dotty using the latest published sources of the library */
925+
/** Scala 2 library compiled by dotty using the latest published sources of the library.
926+
*
927+
* This version of the library is not (yet) TASTy/binary compatible with the Scala 2 compiled library.
928+
*/
926929
lazy val `stdlib-bootstrapped` = project.in(file("stdlib-bootstrapped")).
927930
withCommonSettings(Bootstrapped).
928931
dependsOn(dottyCompiler(Bootstrapped) % "provided; compile->runtime; test->test").
929932
settings(commonBootstrappedSettings).
930933
settings(
931934
moduleName := "scala-library",
932935
javaOptions := (`scala3-compiler-bootstrapped` / javaOptions).value,
933-
Compile/scalacOptions += "-Yerased-terms",
934-
Compile/scalacOptions ++= {
935-
Seq(
936-
"-sourcepath",
937-
Seq(
938-
(Compile/sourceManaged).value / "scala-library-src",
939-
(Compile/sourceManaged).value / "dotty-library-src",
940-
).mkString(File.pathSeparator),
941-
)
936+
Compile / scalacOptions ++= {
937+
Seq("-sourcepath", ((Compile/sourceManaged).value / "scala-library-src").toString)
942938
},
943939
Compile / doc / scalacOptions += "-Ydocument-synthetic-types",
940+
scalacOptions += "-Yscala2-stdlib",
944941
scalacOptions -= "-Xfatal-warnings",
945942
ivyConfigurations += SourceDeps.hide,
946943
transitiveClassifiers := Seq("sources"),
@@ -970,36 +967,6 @@ object Build {
970967
((trgDir ** "*.scala") +++ (trgDir ** "*.java")).get.toSet
971968
} (Set(scalaLibrarySourcesJar)).toSeq
972969
}.taskValue,
973-
(Compile / sourceGenerators) += Def.task {
974-
val s = streams.value
975-
val cacheDir = s.cacheDirectory
976-
val trgDir = (Compile / sourceManaged).value / "dotty-library-src"
977-
978-
// NOTE `sourceDirectory` is used for actual copying,
979-
// but `sources` are used as cache keys
980-
val dottyLibSourceDirs = (`scala3-library-bootstrapped`/Compile/unmanagedSourceDirectories).value
981-
def dottyLibSources = dottyLibSourceDirs.foldLeft(PathFinder.empty) { (pf, dir) =>
982-
if (!dir.exists) pf else pf +++ (dir ** "*.scala") +++ (dir ** "*.java")
983-
}
984-
985-
val cachedFun = FileFunction.cached(
986-
cacheDir / s"copyDottyLibrarySrc",
987-
FilesInfo.lastModified,
988-
FilesInfo.exists,
989-
) { _ =>
990-
if (trgDir.exists) IO.delete(trgDir)
991-
dottyLibSourceDirs.foreach { dir =>
992-
if (dir.exists) {
993-
s.log.info(s"Copying scala3-library sources from $dir to $trgDir...")
994-
IO.copyDirectory(dir, trgDir)
995-
}
996-
}
997-
998-
((trgDir ** "*.scala") +++ (trgDir ** "*.java")).get.toSet
999-
}
1000-
1001-
cachedFun(dottyLibSources.get.toSet).toSeq
1002-
}.taskValue,
1003970
(Compile / sources) ~= (_.filterNot(file =>
1004971
// sources from https://github.com/scala/scala/tree/2.13.x/src/library-aux
1005972
file.getPath.endsWith("scala-library-src/scala/Any.scala") ||
@@ -1008,9 +975,29 @@ object Build {
1008975
file.getPath.endsWith("scala-library-src/scala/Nothing.scala") ||
1009976
file.getPath.endsWith("scala-library-src/scala/Null.scala") ||
1010977
file.getPath.endsWith("scala-library-src/scala/Singleton.scala"))),
978+
(Compile / sources) := {
979+
val files = (Compile / sources).value
980+
val overwritenSourcesDir = (Compile / scalaSource).value
981+
val overwritenSources = files.flatMap(_.relativeTo(overwritenSourcesDir)).toSet
982+
val reference = (Compile/sourceManaged).value / "scala-library-src"
983+
files.filterNot(_.relativeTo(reference).exists(overwritenSources))
984+
},
1011985
(Test / managedClasspath) ~= {
1012986
_.filterNot(file => file.data.getName == s"scala-library-${stdlibVersion(Bootstrapped)}.jar")
1013987
},
988+
mimaCheckDirection := "both",
989+
mimaBackwardIssueFilters := MiMaFilters.StdlibBootstrappedBackwards,
990+
mimaForwardIssueFilters := MiMaFilters.StdlibBootstrappedForward,
991+
mimaPreviousArtifacts += "org.scala-lang" % "scala-library" % stdlibVersion(Bootstrapped),
992+
mimaExcludeAnnotations ++= Seq(
993+
"scala.annotation.experimental",
994+
"scala.annotation.specialized",
995+
"scala.annotation.unspecialized",
996+
),
997+
// TODO package only TASTy files.
998+
// We first need to check that a project can depend on a JAR that only contains TASTy files.
999+
// Compile / exportJars := true,
1000+
// Compile / packageBin / mappings ~= { _.filter(_._2.endsWith(".tasty")) },
10141001
)
10151002

10161003
/** Test the tasty generated by `stdlib-bootstrapped`

0 commit comments

Comments
 (0)