diff --git a/examples/testing/multi_frameworks_toolchain/BUILD b/examples/testing/multi_frameworks_toolchain/BUILD index 27eedcf7e..53baf7211 100644 --- a/examples/testing/multi_frameworks_toolchain/BUILD +++ b/examples/testing/multi_frameworks_toolchain/BUILD @@ -31,5 +31,7 @@ setup_scala_testing_toolchain( ], specs2_junit_classpath = [ "@io_bazel_rules_scala_org_specs2_specs2_junit", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], ) diff --git a/scala/BUILD b/scala/BUILD index 7663b980a..94f81a12b 100644 --- a/scala/BUILD +++ b/scala/BUILD @@ -34,6 +34,7 @@ scala_toolchain( [ toolchain( name = tc, + target_settings = ["@io_bazel_rules_scala_config//:scala_version" + version_suffix(SCALA_VERSION)], toolchain = tc + "_impl", toolchain_type = "//scala:toolchain_type", visibility = ["//visibility:public"], diff --git a/scala/private/common_attributes.bzl b/scala/private/common_attributes.bzl index a71080296..a33c396ed 100644 --- a/scala/private/common_attributes.bzl +++ b/scala/private/common_attributes.bzl @@ -78,6 +78,11 @@ common_attrs.update({ executable = True, cfg = "exec", ), + "_dottyijar": attr.label( + cfg = "exec", + default = "//src/scala/io/bazel/rules_scala/dottyijar", + executable = True, + ), }) implicit_deps = { diff --git a/scala/private/macros/scala_repositories.bzl b/scala/private/macros/scala_repositories.bzl index 81b6c9cbf..f7f462510 100644 --- a/scala/private/macros/scala_repositories.bzl +++ b/scala/private/macros/scala_repositories.bzl @@ -37,6 +37,13 @@ _COMPILER_SOURCES_ENTRY_TEMPLATE = """ "@io_bazel_rules_scala_config//:scala_version{scala_version_suffix}": "@scala_compiler_source{scala_version_suffix}//:src",""" +_IZUMI_REFLECT_DEPS = ["dev_zio_izumi_reflect_thirdparty_boopickle_shaded"] +_JUNIT_DEPS = ["io_bazel_rules_scala_org_hamcrest_hamcrest_core"] +_SPECS2_DEPS = [ + "org_portable_scala_portable_scala_reflect", + "org_scala_sbt_test_interface", +] + def _compiler_sources_repo_impl(rctx): sources = [ _COMPILER_SOURCES_ENTRY_TEMPLATE.format( @@ -144,12 +151,19 @@ def rules_scala_setup(scala_compiler_srcjar = None): def _artifact_ids(scala_version): result = [ + "commons_io_commons_io", + "io_bazel_rules_scala_junit_junit", + "io_bazel_rules_scala_org_specs2_specs2_common", + "io_bazel_rules_scala_org_specs2_specs2_core", + "io_bazel_rules_scala_org_specs2_specs2_fp", + "io_bazel_rules_scala_org_specs2_specs2_junit", + "io_bazel_rules_scala_org_specs2_specs2_matcher", "io_bazel_rules_scala_scala_compiler", "io_bazel_rules_scala_scala_library", "io_bazel_rules_scala_scala_parser_combinators", "io_bazel_rules_scala_scala_xml", "org_scala_lang_modules_scala_collection_compat", - ] + ] + _JUNIT_DEPS + _SPECS2_DEPS if scala_version.startswith("2."): result.extend([ @@ -167,12 +181,14 @@ def _artifact_ids(scala_version): if scala_version.startswith("3."): result.extend([ + "dev_zio_izumi_reflect", "io_bazel_rules_scala_scala_asm", "io_bazel_rules_scala_scala_compiler_2", "io_bazel_rules_scala_scala_interfaces", "io_bazel_rules_scala_scala_library_2", "io_bazel_rules_scala_scala_reflect_2", "io_bazel_rules_scala_scala_tasty_core", + "io_bazel_rules_scala_scala_tasty_inspector", "org_jline_jline_native", "org_jline_jline_reader", "org_jline_jline_terminal", @@ -181,6 +197,8 @@ def _artifact_ids(scala_version): "org_scala_sbt_util_interface", ]) + result.extend(_IZUMI_REFLECT_DEPS) + return result def rules_scala_toolchain_deps_repositories( diff --git a/scala/private/phases/phase_compile.bzl b/scala/private/phases/phase_compile.bzl index d1534aabb..c1df0ada9 100644 --- a/scala/private/phases/phase_compile.bzl +++ b/scala/private/phases/phase_compile.bzl @@ -41,8 +41,10 @@ def phase_compile_library(ctx, p): return _phase_compile_default(ctx, p, args) def phase_compile_library_for_plugin_bootstrapping(ctx, p): + is_scala_2 = ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].scala_version.startswith("2.") + args = struct( - buildijar = ctx.attr.build_ijar, + buildijar = ctx.attr.build_ijar and is_scala_2, ) return _phase_compile_default(ctx, p, args) @@ -102,13 +104,11 @@ def phase_compile_common(ctx, p): return _phase_compile_default(ctx, p) def _phase_compile_default(ctx, p, _args = struct()): - buildijar_default_value = True if ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].scala_version.startswith("2.") else False - return _phase_compile( ctx, p, _args.srcjars if hasattr(_args, "srcjars") else depset(), - _args.buildijar if hasattr(_args, "buildijar") else buildijar_default_value, + not hasattr(_args, "buildijar") or _args.buildijar, _args.implicit_junit_deps_needed_for_java_compilation if hasattr(_args, "implicit_junit_deps_needed_for_java_compilation") else [], unused_dependency_checker_ignored_targets = _args.unused_dependency_checker_ignored_targets if hasattr(_args, "unused_dependency_checker_ignored_targets") else [], ) @@ -228,12 +228,7 @@ def _compile_or_empty( # build ijar if needed if buildijar: - ijar = java_common.run_ijar( - ctx.actions, - jar = ctx.outputs.jar, - target_label = ctx.label, - java_toolchain = specified_java_compile_toolchain(ctx), - ) + ijar = _build_ijar(ctx) else: # macro code needs to be available at compile-time, # so set ijar == jar @@ -266,6 +261,41 @@ def _compile_or_empty( merged_provider = merged_provider, ) +def _build_ijar(ctx): + scala_version = ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].scala_version + is_scala_2 = scala_version.startswith("2.") + + if is_scala_2: + return java_common.run_ijar( + ctx.actions, + jar = ctx.outputs.jar, + target_label = ctx.label, + java_toolchain = specified_java_compile_toolchain(ctx), + ) + + is_scala_3_3_or_lower = scala_version.startswith("3.") and int(scala_version.split(".")[1]) < 4 + + # Prior to Scala v3.4.0, TASTy files couldn't be read directly without a `.class` file present and its + # "TASTY" attributes preserved: + # https://github.com/scala/scala3/pull/17594 + if is_scala_3_3_or_lower: + return ctx.outputs.jar + + output = ctx.actions.declare_file("{}-ijar.jar".format(ctx.label.name)) + arguments = ctx.actions.args() + arguments.add(ctx.outputs.jar) + arguments.add(output) + + ctx.actions.run( + arguments = [arguments], + executable = ctx.executable._dottyijar, + inputs = [ctx.outputs.jar], + mnemonic = "DottyIjar", + outputs = [output], + ) + + return output + def _build_nosrc_jar(ctx): resources = [s + ":" + t for t, s in _resource_paths(ctx.files.resources, ctx.attr.resource_strip_prefix)] diff --git a/scala_config.bzl b/scala_config.bzl index 70cf132c3..d8ffeaf4c 100644 --- a/scala_config.bzl +++ b/scala_config.bzl @@ -29,12 +29,24 @@ def _store_config(repository_ctx): ) # All versions supported - scala_versions = repository_ctx.attr.scala_versions + if "SCALA_VERSIONS" in repository_ctx.os.environ: + scala_versions = repository_ctx.os.environ["SCALA_VERSIONS"].split(",") + else: + scala_versions = repository_ctx.attr.scala_versions + if not scala_versions: scala_versions = [scala_version] elif scala_version not in scala_versions: fail("You have to include the default Scala version (%s) in the `scala_versions` list." % scala_version) + # dottyijar requires Scala v3.6.2, but we don't want to force the caller to always provide 3.6.2. Therefore, we + # append it if it hasn't been provided. + # + # Once we move to Bzlmod, this shouldn't be a problem, since we can register a toolchain for Scala v3.6.2 without + # requiring users of this ruleset to do so. + if "3.6.2" not in scala_versions: + scala_versions = scala_versions + ["3.6.2"] + enable_compiler_dependency_tracking = repository_ctx.os.environ.get( "ENABLE_COMPILER_DEPENDENCY_TRACKING", str(repository_ctx.attr.enable_compiler_dependency_tracking), diff --git a/scripts/create_repository.py b/scripts/create_repository.py index 9ade78254..8931096c1 100755 --- a/scripts/create_repository.py +++ b/scripts/create_repository.py @@ -76,12 +76,12 @@ def select_root_artifacts(scala_version, scala_major, is_scala_3) -> List[str]: scala_2_version = scala_version scala_2_major = scala_major - scalatest_major = scala_major + scala_3_major = scala_major if is_scala_3: scala_2_version = max_scala_2_version scala_2_major = max_scala_2_major - scalatest_major = '3' + scala_3_major = '3' scalafmt_version = SCALAFMT_VERSION scalapb_version = SCALAPB_VERSION @@ -92,29 +92,37 @@ def select_root_artifacts(scala_version, scala_major, is_scala_3) -> List[str]: scalapb_version = '0.9.8' protoc_bridge_version = '0.7.14' + if is_scala_3: + # Versions greater than v4.20.0 depend on a version of the Scala standard library greater than v3.1.3. This is a + # problem because Scala v3.1.3, which we support, needs to use a matching version of the Scala standard library. + specs2_version = '4.20.0' + elif scala_major == '2.11': + specs2_version = '4.10.6' + else: + specs2_version = '4.20.9' + root_artifacts = [ - 'com.google.api.grpc:proto-google-common-protos:' + - GRPC_COMMON_PROTOS_VERSION, + f'com.google.api.grpc:proto-google-common-protos:{GRPC_COMMON_PROTOS_VERSION}', f'com.google.guava:guava:{GUAVA_VERSION}', f'com.google.protobuf:protobuf-java:{PROTOBUF_JAVA_VERSION}', - f'com.thesamet.scalapb:compilerplugin_{scala_2_major}:' + - scalapb_version, - f'com.thesamet.scalapb:protoc-bridge_{scala_2_major}:' + - protoc_bridge_version, - f'com.thesamet.scalapb:scalapb-runtime_{scala_2_major}:' + - scalapb_version, - f'com.thesamet.scalapb:scalapb-runtime-grpc_{scala_2_major}:' + - scalapb_version, - f'org.scala-lang.modules:scala-parser-combinators_{scala_2_major}:' + - PARSER_COMBINATORS_VERSION, + f'com.thesamet.scalapb:compilerplugin_{scala_2_major}:{scalapb_version}', + f'com.thesamet.scalapb:protoc-bridge_{scala_2_major}:{protoc_bridge_version}', + f'com.thesamet.scalapb:scalapb-runtime-grpc_{scala_2_major}:{scalapb_version}', + f'com.thesamet.scalapb:scalapb-runtime_{scala_2_major}:{scalapb_version}', + 'commons-io:commons-io:2.18.0', + f'org.scala-lang.modules:scala-parser-combinators_{scala_2_major}:{PARSER_COMBINATORS_VERSION}', f'org.scala-lang:scala-compiler:{scala_2_version}', f'org.scala-lang:scala-library:{scala_2_version}', f'org.scala-lang:scala-reflect:{scala_2_version}', f'org.scala-lang:scalap:{scala_2_version}', f'org.scalameta:scalafmt-core_{scala_2_major}:{scalafmt_version}', - f'org.scalatest:scalatest_{scalatest_major}:{SCALATEST_VERSION}', - f'org.typelevel:kind-projector_{scala_2_version}:' + - KIND_PROJECTOR_VERSION, + f'org.scalatest:scalatest_{scala_3_major}:{SCALATEST_VERSION}', + f'org.specs2:specs2-common_{scala_3_major}:{specs2_version}', + f'org.specs2:specs2-core_{scala_3_major}:{specs2_version}', + f'org.specs2:specs2-fp_{scala_3_major}:{specs2_version}', + f'org.specs2:specs2-junit_{scala_3_major}:{specs2_version}', + f'org.specs2:specs2-matcher_{scala_3_major}:{specs2_version}', + f'org.typelevel:kind-projector_{scala_2_version}:{KIND_PROJECTOR_VERSION}', ] + [f'io.grpc:grpc-{lib}:{GRPC_VERSION}' for lib in GRPC_LIBS] if scala_version == max_scala_2_version or is_scala_3: @@ -123,16 +131,20 @@ def select_root_artifacts(scala_version, scala_major, is_scala_3) -> List[str]: if is_scala_3: root_artifacts.extend([ - f'org.scala-lang:scala3-library_3:{scala_version}', + # Versions of izumi-reflect greater than v2.2.1 depend on a version of the Scala standard library greater + # than v3.1.3. This is a problem because Scala v3.1.3, which we support, needs to use a matching version of + # the Scala standard library. + 'dev.zio:izumi-reflect_3:2.2.1', + f'org.jline:jline-reader:{JLINE_VERSION}', + f'org.jline:jline-terminal:{JLINE_VERSION}', + f'org.jline:jline-terminal-jna:{JLINE_VERSION}', f'org.scala-lang:scala3-compiler_3:{scala_version}', + f'org.scala-lang:scala3-library_3:{scala_version}', f'org.scala-lang:scala3-interfaces:{scala_version}', + f'org.scala-lang:scala3-tasty-inspector_3:{scala_version}', f'org.scala-lang:tasty-core_3:{scala_version}', - 'org.scala-sbt:compiler-interface:' + - SBT_COMPILER_INTERFACE_VERSION, + f'org.scala-sbt:compiler-interface:{SBT_COMPILER_INTERFACE_VERSION}', f'org.scala-sbt:util-interface:{SBT_UTIL_INTERFACE_VERSION}', - f'org.jline:jline-reader:{JLINE_VERSION}', - f'org.jline:jline-terminal:{JLINE_VERSION}', - f'org.jline:jline-terminal-jna:{JLINE_VERSION}', ]) else: diff --git a/specs2/specs2_junit.bzl b/specs2/specs2_junit.bzl index d6fbf53da..359ac2d1e 100644 --- a/specs2/specs2_junit.bzl +++ b/specs2/specs2_junit.bzl @@ -13,6 +13,8 @@ load("//third_party/repositories:repositories.bzl", "repositories") def specs2_junit_artifact_ids(): return [ "io_bazel_rules_scala_org_specs2_specs2_junit", + "org_portable_scala_portable_scala_reflect", + "org_scala_sbt_test_interface", ] def specs2_junit_repositories( diff --git a/src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala b/src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala index 0acdf8454..52ad0118f 100644 --- a/src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala +++ b/src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala @@ -179,6 +179,6 @@ class FilteredSpecs2ClassRunner(parentRunner: org.specs2.runner.JUnitRunner, tes private implicit class `Collection Regex Extensions`(coll: List[String]) { def toRegexAlternation: Option[String] = if (coll.isEmpty) None - else Some(coll.map(_.toQuotedRegex).mkString("(", "|", ")")) + else Some(coll.map(_.toQuotedRegex).mkString("^(", "|", ")$")) } } diff --git a/src/scala/io/bazel/rules_scala/dottyijar/BUILD b/src/scala/io/bazel/rules_scala/dottyijar/BUILD new file mode 100644 index 000000000..e8dd594e7 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/BUILD @@ -0,0 +1,42 @@ +load("//scala:scala.bzl", "scala_library_for_plugin_bootstrapping", "scala_specs2_junit_test") +load("@rules_java//java:defs.bzl", "java_binary") + +scala_library_for_plugin_bootstrapping( + name = "dottyijar-library", + srcs = glob( + ["*.scala"], + exclude = ["*.spec.scala"], + ), + scala_version = "3.6.2", + visibility = ["//visibility:public"], + deps = [ + "//src/scala/io/bazel/rules_scala/dottyijar/tasty", + "//src/scala/io/bazel/rules_scala/dottyijar/tasty/format", + "//src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric", + "@dev_zio_izumi_reflect_3_6_2", + ], +) + +# TODO: Eventually, we should make a bootstrapping toolchain so we don't have to create a `*_for_plugin_bootstrapping` equivalent for each Scala rule. +java_binary( + name = "dottyijar", + main_class = "io.bazel.rules_scala.dottyijar.DottyIjar", + visibility = ["//visibility:public"], + runtime_deps = [":dottyijar-library"], +) + +scala_specs2_junit_test( + name = "specs", + srcs = glob(["*.spec.scala"]), + scala_version = "3.6.2", + suffixes = ["Spec"], + deps = [ + ":dottyijar-library", + "//src/scala/io/bazel/rules_scala/dottyijar/tasty:test", + "@io_bazel_rules_scala_org_specs2_specs2_common_3_6_2", + "@io_bazel_rules_scala_org_specs2_specs2_core_3_6_2", + "@io_bazel_rules_scala_org_specs2_specs2_junit", + "@io_bazel_rules_scala_org_specs2_specs2_matcher_3_6_2", + "@io_bazel_rules_scala_scala_tasty_inspector_3_6_2", + ], +) diff --git a/src/scala/io/bazel/rules_scala/dottyijar/DottyIjar.scala b/src/scala/io/bazel/rules_scala/dottyijar/DottyIjar.scala new file mode 100644 index 000000000..3d0b721a9 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/DottyIjar.scala @@ -0,0 +1,119 @@ +package io.bazel.rules_scala.dottyijar + +import io.bazel.rules_scala.dottyijar.tasty.Tasty +import io.bazel.rules_scala.dottyijar.tasty.format.{TastyFormat, TastyReader, TastyWriter} +import java.io.FileOutputStream +import java.nio.file.{Path, Paths} +import java.nio.file.attribute.FileTime +import java.util.zip.{ZipEntry, ZipFile, ZipOutputStream} +import scala.jdk.CollectionConverters.* + +object DottyIjar { + private def writeInterfaceJar(inputJar: ZipFile, outputStream: ZipOutputStream): Unit = { + def copyEntryWithContent(entry: ZipEntry, content: Array[Byte]): Unit = { + val newEntry = new ZipEntry(entry.getName) + + newEntry.setCreationTime(FileTime.fromMillis(0)) + newEntry.setLastAccessTime(FileTime.fromMillis(0)) + newEntry.setLastModifiedTime(FileTime.fromMillis(0)) + + outputStream.putNextEntry(newEntry) + outputStream.write(content, 0, content.length) + } + + def copyEntry(entry: ZipEntry): Unit = copyEntryWithContent(entry, inputJar.getInputStream(entry).readAllBytes()) + + outputStream.setComment(inputJar.getComment) + outputStream.setLevel(0) + + val entryNames = inputJar.entries.asScala.map(_.getName).toSet + + inputJar.entries.asScala.foreach { + case entry if entry.getName.startsWith("META-INF/") => copyEntry(entry) + case entry if entry.getName.endsWith(".class") => + val i = entry.getName.lastIndexOf('/') + val directory = entry.getName.slice(0, i) + val filename = entry.getName.slice(i + 1, entry.getName.length) + val j = filename.indexOf("$") + val tastyFileBaseName = if (j == -1) filename.stripSuffix(".class") else filename.slice(0, j) + + if (!entryNames(s"$directory/$tastyFileBaseName.tasty")) { + copyEntry(entry) + } + + case entry if entry.getName.endsWith(".tasty") => + val content = inputJar.getInputStream(entry).readAllBytes() + val updatedContent = TastyUpdater.updateTastyFile(content) + + copyEntryWithContent(entry, updatedContent) + + case entry => copyEntry(entry) + } + } + + def main(arguments: Array[String]): Unit = Arguments + .parseArguments(arguments) + .fold( + println, + arguments => { + val inputJar = new ZipFile(arguments.inputPath.toFile) + + try { + val outputStream = new ZipOutputStream(new FileOutputStream(arguments.outputPath.toFile)) + + try { + writeInterfaceJar(inputJar, outputStream) + } finally { + outputStream.close() + } + } finally { + inputJar.close() + } + }, + ) +} + +private case class Arguments(inputPath: Path, outputPath: Path) + +object Arguments { + def parseArguments(arguments: Array[String]): Either[String, Arguments] = arguments + .foldLeft[Either[String, UnvalidatedArguments]](Right(UnvalidatedArguments())) { + case (unvalidatedArguments, argument) => + unvalidatedArguments.flatMap { unvalidatedArguments => + argument match { + case "-h" | "--help" => + Left( + """dottyijar removes information from Scala 3 JARs that aren't needed for compilation. + | + |Usage: + | dottyijar + | dottyijar -h | --help + | + |Options: + | -h --help Show this screen.""".stripMargin, + ) + + case _ => + lazy val path = Paths.get(argument) + + if (unvalidatedArguments.inputPath.isEmpty) { + Right(unvalidatedArguments.copy(inputPath = Some(path))) + } else if (unvalidatedArguments.outputPath.isEmpty) { + Right(unvalidatedArguments.copy(outputPath = Some(path))) + } else { + Left(s"Unexpected argument: $argument") + } + } + } + } + .flatMap(_.validate) +} + +private case class UnvalidatedArguments(inputPath: Option[Path] = None, outputPath: Option[Path] = None) { + def validate: Either[String, Arguments] = ( + for { + inputPath <- inputPath + outputPath <- outputPath + } yield Arguments(inputPath, outputPath) + ).toRight("Please provide paths to the input and output JARs.") +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/TastyUpdater.scala b/src/scala/io/bazel/rules_scala/dottyijar/TastyUpdater.scala new file mode 100644 index 000000000..4c194a293 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/TastyUpdater.scala @@ -0,0 +1,256 @@ +package io.bazel.rules_scala.dottyijar + +import java.util.UUID +import io.bazel.rules_scala.dottyijar.tasty.* +import io.bazel.rules_scala.dottyijar.tasty.format.{MarkerType, TastyFormat, TastyReader, TastyReference, TastyWriter} +import io.bazel.rules_scala.dottyijar.tasty.numeric.UnsignedInt +import scala.collection.mutable + +/** + * This class is the meat of dottyijar. It's responsible for transforming the read TASTy file into the one written by + * dottyijar. Currently all it does is: + * - Clear the TASTy UUID, positions section, and comments section + * - Remove all `val` and `def` definitions that are private and not inline + * - Replace the values of all `val` and `def` definitions and class parent arguments with `???` + * - Remove unused names from the name table + */ +private class ContextualTastyUpdater(oldTasty: Tasty) { + private var nextNameIndex = oldTasty.nameTable.names.length + private val addedNames = mutable.ArrayBuffer.empty[TastyName] + private def getOrCreateNameReference(name: TastyName): TastyNameReference = oldTasty.nameTable.names.zipWithIndex + .collectFirst { case (`name`, i) => TastyNameReference(UnsignedInt(i)) } + .getOrElse( + TastyNameReference( + UnsignedInt({ + val i = nextNameIndex + + nextNameIndex += 1 + addedNames += name + + i + }), + ), + ) + + private lazy val scalaNameReference = getOrCreateNameReference(TastyName.Simple("scala")) + private lazy val predefNameReference = getOrCreateNameReference(TastyName.Simple("Predef")) + private lazy val `???nameReference` = getOrCreateNameReference(TastyName.Simple("???")) + private lazy val `???identifier` = TastyTerm.Identifier( + `???nameReference`, + TastyType.NonLocalReference(predefNameReference, TastyType.PackageReference(scalaNameReference)), + ) + + private def updateAstsSection( + section: TastySection[TastySectionPayload.Asts], + ): TastySection[TastySectionPayload.Asts] = + section.copy(payload = updateAstsSectionPayload(section.payload)) + + private def updateAstsSectionPayload(payload: TastySectionPayload.Asts): TastySectionPayload.Asts = + TastySectionPayload.Asts(payload.topLevelStatements.flatMap(updateTopLevelStatement)) + + private def updateStatement(statement: TastyStatement): Option[TastyStatement] = + statement match { + case _: TastyTerm => None + case definition: TastyValOrDefDefinition => updateValOrDefDefinition(definition) + case definition: TastyTypeDefinition => Some(updateTypeDefinition(definition)) + case _ => Some(statement) + } + + private def updateTemplate(template: TastyTemplate): TastyTemplate = + template.copy( + parents = template.parents.map { + case apply: TastyTerm.Apply => apply.copy(arguments = apply.arguments.map(_ => `???identifier`)) + case block: TastyTerm.Block => block.copy(statements = block.statements.flatMap(updateStatement)) + case parent => parent + }, + statements = template.statements.flatMap(updateStatement), + ) + + private def updateTopLevelStatement(statement: TastyTopLevelStatement): Option[TastyTopLevelStatement] = + statement match { + case statement: TastyPackageStatement => + Some(statement.copy(topLevelStatements = statement.topLevelStatements.flatMap(updateTopLevelStatement))) + + case statement: TastyStatement => updateStatement(statement) + } + + private def updateTypeDefinition(definition: TastyTypeDefinition): TastyTypeDefinition = definition match { + case TastyTypeDefinition(name, template: TastyTemplate, information, modifiers) => + TastyTypeDefinition(name, updateTemplate(template), information, modifiers) + + case _ => definition + } + + private def updateValOrDefDefinition(definition: TastyValOrDefDefinition): Option[TastyValOrDefDefinition] = { + val modifiers = definition match { + case definition: TastyValOrDefDefinition.Def => definition.modifiers + case definition: TastyValOrDefDefinition.Val => definition.modifiers + } + + val isObject = modifiers.exists { + case _: TastyModifier.Object => true + case _ => false + } + + lazy val isPrivate = modifiers.exists { + case _: TastyModifier.Private => true + case _ => false + } + + lazy val isInline = modifiers.exists { + case _: TastyModifier.Inline => true + case _ => false + } + + if (!isObject && isPrivate && !isInline) { + None + } else { + definition match { + case TastyValOrDefDefinition.Val(name, tastyType, Some(_), modifiers, information) => + Some(TastyValOrDefDefinition.Val(name, tastyType, Some(`???identifier`), modifiers, information)) + + case TastyValOrDefDefinition.Def(name, parameters, tastyType, Some(_), modifiers, information) => + Some(TastyValOrDefDefinition.Def(name, parameters, tastyType, Some(`???identifier`), modifiers, information)) + + case _ => Some(definition) + } + } + } + + lazy val updatedTasty: Tasty = { + val updatedAstsSection = oldTasty.astsSection.map(updateAstsSection) + val updatedNameTable = TastyNameTable(oldTasty.nameTable.names ++ addedNames) + + oldTasty.copy( + uuid = new UUID(0, 0), + nameTable = updatedNameTable, + astsSection = updatedAstsSection, + positionsSection = None, + commentsSection = None, + ) + } +} + +private object TastyUpdater { + private def getUsedNameIndices(tasty: Tasty): Set[Int] = { + val usedInSections = tasty.astsSection.map(getUsedNameIndicesInSection).getOrElse(Iterable.empty) ++ + tasty.positionsSection.map(getUsedNameIndicesInSection).getOrElse(Iterable.empty) ++ + tasty.attributesSection.map(getUsedNameIndicesInSection).getOrElse(Iterable.empty) + + // I'm pretty sure the graph of name dependencies is acyclic, so a standard depth-first search should work + val result = mutable.Set.empty[Int] + val stack = mutable.ArrayBuffer.from(usedInSections) + + while (stack.nonEmpty) { + val i = stack.remove(stack.length - 1) + + if (!result(i)) { + result += i + + TastyElement + .collect(tasty.nameTable.names(i)) { case TastyNameReference(j, _) => stack += j.value } + .foreach { _ => } + } + } + + result.toSet + } + + private def getUsedNameIndicesInSection[A <: TastySectionPayload]( + section: TastySection[A], + )(using TastyElement[TastySection[A]]): Iterable[Int] = + TastyElement.collect(section) { case TastyNameReference(i, _) => i.value } + + private def removeDanglingSharedValues[A: TastyElement]( + node: A, + handledReferences: mutable.Set[TastyReference[? <: MarkerType, ?]], + oldTastyDereferencer: TastyDereferencer, + updatedTastyDeferencer: TastyDereferencer, + ): A = { + val sharedReference = node match { + case TastyPath.Shared(reference, _) => Some(reference) + case TastyTerm.Shared(reference, _) => Some(reference) + case TastyType.Shared(reference, _) => Some(reference) + case TastyTypeTree.Shared(reference, _) => Some(reference) + case _ => None + } + + sharedReference + .map { reference => + if (handledReferences(reference) || updatedTastyDeferencer.isValidReference(reference)) { + node + } else { + handledReferences += reference + + /** + * None of the AST nodes reference types more specific than [[TastyPath]], [[TastyTerm]], [[TastyType]], or + * [[TastyTypeTree]], but the compiler doesn't know that, so we cast the updated value to [[A]]. + */ + val dereferenced = + oldTastyDereferencer.dereference(reference.asInstanceOf[TastyReference[? <: MarkerType, A]]) + + removeDanglingSharedValues(dereferenced, handledReferences, oldTastyDereferencer, updatedTastyDeferencer) + } + } + .getOrElse( + TastyElement.map(node)( + [B] => + child => removeDanglingSharedValues(child, handledReferences, oldTastyDereferencer, updatedTastyDeferencer), + ), + ) + } + + /** + * Remove shared paths, terms, types, and type trees whose referenced values don't exist in a given [[Tasty]]. I + * believe the compiler shares these nodes to save space, even when the shared values aren't linked in any way. + * However, since dottyijar removes private methods and method implementations, it's possible that some shared values' + * referenced values could be deleted, rendering the references dangling. In this situation, we "inline" one of the + * shared values so that the others can reference it. + */ + private def removeDanglingSharedValuesFromTasty(oldTasty: Tasty, updatedTasty: Tasty): Tasty = updatedTasty.copy( + astsSection = updatedTasty.astsSection + .map { section => + section.copy( + payload = removeDanglingSharedValues( + section.payload, + handledReferences = mutable.Set.empty, + oldTastyDereferencer = TastyDereferencer(oldTasty), + updatedTastyDeferencer = TastyDereferencer(updatedTasty), + ), + ) + }, + ) + + private def removeUnusedNames(tasty: Tasty): Tasty = { + val usedNameIndices = getUsedNameIndices(tasty) + val nameIndexUpdates = usedNameIndices.toList.sorted.view.zipWithIndex.toMap + + def updateNameReferences[A: TastyElement](element: A): A = TastyElement.map(element) { + [B] => + child => + child match { + case TastyNameReference(i, information) => + TastyNameReference(UnsignedInt(nameIndexUpdates(i.value)), information).asInstanceOf[B] + + case _ => updateNameReferences(child) + } + } + + updateNameReferences( + tasty.copy( + nameTable = TastyNameTable( + tasty.nameTable.names.zipWithIndex.collect { case (name, i) if usedNameIndices(i) => name }, + ), + ), + ) + } + + def updateTastyFile(content: Array[Byte]): Array[Byte] = { + val oldTasty = Tasty.read(content) + val updatedTasty = new ContextualTastyUpdater(oldTasty).updatedTasty + val withoutSharedValues = removeDanglingSharedValuesFromTasty(oldTasty, updatedTasty) + val withoutUnusedNames = removeUnusedNames(withoutSharedValues) + + withoutUnusedNames.write + } +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/TastyUpdater.spec.scala b/src/scala/io/bazel/rules_scala/dottyijar/TastyUpdater.spec.scala new file mode 100644 index 000000000..918643987 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/TastyUpdater.spec.scala @@ -0,0 +1,41 @@ +package io.bazel.rules_scala.dottyijar + +import io.bazel.rules_scala.dottyijar.tasty.TastySpecification +import java.io.File +import java.nio.file.Files +import org.specs2.execute.Result +import scala.quoted.Quotes +import scala.tasty.inspector.{Inspector, TastyInspector} + +class TastyUpdaterSpec extends TastySpecification { + "TastyUpdater" should { + "Work on every TASTy file for the Scala 3 compiler" in { + withTestCases { + Result.foreach(_) { testCase => + println(s"Testing ${testCase.path}") + + val content = testCase.inputStream.readAllBytes() + val updatedContent = TastyUpdater.updateTastyFile(content) + val i = testCase.path.lastIndexOf("/") + val j = testCase.path.lastIndexOf(".") + val baseName = testCase.path.slice(if (i == -1) 0 else i + 1, if (j == -1) testCase.path.length else j) + + /** + * [[File.createTempFile]] requires the temporary file prefix to be at least three characters long. + */ + val prefix = if (baseName.length < 3) baseName + "_" * (3 - baseName.length) else baseName + val updatedTastyFilePath = File.createTempFile(prefix, ".tasty").toPath + + Files.write(updatedTastyFilePath, updatedContent) + + TastyInspector.inspectTastyFiles(List(updatedTastyFilePath.toString))(DummyInspector) must + not(throwAn[Exception]) + } + } + } + } +} + +private object DummyInspector extends Inspector { + override def inspect(using quotes: Quotes)(tastys: List[scala.tasty.inspector.Tasty[quotes.type]]): Unit = {} +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/BUILD b/src/scala/io/bazel/rules_scala/dottyijar/tasty/BUILD new file mode 100644 index 000000000..c722c6750 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/BUILD @@ -0,0 +1,66 @@ +load("//scala:scala.bzl", "scala_library", "scala_library_for_plugin_bootstrapping", "scala_specs2_junit_test") +load("@bazel_skylib//rules:copy_file.bzl", "copy_file") + +scala_library_for_plugin_bootstrapping( + name = "tasty", + srcs = glob( + ["*.scala"], + exclude = [ + "*.spec.scala", + "*.test.scala", + ], + ), + scala_version = "3.6.2", + visibility = ["//visibility:public"], + deps = [ + "//src/scala/io/bazel/rules_scala/dottyijar/tasty/format", + "//src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric", + "@dev_zio_izumi_reflect_3_6_2", + "@dev_zio_izumi_reflect_thirdparty_boopickle_shaded_3_6_2", + "@io_bazel_rules_scala_scala_compiler_3_6_2", + "@io_bazel_rules_scala_scala_tasty_core_3_6_2", + ], +) + +copy_file( + name = "scala3-compiler-jar", + src = "@io_bazel_rules_scala_scala_compiler_3_6_2//:scala3-compiler_3-3.6.2.jar", + out = "scala3-compiler.jar", +) + +scala_library( + name = "test", + testonly = True, + srcs = glob(["*.test.scala"]), + resource_strip_prefix = package_name(), + resources = [":scala3-compiler-jar"], + scala_version = "3.6.2", + visibility = ["//visibility:public"], + deps = [ + "@commons_io_commons_io_3_6_2", + "@io_bazel_rules_scala_junit_junit", + "@io_bazel_rules_scala_org_specs2_specs2_common_3_6_2", + "@io_bazel_rules_scala_org_specs2_specs2_core_3_6_2", + "@io_bazel_rules_scala_org_specs2_specs2_junit", + "@io_bazel_rules_scala_org_specs2_specs2_matcher_3_6_2", + ], +) + +scala_specs2_junit_test( + name = "specs", + srcs = glob(["*.spec.scala"]), + jvm_flags = ["-DDEBUG_TASTYFORMAT=true"], + scala_version = "3.6.2", + suffixes = ["Spec"], + deps = [ + ":tasty", + ":test", + "//src/scala/io/bazel/rules_scala/dottyijar/tasty/format", + "//src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric", + "@io_bazel_rules_scala_org_specs2_specs2_common_3_6_2", + "@io_bazel_rules_scala_org_specs2_specs2_core_3_6_2", + "@io_bazel_rules_scala_org_specs2_specs2_junit", + "@io_bazel_rules_scala_org_specs2_specs2_matcher_3_6_2", + "@io_bazel_rules_scala_scala_compiler_3_6_2", + ], +) diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/Tasty.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/Tasty.scala new file mode 100644 index 000000000..41fa5cd8d --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/Tasty.scala @@ -0,0 +1,2767 @@ +package io.bazel.rules_scala.dottyijar.tasty + +import dotty.tools.dotc.util.Spans.Span +import dotty.tools.tasty.TastyFormat as DottyTastyFormat +import io.bazel.rules_scala.dottyijar.tasty.format.* +import io.bazel.rules_scala.dottyijar.tasty.numeric.{SignedInt, SignedLong, UnsignedInt} +import java.util.UUID +import scala.annotation.nowarn +import scala.util.control.NonFatal + +case class Tasty( + majorVersion: UnsignedInt, + minorVersion: UnsignedInt, + experimentalVersion: UnsignedInt, + versionString: String, + uuid: UUID, + nameTable: TastyNameTable, + astsSection: Option[TastySection[TastySectionPayload.Asts]], + positionsSection: Option[TastySection[TastySectionPayload.Positions]], + commentsSection: Option[TastySection[TastySectionPayload.Comments]], + attributesSection: Option[TastySection[TastySectionPayload.Attributes]], +) { + def write: Array[Byte] = { + val writer = TastyWriter.empty + + summon[TastyFormat[Tasty]].write(writer, this) + + writer.fillInReferences() + writer.toArray + } +} + +object Tasty { + def apply( + majorVersion: UnsignedInt, + minorVersion: UnsignedInt, + experimentalVersion: UnsignedInt, + versionString: String, + uuid: UUID, + nameTable: TastyNameTable, + sections: List[TastySection[? <: TastySectionPayload]], + ): Tasty = { + val astsSection: Option[TastySection[TastySectionPayload.Asts]] = sections.collectFirst { + case TastySection(name, payload: TastySectionPayload.Asts) => TastySection(name, payload) + } + + val positionsSection: Option[TastySection[TastySectionPayload.Positions]] = sections.collectFirst { + case TastySection(name, payload: TastySectionPayload.Positions) => TastySection(name, payload) + } + + val commentsSection: Option[TastySection[TastySectionPayload.Comments]] = sections.collectFirst { + case TastySection(name, payload: TastySectionPayload.Comments) => TastySection(name, payload) + } + + val attributesSection: Option[TastySection[TastySectionPayload.Attributes]] = sections.collectFirst { + case TastySection(name, payload: TastySectionPayload.Attributes) => TastySection(name, payload) + } + + Tasty( + majorVersion, + minorVersion, + experimentalVersion, + versionString, + uuid, + nameTable, + astsSection, + positionsSection, + commentsSection, + attributesSection, + ) + } + + def read(encoded: Array[Byte]): Tasty = { + val reader = TastyReader(encoded) + val result = summon[TastyFormat[Tasty]].read(reader) + + reader.linkReferences() + + result + } + + given TastyFormat[Tasty] = TastyFormat( + reader => { + reader.readMagicNumber() + + val majorVersion = summon[TastyFormat[UnsignedInt]].read(reader) + val minorVersion = summon[TastyFormat[UnsignedInt]].read(reader) + val experimentalVersion = summon[TastyFormat[UnsignedInt]].read(reader) + val versionString = summon[TastyFormat[String]].read(reader) + val uuid = summon[TastyFormat[UUID]].read(reader) + val nameTable = summon[TastyFormat[TastyNameTable]].read(reader) + val sectionTastyFormat = TastySection.tastyFormat(nameTable) + val sections = reader.readUntilEnd(sectionTastyFormat.read(reader)) + + Tasty(majorVersion, minorVersion, experimentalVersion, versionString, uuid, nameTable, sections) + }, + (writer, tasty) => { + writer.writeMagicNumber() + + summon[TastyFormat[UnsignedInt]].write(writer, tasty.majorVersion) + summon[TastyFormat[UnsignedInt]].write(writer, tasty.minorVersion) + summon[TastyFormat[UnsignedInt]].write(writer, tasty.experimentalVersion) + summon[TastyFormat[String]].write(writer, tasty.versionString) + summon[TastyFormat[UUID]].write(writer, tasty.uuid) + summon[TastyFormat[TastyNameTable]].write(writer, tasty.nameTable) + + val sectionTastyFormat = TastySection.tastyFormat(tasty.nameTable) + + tasty.astsSection.foreach(sectionTastyFormat.write(writer, _)) + tasty.positionsSection.foreach(sectionTastyFormat.write(writer, _)) + tasty.commentsSection.foreach(sectionTastyFormat.write(writer, _)) + tasty.attributesSection.foreach(sectionTastyFormat.write(writer, _)) + }, + ) +} + +case class TastyCaseDefinition( + pattern: TastyTerm, + rightHandSide: TastyTerm, + override var information: TastyReferencableInformation, + guard: Option[TastyTerm], +) extends TastyReferencable + +object TastyCaseDefinition { + private given TastyFormat[(TastyTerm, TastyTerm, TastyReferencableInformation, Option[TastyTerm])] = + TastyFormat.forOptional[(TastyTerm, TastyTerm, TastyReferencableInformation), TastyTerm] + + given TastySumType[TastyCaseDefinition] = + TastySumType.withSingleVariant(DottyTastyFormat.CASEDEF, TastyFormat.forProduct[TastyCaseDefinition]) + + given TastyFormat[TastyCaseDefinition] = TastyFormat.forSumType +} + +sealed trait TastyConstant extends TastyPath with TastyReferencable + +object TastyConstant { + given TastySumType[TastyConstant] = new TastySumType( + TastySumType.Variant[TastyUnitConstant](DottyTastyFormat.UNITconst), + TastySumType.Variant[TastyFalseConstant](DottyTastyFormat.FALSEconst), + TastySumType.Variant[TastyTrueConstant](DottyTastyFormat.TRUEconst), + TastySumType.Variant[TastyByteConstant](DottyTastyFormat.BYTEconst), + TastySumType.Variant[TastyShortConstant](DottyTastyFormat.SHORTconst), + TastySumType.Variant[TastyCharConstant](DottyTastyFormat.CHARconst), + TastySumType.Variant[TastyIntConstant](DottyTastyFormat.INTconst), + TastySumType.Variant[TastyLongConstant](DottyTastyFormat.LONGconst), + TastySumType.Variant[TastyFloatConstant](DottyTastyFormat.FLOATconst), + TastySumType.Variant[TastyDoubleConstant](DottyTastyFormat.DOUBLEconst), + TastySumType.Variant[TastyStringConstant](DottyTastyFormat.STRINGconst), + TastySumType.Variant[TastyNullConstant](DottyTastyFormat.NULLconst), + TastySumType.Variant[TastyClassConstant](DottyTastyFormat.CLASSconst), + ) + + given TastyFormat[TastyConstant] = TastyFormat.forSumType +} + +case class TastyUnitConstant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyConstant + +object TastyUnitConstant { + given TastyFormat[TastyUnitConstant] = TastyFormat.forProduct +} + +case class TastyFalseConstant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyConstant + +object TastyFalseConstant { + given TastyFormat[TastyFalseConstant] = TastyFormat.forProduct +} + +case class TastyTrueConstant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyConstant + +object TastyTrueConstant { + given TastyFormat[TastyTrueConstant] = TastyFormat.forProduct +} + +case class TastyByteConstant( + value: SignedInt, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyByteConstant { + given TastyFormat[TastyByteConstant] = TastyFormat.forProduct +} + +case class TastyShortConstant( + value: SignedInt, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyShortConstant { + given TastyFormat[TastyShortConstant] = TastyFormat.forProduct +} + +case class TastyCharConstant( + value: UnsignedInt, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyCharConstant { + given TastyFormat[TastyCharConstant] = TastyFormat.forProduct +} + +case class TastyIntConstant( + value: SignedInt, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyIntConstant { + given TastyFormat[TastyIntConstant] = TastyFormat.forProduct +} + +case class TastyLongConstant( + value: SignedLong, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyLongConstant { + given TastyFormat[TastyLongConstant] = TastyFormat.forProduct +} + +case class TastyFloatConstant( + value: SignedInt, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyFloatConstant { + given TastyFormat[TastyFloatConstant] = TastyFormat.forProduct +} + +case class TastyDoubleConstant( + value: SignedLong, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyDoubleConstant { + given TastyFormat[TastyDoubleConstant] = TastyFormat.forProduct +} + +case class TastyStringConstant( + value: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyStringConstant { + given TastyFormat[TastyStringConstant] = TastyFormat.forProduct +} + +case class TastyNullConstant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyConstant + +object TastyNullConstant { + given TastyFormat[TastyNullConstant] = TastyFormat.forProduct +} + +case class TastyClassConstant( + typeArgument: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyConstant + +object TastyClassConstant { + given TastyFormat[TastyClassConstant] = TastyFormat.forProduct +} + +case class TastyImplicitArgument( + argument: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyReferencable + +object TastyImplicitArgument { + given TastySumType[TastyImplicitArgument] = + TastySumType.withSingleVariant(DottyTastyFormat.IMPLICITarg, TastyFormat.forProduct[TastyImplicitArgument]) + + given TastyFormat[TastyImplicitArgument] = TastyFormat.forSumType +} + +sealed trait TastyModifier extends TastyReferencable + +object TastyModifier { + case class Private(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Private { + given TastyFormat[Private] = TastyFormat.forProduct + } + + case class Protected(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Protected { + given TastyFormat[Protected] = TastyFormat.forProduct + } + + case class PrivateQualified( + qualifier: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyModifier + + object PrivateQualified { + given TastyFormat[PrivateQualified] = TastyFormat.forProduct + } + + case class ProtectedQualified( + qualifier: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyModifier + + object ProtectedQualified { + given TastyFormat[ProtectedQualified] = TastyFormat.forProduct + } + + case class Abstract(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Abstract { + given TastyFormat[Abstract] = TastyFormat.forProduct + } + + case class Final(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Final { + given TastyFormat[Final] = TastyFormat.forProduct + } + + case class Sealed(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Sealed { + given TastyFormat[Sealed] = TastyFormat.forProduct + } + + case class Case(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Case { + given TastyFormat[Case] = TastyFormat.forProduct + } + + case class Implicit(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Implicit { + given TastyFormat[Implicit] = TastyFormat.forProduct + } + + case class Given(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Given { + given TastyFormat[Given] = TastyFormat.forProduct + } + + case class Erased(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Erased { + given TastyFormat[Erased] = TastyFormat.forProduct + } + + case class Lazy(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Lazy { + given TastyFormat[Lazy] = TastyFormat.forProduct + } + + case class Override(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Override { + given TastyFormat[Override] = TastyFormat.forProduct + } + + case class Opaque(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Opaque { + given TastyFormat[Opaque] = TastyFormat.forProduct + } + + case class Inline(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Inline { + given TastyFormat[Inline] = TastyFormat.forProduct + } + + case class Macro(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Macro { + given TastyFormat[Macro] = TastyFormat.forProduct + } + + case class InlineProxy(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object InlineProxy { + given TastyFormat[InlineProxy] = TastyFormat.forProduct + } + + case class Static(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Static { + given TastyFormat[Static] = TastyFormat.forProduct + } + + case class Object(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Object { + given TastyFormat[Object] = TastyFormat.forProduct + } + + case class Trait(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Trait { + given TastyFormat[Trait] = TastyFormat.forProduct + } + + case class Enum(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Enum { + given TastyFormat[Enum] = TastyFormat.forProduct + } + + case class Local(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Local { + given TastyFormat[Local] = TastyFormat.forProduct + } + + case class Synthetic(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Synthetic { + given TastyFormat[Synthetic] = TastyFormat.forProduct + } + + case class Artifact(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Artifact { + given TastyFormat[Artifact] = TastyFormat.forProduct + } + + case class Mutable(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Mutable { + given TastyFormat[Mutable] = TastyFormat.forProduct + } + + case class FieldAccessor(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object FieldAccessor { + given TastyFormat[FieldAccessor] = TastyFormat.forProduct + } + + case class CaseAccessor(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object CaseAccessor { + given TastyFormat[CaseAccessor] = TastyFormat.forProduct + } + + case class Covariant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Covariant { + given TastyFormat[Covariant] = TastyFormat.forProduct + } + + case class Contravariant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Contravariant { + given TastyFormat[Contravariant] = TastyFormat.forProduct + } + + case class HasDefault(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object HasDefault { + given TastyFormat[HasDefault] = TastyFormat.forProduct + } + + case class Stable(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Stable { + given TastyFormat[Stable] = TastyFormat.forProduct + } + + case class Extension(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Extension { + given TastyFormat[Extension] = TastyFormat.forProduct + } + + case class ParameterSetter(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object ParameterSetter { + given TastyFormat[ParameterSetter] = TastyFormat.forProduct + } + + case class ParameterAlias(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object ParameterAlias { + given TastyFormat[ParameterAlias] = TastyFormat.forProduct + } + + case class Exported(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Exported { + given TastyFormat[Exported] = TastyFormat.forProduct + } + + case class Open(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Open { + given TastyFormat[Open] = TastyFormat.forProduct + } + + case class Invisible(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Invisible { + given TastyFormat[Invisible] = TastyFormat.forProduct + } + + case class Tracked(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Tracked { + given TastyFormat[Tracked] = TastyFormat.forProduct + } + + case class Annotation( + `type`: TastyType, + value: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyModifier + + object Annotation { + given TastyFormat[Annotation] = TastyFormat.forProduct[Annotation].withLengthPrefixed + } + + /** + * This isn't formally documented in the TASTy grammar as a modifier, but you can find it here: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala#L757]] + */ + case class Transparent(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Transparent { + given TastyFormat[Transparent] = TastyFormat.forProduct + } + + /** + * This isn't formally documented in the TASTy grammar as a modifier, but you can find it here: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala#L758]] + */ + case class Infix(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyModifier + + object Infix { + given TastyFormat[Infix] = TastyFormat.forProduct + } + + given TastySumType[TastyModifier] = new TastySumType( + TastySumType.Variant[Private](DottyTastyFormat.PRIVATE), + TastySumType.Variant[Protected](DottyTastyFormat.PROTECTED), + TastySumType.Variant[PrivateQualified](DottyTastyFormat.PRIVATEqualified), + TastySumType.Variant[ProtectedQualified](DottyTastyFormat.PROTECTEDqualified), + TastySumType.Variant[Abstract](DottyTastyFormat.ABSTRACT), + TastySumType.Variant[Final](DottyTastyFormat.FINAL), + TastySumType.Variant[Sealed](DottyTastyFormat.SEALED), + TastySumType.Variant[Case](DottyTastyFormat.CASE), + TastySumType.Variant[Implicit](DottyTastyFormat.IMPLICIT), + TastySumType.Variant[Given](DottyTastyFormat.GIVEN), + TastySumType.Variant[Erased](DottyTastyFormat.ERASED), + TastySumType.Variant[Lazy](DottyTastyFormat.LAZY), + TastySumType.Variant[Override](DottyTastyFormat.OVERRIDE), + TastySumType.Variant[Opaque](DottyTastyFormat.OPAQUE), + TastySumType.Variant[Inline](DottyTastyFormat.INLINE), + TastySumType.Variant[Macro](DottyTastyFormat.MACRO), + TastySumType.Variant[InlineProxy](DottyTastyFormat.INLINEPROXY), + TastySumType.Variant[Static](DottyTastyFormat.STATIC), + TastySumType.Variant[Object](DottyTastyFormat.OBJECT), + TastySumType.Variant[Trait](DottyTastyFormat.TRAIT), + TastySumType.Variant[Enum](DottyTastyFormat.ENUM), + TastySumType.Variant[Local](DottyTastyFormat.LOCAL), + TastySumType.Variant[Synthetic](DottyTastyFormat.SYNTHETIC), + TastySumType.Variant[Artifact](DottyTastyFormat.ARTIFACT), + TastySumType.Variant[Mutable](DottyTastyFormat.MUTABLE), + TastySumType.Variant[FieldAccessor](DottyTastyFormat.FIELDaccessor), + TastySumType.Variant[CaseAccessor](DottyTastyFormat.CASEaccessor), + TastySumType.Variant[Covariant](DottyTastyFormat.COVARIANT), + TastySumType.Variant[Contravariant](DottyTastyFormat.CONTRAVARIANT), + TastySumType.Variant[HasDefault](DottyTastyFormat.HASDEFAULT), + TastySumType.Variant[Stable](DottyTastyFormat.STABLE), + TastySumType.Variant[Extension](DottyTastyFormat.EXTENSION), + TastySumType.Variant[ParameterSetter](DottyTastyFormat.PARAMsetter), + TastySumType.Variant[ParameterAlias](DottyTastyFormat.PARAMalias), + TastySumType.Variant[Exported](DottyTastyFormat.EXPORTED), + TastySumType.Variant[Open](DottyTastyFormat.OPEN), + TastySumType.Variant[Invisible](DottyTastyFormat.INVISIBLE), + TastySumType.Variant[Tracked](DottyTastyFormat.TRACKED), + TastySumType.Variant[Annotation](DottyTastyFormat.ANNOTATION), + TastySumType.Variant[Transparent](DottyTastyFormat.TRANSPARENT), + TastySumType.Variant[Infix](DottyTastyFormat.INFIX), + ) + + given TastyFormat[TastyModifier] = TastyFormat.forSumType +} + +sealed trait TastyName extends TastyReferencable + +object TastyName { + case class Simple( + name: String, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object Simple { + given TastyFormat[Simple] = TastyFormat.forProduct + } + + case class Qualified( + qualified: TastyNameReference, + selector: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object Qualified { + given TastyFormat[Qualified] = TastyFormat.forProduct[Qualified].withLengthPrefixed + } + + case class Expanded( + qualified: TastyNameReference, + selector: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object Expanded { + given TastyFormat[Expanded] = TastyFormat.forProduct[Expanded].withLengthPrefixed + } + + case class ExpandPrefix( + qualified: TastyNameReference, + selector: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object ExpandPrefix { + given TastyFormat[ExpandPrefix] = TastyFormat.forProduct[ExpandPrefix].withLengthPrefixed + } + + case class Unique( + separator: TastyNameReference, + uniqueId: UnsignedInt, + override var information: TastyReferencableInformation, + underlying: Option[TastyNameReference], + ) extends TastyName + + object Unique { + private given TastyFormat[ + (TastyNameReference, UnsignedInt, TastyReferencableInformation, Option[TastyNameReference]), + ] = TastyFormat.forOptional[(TastyNameReference, UnsignedInt, TastyReferencableInformation), TastyNameReference] + + given TastyFormat[Unique] = TastyFormat.forProduct + } + + case class DefaultGetter( + underlying: TastyNameReference, + index: UnsignedInt, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object DefaultGetter { + given TastyFormat[DefaultGetter] = TastyFormat.forProduct[DefaultGetter].withLengthPrefixed + } + + case class SuperAccessor( + underlying: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object SuperAccessor { + given TastyFormat[SuperAccessor] = TastyFormat.forProduct[SuperAccessor].withLengthPrefixed + } + + case class InlineAccessor( + underlying: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object InlineAccessor { + given TastyFormat[InlineAccessor] = TastyFormat.forProduct[InlineAccessor].withLengthPrefixed + } + + case class ObjectClass( + underlying: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object ObjectClass { + given TastyFormat[ObjectClass] = TastyFormat.forProduct[ObjectClass].withLengthPrefixed + } + + case class BodyRetainer( + underlying: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyName + + object BodyRetainer { + given TastyFormat[BodyRetainer] = TastyFormat.forProduct[BodyRetainer].withLengthPrefixed + } + + case class Signed( + original: TastyNameReference, + resultSignature: TastyNameReference, + override var information: TastyReferencableInformation, + parameterSignatures: List[TastyParameterSignature], + ) extends TastyName + + object Signed { + given TastyFormat[ + (TastyNameReference, TastyNameReference, TastyReferencableInformation, List[TastyParameterSignature]), + ] = TastyFormat.forVariadic[ + (TastyNameReference, TastyNameReference, TastyReferencableInformation), + TastyParameterSignature, + List[TastyParameterSignature], + ] + + given TastyFormat[Signed] = TastyFormat.forProduct + } + + case class TargetSigned( + original: TastyNameReference, + target: TastyNameReference, + resultSignature: TastyNameReference, + override var information: TastyReferencableInformation, + parameterSignatures: List[TastyParameterSignature], + ) extends TastyName + + object TargetSigned { + given TastyFormat[ + ( + TastyNameReference, + TastyNameReference, + TastyNameReference, + TastyReferencableInformation, + List[TastyParameterSignature], + ), + ] = TastyFormat.forVariadic[ + (TastyNameReference, TastyNameReference, TastyNameReference, TastyReferencableInformation), + TastyParameterSignature, + List[TastyParameterSignature], + ] + + given TastyFormat[TargetSigned] = TastyFormat.forProduct + } + + given TastySumType[TastyName] = new TastySumType( + TastySumType.Variant[Simple](DottyTastyFormat.NameTags.UTF8), + TastySumType.Variant[Qualified](DottyTastyFormat.NameTags.QUALIFIED), + TastySumType.Variant[Expanded](DottyTastyFormat.NameTags.EXPANDED), + TastySumType.Variant[ExpandPrefix](DottyTastyFormat.NameTags.EXPANDPREFIX), + TastySumType.Variant[Unique](DottyTastyFormat.NameTags.UNIQUE), + TastySumType.Variant[DefaultGetter](DottyTastyFormat.NameTags.DEFAULTGETTER), + TastySumType.Variant[SuperAccessor](DottyTastyFormat.NameTags.SUPERACCESSOR), + TastySumType.Variant[InlineAccessor](DottyTastyFormat.NameTags.INLINEACCESSOR), + TastySumType.Variant[ObjectClass](DottyTastyFormat.NameTags.OBJECTCLASS), + TastySumType.Variant[BodyRetainer](DottyTastyFormat.NameTags.BODYRETAINER), + TastySumType.Variant[Signed](DottyTastyFormat.NameTags.SIGNED), + TastySumType.Variant[TargetSigned](DottyTastyFormat.NameTags.TARGETSIGNED), + ) + + given TastyFormat[TastyName] = TastyFormat.forSumType +} + +case class TastyNameReference( + i: UnsignedInt, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyReferencable + +object TastyNameReference { + given TastyFormat[TastyNameReference] = TastyFormat.forProduct +} + +sealed trait TastyParameter extends TastySymbol with TastyReferencable + +object TastyParameter { + case class EmptyClause(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyParameter + + object EmptyClause { + given TastyFormat[EmptyClause] = TastyFormat.forProduct + } + + case class SplitClause(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends TastyParameter + + object SplitClause { + given TastyFormat[SplitClause] = TastyFormat.forProduct + } + + given TastySumType[TastyParameter] = summon[TastySumType[TastyTypeParameter]] + .or(summon[TastySumType[TastyTermParameter]]) + .or( + new TastySumType( + TastySumType.Variant[EmptyClause](DottyTastyFormat.EMPTYCLAUSE), + TastySumType.Variant[SplitClause](DottyTastyFormat.SPLITCLAUSE), + ), + ) + + given TastyFormat[TastyParameter] = TastyFormat.forSumType +} + +case class TastyTypeParameter( + name: TastyNameReference, + `type`: TastyTypeTree, + override var information: TastyReferencableInformation, + modifiers: List[TastyModifier], +) extends TastyParameter + +object TastyTypeParameter { + private given TastyFormat[(TastyNameReference, TastyTypeTree, TastyReferencableInformation, List[TastyModifier])] = + TastyFormat.forVariadic[ + (TastyNameReference, TastyTypeTree, TastyReferencableInformation), + TastyModifier, + List[TastyModifier], + ] + + given TastySumType[TastyTypeParameter] = + TastySumType.withSingleVariant(DottyTastyFormat.TYPEPARAM, TastyFormat.forProduct[TastyTypeParameter]) + + given TastyFormat[TastyTypeParameter] = TastyFormat.forSumType +} + +case class TastyTermParameter( + name: TastyNameReference, + `type`: TastyTypeTree, + override var information: TastyReferencableInformation, + modifiers: List[TastyModifier], +) extends TastyParameter + +object TastyTermParameter { + private given TastyFormat[(TastyNameReference, TastyTypeTree, TastyReferencableInformation, List[TastyModifier])] = + TastyFormat.forVariadic[ + (TastyNameReference, TastyTypeTree, TastyReferencableInformation), + TastyModifier, + List[TastyModifier], + ] + + given TastySumType[TastyTermParameter] = + TastySumType.withSingleVariant(DottyTastyFormat.PARAM, TastyFormat.forProduct[TastyTermParameter]) + + given TastyFormat[TastyTermParameter] = TastyFormat.forSumType +} + +sealed trait TastyParameterSignature extends TastyReferencable + +object TastyParameterSignature { + case class TypeParameterSectionLength( + length: Int, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyParameterSignature + + case class TermParameter( + name: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyParameterSignature + + given TastyFormat[TastyParameterSignature] = TastyFormat( + reader => { + val value = reader.readSignedInt().value + + if (value < 0) { + TypeParameterSectionLength(-value) + } else { + TermParameter(TastyNameReference(UnsignedInt(value))) + } + }, + (writer, value) => + value match { + case TypeParameterSectionLength(length, _) => writer.writeSignedInt(SignedInt(-length)) + case TermParameter(name, _) => writer.writeSignedInt(SignedInt(name.i.value)) + }, + ) +} + +sealed trait TastyPath extends TastyTerm with TastyType with TastyReferencable + +object TastyPath { + case class LocalReference( + reference: TastyAstReference[TastySymbol], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyPath + + object LocalReference { + given TastyFormat[LocalReference] = TastyFormat.forProduct + } + + case class PrefixedLocalReference( + reference: TastyAstReference[TastySymbol], + qualified: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyPath + + object PrefixedLocalReference { + given TastyFormat[PrefixedLocalReference] = TastyFormat.forProduct + } + + case class PackageReference( + fullyQualifiedName: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyPath + + object PackageReference { + given TastyFormat[PackageReference] = TastyFormat.forProduct + } + + case class NonLocalReference( + possiblySignedName: TastyNameReference, + qualified: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyPath + + object NonLocalReference { + given TastyFormat[NonLocalReference] = TastyFormat.forProduct + } + + case class NonLocalReferenceIn( + possiblySignedName: TastyNameReference, + qualified: TastyType, + owner: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyPath + + object NonLocalReferenceIn { + given TastyFormat[NonLocalReferenceIn] = TastyFormat.forProduct + } + + case class This( + classReference: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyPath + + object This { + given TastyFormat[This] = TastyFormat.forProduct + } + + case class RecursivelyRefinedThis( + recursivelyRefinedType: TastyAstReference[TastyType], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyPath + + object RecursivelyRefinedThis { + given TastyFormat[RecursivelyRefinedThis] = TastyFormat.forProduct + } + + case class Shared( + path: TastyAstReference[TastyPath], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyPath + + object Shared { + given TastyFormat[Shared] = TastyFormat.forProduct + } + + given TastySumType[TastyPath] = summon[TastySumType[TastyConstant]].or( + new TastySumType( + TastySumType.Variant[LocalReference](DottyTastyFormat.TERMREFdirect), + TastySumType.Variant[PrefixedLocalReference](DottyTastyFormat.TERMREFsymbol), + TastySumType.Variant[PackageReference](DottyTastyFormat.TERMREFpkg), + TastySumType.Variant[NonLocalReference](DottyTastyFormat.TERMREF), + TastySumType.Variant[NonLocalReferenceIn](DottyTastyFormat.TERMREFin), + TastySumType.Variant[This](DottyTastyFormat.THIS), + TastySumType.Variant[RecursivelyRefinedThis](DottyTastyFormat.RECthis), + TastySumType.Variant[Shared](DottyTastyFormat.SHAREDtype), + ), + ) + + given TastyFormat[TastyPath] = TastyFormat.forSumType +} + +case class TastyNameTable(names: Vector[TastyName]) { + override def toString: String = + s"""TastyNameTable( + |${names.zipWithIndex.map { case (name, i) => s" $i: $name" }.mkString("\n")} + |)""".stripMargin + + def apply(reference: TastyNameReference): TastyName = names(reference.i.value) +} + +object TastyNameTable { + given TastyFormat[TastyNameTable] = + TastyFormat.forIterableWithLengthPrefixed[TastyName, Vector[TastyName]].bimap(TastyNameTable(_), _.names) +} + +case class TastySection[A <: TastySectionPayload](name: TastyNameReference, payload: A) + +object TastySection { + def tastyFormat(nameTable: TastyNameTable): TastyFormat[TastySection[? <: TastySectionPayload]] = TastyFormat( + reader => { + val nameReference = summon[TastyFormat[TastyNameReference]].read(reader) + val name = nameTable(nameReference) + val payload = reader.readWithLength(reader.readUnsignedInt().value) { reader => + name match { + case TastyName.Simple("ASTs", _) => summon[TastyFormat[TastySectionPayload.Asts]].read(reader) + case TastyName.Simple("Positions", _) => summon[TastyFormat[TastySectionPayload.Positions]].read(reader) + case TastyName.Simple("Comments", _) => summon[TastyFormat[TastySectionPayload.Comments]].read(reader) + case TastyName.Simple("Attributes", _) => summon[TastyFormat[TastySectionPayload.Attributes]].read(reader) + case TastyName.Simple(name, _) => throw new Exception(s"Unrecognized section name: $name") + case other => + throw new Exception(s"Expected a simple string when following a section name reference, but got $other") + } + } + + TastySection(nameReference, payload) + }, + (writer, section) => { + summon[TastyFormat[TastyNameReference]].write(writer, section.name) + + writer.writeWithLengthPrefixed { writer => + section.payload match { + case asts: TastySectionPayload.Asts => + summon[TastyFormat[TastySectionPayload.Asts]].write(writer, asts) + + case positions: TastySectionPayload.Positions => + summon[TastyFormat[TastySectionPayload.Positions]].write(writer, positions) + + case comments: TastySectionPayload.Comments => + summon[TastyFormat[TastySectionPayload.Comments]].write(writer, comments) + + case attributes: TastySectionPayload.Attributes => + summon[TastyFormat[TastySectionPayload.Attributes]].write(writer, attributes) + } + } + }, + ) +} + +sealed trait TastySectionPayload + +object TastySectionPayload { + case class Asts(topLevelStatements: List[TastyTopLevelStatement]) extends TastySectionPayload + + object Asts { + given TastyFormat[Asts] = TastyFormat + .forIterableWithoutLengthPrefixed[TastyTopLevelStatement, List[TastyTopLevelStatement]] + .bimap(Asts(_), _.topLevelStatements) + .marked(MarkerType.AstSection) + } + + case class Positions(lineSizes: Positions.LineSizes, deltas: List[Positions.Delta | Positions.Source]) + extends TastySectionPayload + + object Positions { + case class LineSizes(sizes: List[UnsignedInt]) + + object LineSizes { + given TastyFormat[LineSizes] = TastyFormat( + reader => { + val length = reader.readUnsignedInt().value + + LineSizes(Range(0, length).map(_ => reader.readUnsignedInt()).toList) + }, + (writer, lineSizes) => { + writer.writeUnsignedInt(UnsignedInt(lineSizes.sizes.length)) + + lineSizes.sizes.foreach(writer.writeUnsignedInt) + }, + ) + } + + case class Delta(addressDelta: Int, start: Option[SignedInt], end: Option[SignedInt], point: Option[SignedInt]) + + object Delta { + given TastyFormat[Delta] = TastyFormat( + reader => { + val header = reader.readUnsignedInt().value + val addressDelta = header >> 3 + val start = Option.when(((header >>> 2) & 0x1) == 1)(reader.readSignedInt()) + val end = Option.when(((header >>> 1) & 0x1) == 1)(reader.readSignedInt()) + val point = Option.when((header & 0x1) == 1)(reader.readSignedInt()) + + Delta(addressDelta, start, end, point) + }, + (writer, delta) => { + val header = UnsignedInt( + (delta.addressDelta << 3) | + ((if (delta.start.isDefined) 1 else 0) << 2) | + ((if (delta.end.isDefined) 1 else 0) << 1) | + (if (delta.point.isDefined) 1 else 0) + ) + + writer.writeUnsignedInt(header) + + delta.start.foreach(writer.writeSignedInt) + delta.end.foreach(writer.writeSignedInt) + delta.point.foreach(writer.writeSignedInt) + }, + ) + } + + case class Source(path: TastyNameReference) + + object Source { + given TastyFormat[Source] = TastyFormat.forProduct + } + + private given TastyFormat[Delta | Source] = TastyFormat( + reader => + if (reader.peek(_.readUnsignedInt().value) == DottyTastyFormat.SOURCE) { + reader.readUnsignedInt() + + summon[TastyFormat[Source]].read(reader) + } else { + summon[TastyFormat[Delta]].read(reader) + }, + (writer, value) => + value match { + case delta: Delta => summon[TastyFormat[Delta]].write(writer, delta) + case source: Source => + writer.writeUnsignedInt(UnsignedInt(DottyTastyFormat.SOURCE)) + + summon[TastyFormat[Source]].write(writer, source) + }, + ) + + private given TastyFormat[List[Delta | Source]] = TastyFormat.forIterableWithoutLengthPrefixed + + given TastyFormat[Positions] = TastyFormat.forProduct + } + + /** + * The TASTy grammar doesn't mention this, but each comment is prefixed with the address of the symbol to which it + * belongs: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala#L21]] + */ + case class Comments(comments: List[(TastyAstReference[TastySymbol], Comments.Comment)]) extends TastySectionPayload + + object Comments { + case class Comment(content: String, coordinates: Span) + + object Comment { + given TastyFormat[Comment] = TastyFormat.forProduct + } + + given TastyFormat[Comments] = TastyFormat + .forIterableWithoutLengthPrefixed[ + (TastyAstReference[TastySymbol], Comment), + List[(TastyAstReference[TastySymbol], Comment)], + ] + .bimap(Comments(_), _.comments) + } + + case class Attributes(attributes: List[Attributes.Attribute]) extends TastySectionPayload + + object Attributes { + sealed trait Attribute extends TastyReferencable + + object Attribute { + case class Scala2StandardLibrary( + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends Attribute + + object Scala2StandardLibrary { + given TastyFormat[Scala2StandardLibrary] = TastyFormat.forProduct + } + + case class ExplicitNulls(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends Attribute + + object ExplicitNulls { + given TastyFormat[ExplicitNulls] = TastyFormat.forProduct + } + + case class CaptureChecked(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends Attribute + + object CaptureChecked { + given TastyFormat[CaptureChecked] = TastyFormat.forProduct + } + + case class WithPureFunctions( + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends Attribute + + object WithPureFunctions { + given TastyFormat[WithPureFunctions] = TastyFormat.forProduct + } + + case class Java(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends Attribute + + object Java { + given TastyFormat[Java] = TastyFormat.forProduct + } + + case class Outline(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends Attribute + + object Outline { + given TastyFormat[Outline] = TastyFormat.forProduct + } + + case class SourceFile( + path: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends Attribute + + object SourceFile { + given TastyFormat[SourceFile] = TastyFormat.forProduct + } + + given TastySumType[Attribute] = new TastySumType( + TastySumType.Variant[Scala2StandardLibrary](DottyTastyFormat.SCALA2STANDARDLIBRARYattr), + TastySumType.Variant[ExplicitNulls](DottyTastyFormat.EXPLICITNULLSattr), + TastySumType.Variant[CaptureChecked](DottyTastyFormat.CAPTURECHECKEDattr), + TastySumType.Variant[WithPureFunctions](DottyTastyFormat.WITHPUREFUNSattr), + TastySumType.Variant[Java](DottyTastyFormat.JAVAattr), + TastySumType.Variant[Outline](DottyTastyFormat.OUTLINEattr), + TastySumType.Variant[SourceFile](DottyTastyFormat.SOURCEFILEattr), + ) + + given TastyFormat[Attribute] = TastyFormat.forSumType + } + + given TastyFormat[Attributes] = + TastyFormat.forIterableWithoutLengthPrefixed[Attribute, List[Attribute]].bimap(Attributes(_), _.attributes) + } +} + +sealed trait TastySelector extends TastyReferencable + +object TastySelector { + case class Imported( + name: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastySelector + + object Imported { + given TastyFormat[Imported] = TastyFormat.forProduct + } + + case class Renamed( + to: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastySelector + + object Renamed { + given TastyFormat[Renamed] = TastyFormat.forProduct + } + + case class Bounded( + `type`: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastySelector + + object Bounded { + given TastyFormat[Bounded] = TastyFormat.forProduct + } + + given TastySumType[TastySelector] = new TastySumType( + TastySumType.Variant[Imported](DottyTastyFormat.IMPORTED), + TastySumType.Variant[Renamed](DottyTastyFormat.RENAMED), + TastySumType.Variant[Bounded](DottyTastyFormat.BOUNDED), + ) + + given TastyFormat[TastySelector] = TastyFormat.forSumType +} + +case class TastySelf( + name: TastyNameReference, + `type`: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastyReferencable + +object TastySelf { + given TastySumType[TastySelf] = + TastySumType.withSingleVariant(DottyTastyFormat.SELFDEF, TastyFormat.forProduct[TastySelf]) + + given TastyFormat[TastySelf] = TastyFormat.forSumType +} + +sealed trait TastyStatement extends TastyTopLevelStatement with TastyReferencable + +object TastyStatement { + given TastySumType[TastyStatement] = summon[TastySumType[TastyTerm]] + .or(summon[TastySumType[TastyValOrDefDefinition]]) + .or(summon[TastySumType[TastyTypeDefinition]]) + .or( + new TastySumType( + TastySumType.Variant[TastyImportStatement](DottyTastyFormat.IMPORT), + TastySumType.Variant[TastyExportStatement](DottyTastyFormat.EXPORT), + ), + ) + + given TastyFormat[TastyStatement] = TastyFormat.forSumType +} + +case class TastyImportStatement( + qualifier: TastyTerm, + override var information: TastyReferencableInformation, + selectors: List[TastySelector], +) extends TastyStatement + with TastyReferencable + +object TastyImportStatement { + private given TastyFormat[(TastyTerm, TastyReferencableInformation, List[TastySelector])] = + TastyFormat.forVariadic[(TastyTerm, TastyReferencableInformation), TastySelector, List[TastySelector]] + + given TastyFormat[TastyImportStatement] = TastyFormat.forProduct +} + +case class TastyExportStatement( + qualifier: TastyTerm, + override var information: TastyReferencableInformation, + selectors: List[TastySelector], +) extends TastyStatement + with TastyReferencable + +object TastyExportStatement { + private given TastyFormat[(TastyTerm, TastyReferencableInformation, List[TastySelector])] = + TastyFormat.forVariadic[(TastyTerm, TastyReferencableInformation), TastySelector, List[TastySelector]] + + given TastyFormat[TastyExportStatement] = TastyFormat.forProduct +} + +/** + * A symbol is ill-defined in the TASTy grammar, but according to Dotty's TASTy unpickler, it can be a definition + * (`val`, `def`, or `type`), type parameter, parameter, bind pattern, or template: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala#L592]] + */ +sealed trait TastySymbol extends TastyReferencable + +object TastySymbol { + given TastySumType[TastySymbol] = summon[TastySumType[TastyValOrDefDefinition]] + .or(summon[TastySumType[TastyTypeDefinition]]) + .or(summon[TastySumType[TastyParameter]]) + .or(summon[TastySumType[TastyTerm.Pattern.Bind]]) + .or(summon[TastySumType[TastyTemplate]]) + /** + * The compiler is incapable of proving that the union of every subclass of [[TastySymbol]] is equivalent to + * [[TastySymbol]] itself. + */ + .or(new TastySumType()) + + given TastyFormat[TastySymbol] = TastyFormat.forSumType +} + +case class TastyTemplate( + typeParameters: List[TastyTypeParameter], + parameters: List[TastyTermParameter], + parents: List[TastyTerm | TastyTypeTree], + self: Option[TastySelf], + statements: List[TastyStatement], + override var information: TastyReferencableInformation = TastyReferencableInformation(), +) extends TastySymbol + +object TastyTemplate { + private val underlyingTastyFormat = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val typeParameters = reader.readWhile( + !reader.isAtEnd && summon[TastySumType[TastyTypeParameter]].peekIsVariant(reader), + )(summon[TastyFormat[TastyTypeParameter]].read(reader)) + + val parameters = reader.readWhile( + !reader.isAtEnd && summon[TastySumType[TastyTermParameter]].peekIsVariant(reader), + )(summon[TastyFormat[TastyTermParameter]].read(reader)) + + val parents = reader.readWhile( + !reader.isAtEnd && summon[TastySumType[TastyTerm | TastyTypeTree]].peekIsVariant(reader), + )(summon[TastyFormat[TastyTerm | TastyTypeTree]].read(reader)) + + val self = Option.when(!reader.isAtEnd && summon[TastySumType[TastySelf]].peekIsVariant(reader))( + summon[TastyFormat[TastySelf]].read(reader), + ) + + // Why does the specification allow for a `SPLITCLAUSE` here? + if (reader.peek(_.readByte()) == DottyTastyFormat.SPLITCLAUSE.toByte) { + reader.readByte() + } + + val statements = reader.readUntilEnd(summon[TastyFormat[TastyStatement]].read(reader)) + + TastyTemplate(typeParameters, parameters, parents, self, statements) + }, + (writer, template) => + writer.writeWithLengthPrefixed { writer => + template.typeParameters.foreach(summon[TastyFormat[TastyTypeParameter]].write(writer, _)) + template.parameters.foreach(summon[TastyFormat[TastyTermParameter]].write(writer, _)) + template.parents.foreach(summon[TastyFormat[TastyTerm | TastyTypeTree]].write(writer, _)) + template.self.foreach(summon[TastyFormat[TastySelf]].write(writer, _)) + template.statements.foreach(summon[TastyFormat[TastyStatement]].write(writer, _)) + }, + ) + + private given TastySumType[TastyTerm | TastyTypeTree] = + summon[TastySumType[TastyTerm]].or(summon[TastySumType[TastyTypeTree]]) + + private given TastyFormat[TastyTerm | TastyTypeTree] = TastyFormat.forSumType + + given TastySumType[TastyTemplate] = + TastySumType.withSingleVariant(DottyTastyFormat.TEMPLATE, underlyingTastyFormat) + + given TastyFormat[TastyTemplate] = TastyFormat.forSumType +} + +sealed trait TastyTerm extends TastyStatement with TastyReferencable + +object TastyTerm { + case class Identifier( + name: TastyNameReference, + `type`: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Identifier { + given TastyFormat[Identifier] = TastyFormat.forProduct + } + + case class Select( + possiblySignedName: TastyNameReference, + qualified: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Select { + given TastyFormat[Select] = TastyFormat.forProduct + } + + case class SelectIn( + possiblySignedName: TastyNameReference, + qualified: TastyTerm, + owner: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object SelectIn { + given TastyFormat[SelectIn] = TastyFormat.forProduct[SelectIn].withLengthPrefixed + } + + case class QualifiedThis( + qualifier: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object QualifiedThis { + given TastyFormat[QualifiedThis] = TastyFormat.forProduct + } + + case class New( + classType: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object New { + given TastyFormat[New] = TastyFormat.forProduct + } + + case class Elided( + expressionType: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Elided { + given TastyFormat[Elided] = TastyFormat.forProduct + } + + case class Throw( + throwable: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Throw { + given TastyFormat[Throw] = TastyFormat.forProduct + } + + case class NamedArgument( + parameterName: TastyNameReference, + argument: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object NamedArgument { + given TastyFormat[NamedArgument] = TastyFormat.forProduct + } + + case class Apply( + function: TastyTerm, + override var information: TastyReferencableInformation, + arguments: List[TastyTerm], + ) extends TastyTerm + + object Apply { + private given TastyFormat[(TastyTerm, TastyReferencableInformation, List[TastyTerm])] = + TastyFormat.forVariadic[(TastyTerm, TastyReferencableInformation), TastyTerm, List[TastyTerm]] + + given TastyFormat[Apply] = TastyFormat.forProduct + } + + case class ApplySignaturePolymorphic( + function: TastyTerm, + methodType: TastyType, + override var information: TastyReferencableInformation, + arguments: List[TastyTerm], + ) extends TastyTerm + + object ApplySignaturePolymorphic { + private given TastyFormat[(TastyTerm, TastyType, TastyReferencableInformation, List[TastyTerm])] = + TastyFormat.forVariadic[(TastyTerm, TastyType, TastyReferencableInformation), TastyTerm, List[TastyTerm]] + + given TastyFormat[ApplySignaturePolymorphic] = TastyFormat.forProduct + } + + case class TypeApply( + function: TastyTerm, + override var information: TastyReferencableInformation, + typeArguments: List[TastyTypeTree], + ) extends TastyTerm + + object TypeApply { + private given TastyFormat[(TastyTerm, TastyReferencableInformation, List[TastyTypeTree])] = + TastyFormat.forVariadic[(TastyTerm, TastyReferencableInformation), TastyTypeTree, List[TastyTypeTree]] + + given TastyFormat[TypeApply] = TastyFormat.forProduct + } + + case class Super( + `this`: TastyTerm, + override var information: TastyReferencableInformation, + typeArgument: Option[TastyTypeTree], + ) extends TastyTerm + + object Super { + private given TastyFormat[(TastyTerm, TastyReferencableInformation, Option[TastyTypeTree])] = + TastyFormat.forOptional[(TastyTerm, TastyReferencableInformation), TastyTypeTree] + + given TastyFormat[Super] = TastyFormat.forProduct + } + + case class TypeAscribed( + expression: TastyTerm, + ascribedType: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object TypeAscribed { + given TastyFormat[TypeAscribed] = TastyFormat.forProduct[TypeAscribed].withLengthPrefixed + } + + case class Assignment( + leftHandSide: TastyTerm, + rightHandSide: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Assignment { + given TastyFormat[Assignment] = TastyFormat.forProduct[Assignment].withLengthPrefixed + } + + case class Block( + expression: TastyTerm, + override var information: TastyReferencableInformation, + statements: List[TastyStatement], + ) extends TastyTerm + + object Block { + private given TastyFormat[(TastyTerm, TastyReferencableInformation, List[TastyStatement])] = + TastyFormat.forVariadic[(TastyTerm, TastyReferencableInformation), TastyStatement, List[TastyStatement]] + + given TastyFormat[Block] = TastyFormat.forProduct + } + + case class Inlined( + expression: TastyTerm, + call: Option[TastyTypeTree], + definitions: List[TastyValOrDefDefinition], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Inlined { + given TastyFormat[Inlined] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val expression = summon[TastyFormat[TastyTerm]].read(reader) + val call = Option.when(!reader.isAtEnd && summon[TastySumType[TastyTypeTree]].peekIsVariant(reader))( + summon[TastyFormat[TastyTypeTree]].read(reader), + ) + + val definitions = reader.readUntilEnd(summon[TastyFormat[TastyValOrDefDefinition]].read(reader)) + + Inlined(expression, call, definitions) + }, + (writer, inlined) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyTerm]].write(writer, inlined.expression) + + inlined.call.foreach(summon[TastyFormat[TastyTypeTree]].write(writer, _)) + inlined.definitions.foreach(summon[TastyFormat[TastyValOrDefDefinition]].write(writer, _)) + }, + ) + } + + case class Lambda( + method: TastyTerm, + override var information: TastyReferencableInformation, + targetType: Option[TastyTypeTree], + ) extends TastyTerm + + object Lambda { + private given TastyFormat[(TastyTerm, TastyReferencableInformation, Option[TastyTypeTree])] = + TastyFormat.forOptional[(TastyTerm, TastyReferencableInformation), TastyTypeTree] + + given TastyFormat[Lambda] = TastyFormat.forProduct + } + + case class If( + inlined: Boolean, + condition: TastyTerm, + thenValue: TastyTerm, + elseValue: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object If { + given TastyFormat[If] = TastyFormat( + reader => { + reader.readUnsignedInt() + + val inlined = reader.peek(_.readByte()).toInt == DottyTastyFormat.INLINE.toByte + + if (inlined) { + reader.readByte() + } + + val condition = summon[TastyFormat[TastyTerm]].read(reader) + val thenValue = summon[TastyFormat[TastyTerm]].read(reader) + val elseValue = summon[TastyFormat[TastyTerm]].read(reader) + + If(inlined, condition, thenValue, elseValue) + }, + (writer, value) => + writer.writeWithLengthPrefixed { writer => + if (value.inlined) { + writer.writeByte(DottyTastyFormat.INLINE.toByte) + } + + summon[TastyFormat[TastyTerm]].write(writer, value.condition) + summon[TastyFormat[TastyTerm]].write(writer, value.thenValue) + summon[TastyFormat[TastyTerm]].write(writer, value.elseValue) + }, + ) + } + + case class Match( + inline: Boolean, + scrutinee: Option[TastyTerm], + cases: List[TastyCaseDefinition], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Match { + given TastyFormat[Match] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val implicitTag = DottyTastyFormat.IMPLICIT.toByte + val inlineTag = DottyTastyFormat.INLINE.toByte + val (inline, scrutinee) = reader.peek(_.readByte()) match { + case `implicitTag` => + reader.readByte() + + (true, None) + + case `inlineTag` => + reader.readByte() + + (true, Some(summon[TastyFormat[TastyTerm]].read(reader))) + + case _ => (false, Some(summon[TastyFormat[TastyTerm]].read(reader))) + } + + val cases = reader.readUntilEnd(summon[TastyFormat[TastyCaseDefinition]].read(reader)) + + Match(inline, scrutinee, cases) + }, + (writer, value) => + writer.writeWithLengthPrefixed { writer => + (value.inline, value.scrutinee) match { + case (true, None) => writer.writeByte(DottyTastyFormat.IMPLICIT.toByte) + case (true, Some(_)) => writer.writeByte(DottyTastyFormat.INLINE.toByte) + case _ => + } + + value.scrutinee.foreach(summon[TastyFormat[TastyTerm]].write(writer, _)) + value.cases.foreach(summon[TastyFormat[TastyCaseDefinition]].write(writer, _)) + }, + ) + } + + object Pattern { + + /** + * [[Bind]] refers to a bound pattern in an ordinary `match` expression or a type `match` (referred to here as an + * "ordinary bind" and "type bind", respectively). The TASTy grammar differentiates ordinary binds and type binds, + * but we've merged them into a single class for a couple of reasons: + * - It's possible for the pattern of a type bind to be a [[TastyTerm.Identifier]], when it should be a + * [[TastyTypeTree.Identifier]]: + * [[https://github.com/scalacenter/tasty-query/blob/275ea2d125b16ac74d47d56babfa91bea144f2ad/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala#L1645]] + * Expressing this in the definition of a type [[Bind]] bind class would complicate it. + * - Both ordinary binds and type binds can be [[TastySymbol]]s. Because they begin with the same tag + * ([[DottyTastyFormat.BIND]]), differentiating them would be extremely difficult and messy. It's much simpler + * to combine them into a single class. + * + * @param modifiers + * The TASTy grammar doesn't mention this, but [[Bind]] can have modifiers: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala#L1591]] + */ + case class Bind( + boundName: TastyNameReference, + patternType: TastyType, + pattern: TastyTerm | TastyTypeTree, + override var information: TastyReferencableInformation, + modifiers: List[TastyModifier], + ) extends TastyTerm + with TastyType + with TastySymbol + + object Bind { + private given TastySumType[TastyTerm | TastyTypeTree] = + summon[TastySumType[TastyTerm]].or(summon[TastySumType[TastyTypeTree]]) + + private given TastyFormat[TastyTerm | TastyTypeTree] = TastyFormat.forSumType + private given TastyFormat[ + (TastyNameReference, TastyType, TastyTerm | TastyTypeTree, TastyReferencableInformation, List[TastyModifier]), + ] = TastyFormat.forVariadic[ + (TastyNameReference, TastyType, TastyTerm | TastyTypeTree, TastyReferencableInformation), + TastyModifier, + List[TastyModifier], + ] + + /** + * [[Bind]] gets its own [[TastySumType]] because it's both a [[TastyTerm]] and [[TastySymbol]]. Therefore, its + * [[TastySumType]] needs to be passed to [[TastySumType.or]]. + */ + given TastySumType[Bind] = TastySumType.withSingleVariant(DottyTastyFormat.BIND, TastyFormat.forProduct[Bind]) + given TastyFormat[Bind] = TastyFormat.forSumType + } + + case class Alternative( + override var information: TastyReferencableInformation, + alternatives: List[TastyTerm], + ) extends TastyTerm + + object Alternative { + given TastyFormat[(TastyReferencableInformation, List[TastyTerm])] = + TastyFormat.forVariadic[Tuple1[TastyReferencableInformation], TastyTerm, List[TastyTerm]] + + given TastyFormat[Alternative] = TastyFormat.forProduct + } + + case class Unapply( + function: TastyTerm, + implicitArguments: List[TastyImplicitArgument], + patternType: TastyType, + patterns: List[TastyTerm], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Unapply { + given TastyFormat[Unapply] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val function = summon[TastyFormat[TastyTerm]].read(reader) + val implicitArguments = reader.readWhile( + !reader.isAtEnd && summon[TastySumType[TastyImplicitArgument]].peekIsVariant(reader), + )(summon[TastyFormat[TastyImplicitArgument]].read(reader)) + + val patternType = summon[TastyFormat[TastyType]].read(reader) + val patterns = reader.readUntilEnd(summon[TastyFormat[TastyTerm]].read(reader)) + + Unapply(function, implicitArguments, patternType, patterns) + }, + (writer, unapply) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyTerm]].write(writer, unapply.function) + + unapply.implicitArguments.foreach(summon[TastyFormat[TastyImplicitArgument]].write(writer, _)) + + summon[TastyFormat[TastyType]].write(writer, unapply.patternType) + + unapply.patterns.foreach(summon[TastyFormat[TastyTerm]].write(writer, _)) + }, + ) + } + + case class QuotePattern( + body: TastyTerm, + quotes: TastyTerm, + patternType: TastyType, + override var information: TastyReferencableInformation, + bindings: List[TastyTerm], + ) extends TastyTerm + + object QuotePattern { + private given TastyFormat[(TastyTerm, TastyTerm, TastyType, TastyReferencableInformation, List[TastyTerm])] = + TastyFormat + .forVariadic[(TastyTerm, TastyTerm, TastyType, TastyReferencableInformation), TastyTerm, List[TastyTerm]] + + given TastyFormat[QuotePattern] = TastyFormat.forProduct + } + } + + object PickledQuoteTree { + case class Explicit( + typeTree: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Explicit { + given TastyFormat[Explicit] = TastyFormat.forProduct + } + + case class Hole( + index: UnsignedInt, + `type`: TastyType, + override var information: TastyReferencableInformation, + arguments: List[TastyTerm], + ) extends TastyTerm + + object Hole { + private given TastyFormat[(UnsignedInt, TastyType, TastyReferencableInformation, List[TastyTerm])] = + TastyFormat.forVariadic[(UnsignedInt, TastyType, TastyReferencableInformation), TastyTerm, List[TastyTerm]] + + given TastyFormat[Hole] = TastyFormat.forProduct + } + } + + case class Try( + expression: TastyTerm, + cases: List[TastyCaseDefinition], + finalizer: Option[TastyTerm], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Try { + given TastyFormat[Try] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val expression = summon[TastyFormat[TastyTerm]].read(reader) + val cases = reader.readWhile( + !reader.isAtEnd && summon[TastySumType[TastyCaseDefinition]].peekIsVariant(reader), + )(summon[TastyFormat[TastyCaseDefinition]].read(reader)) + + val finalizer = Option.unless(reader.isAtEnd)(summon[TastyFormat[TastyTerm]].read(reader)) + + Try(expression, cases, finalizer) + }, + (writer, value) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyTerm]].write(writer, value.expression) + + value.cases.foreach(summon[TastyFormat[TastyCaseDefinition]].write(writer, _)) + value.finalizer.foreach(summon[TastyFormat[TastyTerm]].write(writer, _)) + }, + ) + } + + case class Return( + method: TastyAstReference[TastyValOrDefDefinition.Def], + override var information: TastyReferencableInformation, + expression: Option[TastyTerm], + ) extends TastyTerm + + object Return { + private given TastyFormat[ + (TastyAstReference[TastyValOrDefDefinition.Def], TastyReferencableInformation, Option[TastyTerm]), + ] = + TastyFormat.forOptional[(TastyAstReference[TastyValOrDefDefinition.Def], TastyReferencableInformation), TastyTerm] + + given TastyFormat[Return] = TastyFormat.forProduct + } + + case class While( + condition: TastyTerm, + body: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object While { + given TastyFormat[While] = TastyFormat.forProduct[While].withLengthPrefixed + } + + case class RepeatedArgument( + elementType: TastyTypeTree, + override var information: TastyReferencableInformation, + elements: List[TastyTerm], + ) extends TastyTerm + + object RepeatedArgument { + private given TastyFormat[(TastyTypeTree, TastyReferencableInformation, List[TastyTerm])] = + TastyFormat.forVariadic[(TastyTypeTree, TastyReferencableInformation), TastyTerm, List[TastyTerm]] + + given TastyFormat[RepeatedArgument] = TastyFormat.forProduct + } + + case class SelectOuter( + levels: UnsignedInt, + qualified: TastyTerm, + `type`: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object SelectOuter { + given TastyFormat[SelectOuter] = TastyFormat.forProduct[SelectOuter].withLengthPrefixed + } + + case class Quoted( + body: TastyTerm, + bodyType: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Quoted { + given TastyFormat[Quoted] = TastyFormat.forProduct[Quoted].withLengthPrefixed + } + + case class Spliced( + expression: TastyTerm, + expressionType: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Spliced { + given TastyFormat[Spliced] = TastyFormat.forProduct[Spliced].withLengthPrefixed + } + + case class SplicedPattern( + pattern: TastyTerm, + patternType: TastyType, + typeArguments: List[TastyTypeTree], + arguments: List[TastyTerm], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object SplicedPattern { + given TastyFormat[SplicedPattern] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val pattern = summon[TastyFormat[TastyTerm]].read(reader) + val patternType = summon[TastyFormat[TastyType]].read(reader) + val typeArguments = reader.readWhile( + !reader.isAtEnd && summon[TastySumType[TastyTypeTree]].peekIsVariant(reader), + )(summon[TastyFormat[TastyTypeTree]].read(reader)) + + val arguments = reader.readUntilEnd(summon[TastyFormat[TastyTerm]].read(reader)) + + SplicedPattern(pattern, patternType, typeArguments, arguments) + }, + (writer, splicedPattern) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyTerm]].write(writer, splicedPattern.pattern) + summon[TastyFormat[TastyType]].write(writer, splicedPattern.patternType) + + splicedPattern.typeArguments.foreach(summon[TastyFormat[TastyTypeTree]].write(writer, _)) + splicedPattern.arguments.foreach(summon[TastyFormat[TastyTerm]].write(writer, _)) + }, + ) + } + + case class Shared( + term: TastyAstReference[TastyTerm], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTerm + + object Shared { + given TastyFormat[Shared] = TastyFormat.forProduct + } + + given TastySumType[TastyTerm] = summon[TastySumType[TastyPath]] + .or(summon[TastySumType[Pattern.Bind]]) + .or( + new TastySumType( + TastySumType.Variant[Identifier](DottyTastyFormat.IDENT), + TastySumType.Variant[Select](DottyTastyFormat.SELECT), + TastySumType.Variant[SelectIn](DottyTastyFormat.SELECTin), + TastySumType.Variant[QualifiedThis](DottyTastyFormat.QUALTHIS), + TastySumType.Variant[New](DottyTastyFormat.NEW), + TastySumType.Variant[Elided](DottyTastyFormat.ELIDED), + TastySumType.Variant[Throw](DottyTastyFormat.THROW), + TastySumType.Variant[NamedArgument](DottyTastyFormat.NAMEDARG), + TastySumType.Variant[Apply](DottyTastyFormat.APPLY), + TastySumType.Variant[ApplySignaturePolymorphic](DottyTastyFormat.APPLYsigpoly), + TastySumType.Variant[TypeApply](DottyTastyFormat.TYPEAPPLY), + TastySumType.Variant[Super](DottyTastyFormat.SUPER), + TastySumType.Variant[TypeAscribed](DottyTastyFormat.TYPED), + TastySumType.Variant[Assignment](DottyTastyFormat.ASSIGN), + TastySumType.Variant[Block](DottyTastyFormat.BLOCK), + TastySumType.Variant[Inlined](DottyTastyFormat.INLINED), + TastySumType.Variant[Lambda](DottyTastyFormat.LAMBDA), + TastySumType.Variant[If](DottyTastyFormat.IF), + TastySumType.Variant[Match](DottyTastyFormat.MATCH), + TastySumType.Variant[Pattern.Alternative](DottyTastyFormat.ALTERNATIVE), + TastySumType.Variant[Pattern.Unapply](DottyTastyFormat.UNAPPLY), + TastySumType.Variant[Pattern.QuotePattern](DottyTastyFormat.QUOTE), + TastySumType.Variant[PickledQuoteTree.Explicit](DottyTastyFormat.EXPLICITtpt), + TastySumType.Variant[PickledQuoteTree.Hole](DottyTastyFormat.HOLE), + TastySumType.Variant[Try](DottyTastyFormat.TRY), + TastySumType.Variant[Return](DottyTastyFormat.RETURN), + TastySumType.Variant[While](DottyTastyFormat.WHILE), + TastySumType.Variant[RepeatedArgument](DottyTastyFormat.REPEATED), + TastySumType.Variant[SelectOuter](DottyTastyFormat.SELECTouter), + TastySumType.Variant[Quoted](DottyTastyFormat.QUOTE), + TastySumType.Variant[Spliced](DottyTastyFormat.SPLICE), + TastySumType.Variant[SplicedPattern](DottyTastyFormat.SPLICEPATTERN), + TastySumType.Variant[Shared](DottyTastyFormat.SHAREDterm), + ), + ) + + given TastyFormat[TastyTerm] = TastyFormat.forSumType +} + +sealed trait TastyTopLevelStatement extends TastyReferencable + +object TastyTopLevelStatement { + given TastySumType[TastyTopLevelStatement] = + new TastySumType(TastySumType.Variant[TastyPackageStatement](DottyTastyFormat.PACKAGE)) + .or(summon[TastySumType[TastyStatement]]) + + given TastyFormat[TastyTopLevelStatement] = TastyFormat.forSumType +} + +case class TastyPackageStatement( + path: TastyPath, + override var information: TastyReferencableInformation, + topLevelStatements: List[TastyTopLevelStatement], +) extends TastyTopLevelStatement + +object TastyPackageStatement { + private given TastyFormat[(TastyPath, TastyReferencableInformation, List[TastyTopLevelStatement])] = TastyFormat + .forVariadic[(TastyPath, TastyReferencableInformation), TastyTopLevelStatement, List[TastyTopLevelStatement]] + + given TastyFormat[TastyPackageStatement] = TastyFormat.forProduct +} + +sealed trait TastyType extends TastyTypeTree with TastyReferencable + +object TastyType { + case class LocalReference( + reference: TastyAstReference[TastySymbol], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object LocalReference { + given TastyFormat[LocalReference] = TastyFormat.forProduct + } + + case class PrefixedLocalReference( + reference: TastyAstReference[TastySymbol], + qualified: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object PrefixedLocalReference { + given TastyFormat[PrefixedLocalReference] = TastyFormat.forProduct + } + + case class PackageReference( + fullyQualifiedName: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object PackageReference { + given TastyFormat[PackageReference] = TastyFormat.forProduct + } + + case class NonLocalReference( + name: TastyNameReference, + qualified: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object NonLocalReference { + given TastyFormat[NonLocalReference] = TastyFormat.forProduct + } + + case class NonLocalReferenceIn( + name: TastyNameReference, + qualified: TastyType, + namespace: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object NonLocalReferenceIn { + given TastyFormat[NonLocalReferenceIn] = TastyFormat.forProduct[NonLocalReferenceIn].withLengthPrefixed + } + + case class RecursivelyRefined( + underlying: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object RecursivelyRefined { + given TastyFormat[RecursivelyRefined] = TastyFormat.forProduct + } + + case class Super( + thisType: TastyType, + underlying: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object Super { + given TastyFormat[Super] = TastyFormat.forProduct[Super].withLengthPrefixed + } + + case class Refined( + refinementName: TastyNameReference, + underlying: TastyType, + info: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object Refined { + given TastyFormat[Refined] = TastyFormat.forProduct[Refined].withLengthPrefixed + } + + case class Applied( + typeConstructor: TastyType, + override var information: TastyReferencableInformation, + arguments: List[TastyType], + ) extends TastyType + + object Applied { + private given TastyFormat[(TastyType, TastyReferencableInformation, List[TastyType])] = + TastyFormat.forVariadic[(TastyType, TastyReferencableInformation), TastyType, List[TastyType]] + + given TastyFormat[Applied] = TastyFormat.forProduct + } + + case class TypeBounds( + low: TastyType, + high: Option[TastyType], + variances: List[Variance], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object TypeBounds { + given TastyFormat[TypeBounds] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val low = summon[TastyFormat[TastyType]].read(reader) + val high = Option.when(!reader.isAtEnd && summon[TastySumType[TastyType]].peekIsVariant(reader))( + summon[TastyFormat[TastyType]].read(reader), + ) + + val variances = reader.readUntilEnd(summon[TastyFormat[Variance]].read(reader)) + + TypeBounds(low, high, variances) + }, + (writer, typeBounds) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyType]].write(writer, typeBounds.low) + + typeBounds.high.foreach(summon[TastyFormat[TastyType]].write(writer, _)) + typeBounds.variances.foreach(summon[TastyFormat[Variance]].write(writer, _)) + }, + ) + } + + sealed trait Variance extends TastyReferencable + + object Variance { + case class Invariant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends Variance + + object Invariant { + given TastyFormat[Invariant] = TastyFormat.forProduct + } + + case class Covariant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends Variance + + object Covariant { + given TastyFormat[Covariant] = TastyFormat.forProduct + } + + case class Contravariant(override var information: TastyReferencableInformation = TastyReferencableInformation()) + extends Variance + + object Contravariant { + given TastyFormat[Contravariant] = TastyFormat.forProduct + } + + given TastySumType[Variance] = new TastySumType( + TastySumType.Variant[Invariant](DottyTastyFormat.STABLE), + TastySumType.Variant[Covariant](DottyTastyFormat.COVARIANT), + TastySumType.Variant[Contravariant](DottyTastyFormat.CONTRAVARIANT), + ) + + given TastyFormat[Variance] = TastyFormat.forSumType + } + + case class Annotated( + underlying: TastyType, + annotation: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object Annotated { + given TastyFormat[Annotated] = TastyFormat.forProduct[Annotated].withLengthPrefixed + } + + case class And( + left: TastyType, + right: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object And { + given TastyFormat[And] = TastyFormat.forProduct[And].withLengthPrefixed + } + + case class Or( + left: TastyType, + right: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object Or { + given TastyFormat[Or] = TastyFormat.forProduct[Or].withLengthPrefixed + } + + case class Match( + upperBound: TastyType, + scrutinee: TastyType, + override var information: TastyReferencableInformation, + cases: List[TastyType], + ) extends TastyType + + object Match { + private given TastyFormat[(TastyType, TastyType, TastyReferencableInformation, List[TastyType])] = + TastyFormat.forVariadic[(TastyType, TastyType, TastyReferencableInformation), TastyType, List[TastyType]] + + given TastyFormat[Match] = TastyFormat.forProduct + } + + case class MatchCase( + pattern: TastyType, + rightHandSide: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object MatchCase { + given TastyFormat[MatchCase] = TastyFormat.forProduct[MatchCase].withLengthPrefixed + } + + case class Flexible( + underlying: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object Flexible { + given TastyFormat[Flexible] = TastyFormat.forProduct[Flexible].withLengthPrefixed + } + + case class ByName( + underlying: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object ByName { + given TastyFormat[ByName] = TastyFormat.forProduct + } + + /** + * @param typeOrBounds + * According to the TASTy grammar, this should be an AST reference to a type, but it's read in tasty-query as an + * actual type: + * [[https://github.com/scalacenter/tasty-query/blob/275ea2d125b16ac74d47d56babfa91bea144f2ad/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala#L438]] + */ + case class TypeName( + typeOrBounds: TastyType, + name: TastyNameReference, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyReferencable + + object TypeName { + given TastyFormat[TypeName] = TastyFormat.forProduct + } + + case class ParameterReference( + binder: TastyAstReference[TastyType], + parameterNumber: UnsignedInt, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object ParameterReference { + given TastyFormat[ParameterReference] = TastyFormat.forProduct[ParameterReference].withLengthPrefixed + } + + case class PolymorphicMethod( + result: TastyType, + override var information: TastyReferencableInformation, + parameters: List[TypeName], + ) extends TastyType + + object PolymorphicMethod { + private given TastyFormat[(TastyType, TastyReferencableInformation, List[TypeName])] = + TastyFormat.forVariadic[(TastyType, TastyReferencableInformation), TypeName, List[TypeName]] + + given TastyFormat[PolymorphicMethod] = TastyFormat.forProduct + } + + case class Method( + result: TastyType, + parameters: List[TypeName], + modifiers: List[TastyModifier], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object Method { + given TastyFormat[Method] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val result = summon[TastyFormat[TastyType]].read(reader) + val parameters = reader.readWhile( + !reader.isAtEnd && !summon[TastySumType[TastyModifier]].peekIsVariant(reader), + )(summon[TastyFormat[TypeName]].read(reader)) + + val modifiers = reader.readUntilEnd(summon[TastyFormat[TastyModifier]].read(reader)) + + Method(result, parameters, modifiers) + }, + (writer, method) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyType]].write(writer, method.result) + + method.parameters.foreach(summon[TastyFormat[TypeName]].write(writer, _)) + method.modifiers.foreach(summon[TastyFormat[TastyModifier]].write(writer, _)) + }, + ) + } + + case class TypeLambda( + result: TastyType, + override var information: TastyReferencableInformation, + parameters: List[TypeName], + ) extends TastyType + + object TypeLambda { + private given TastyFormat[(TastyType, TastyReferencableInformation, List[TypeName])] = + TastyFormat.forVariadic[(TastyType, TastyReferencableInformation), TypeName, List[TypeName]] + + given TastyFormat[TypeLambda] = TastyFormat.forProduct + } + + case class Shared( + `type`: TastyAstReference[TastyType], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object Shared { + given TastyFormat[Shared] = TastyFormat.forProduct + } + + /** + * This isn't formally documented in the TASTy grammar as a type (nor is it clear from reading Dotty's TASTy parser + * that it's a valid type), but you can find it here: + * [[https://github.com/scalacenter/tasty-query/blob/275ea2d125b16ac74d47d56babfa91bea144f2ad/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala#L980]] + */ + case class QualifiedThis( + qualifier: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyType + + object QualifiedThis { + given TastyFormat[QualifiedThis] = TastyFormat.forProduct + } + + given TastySumType[TastyType] = summon[TastySumType[TastyPath]] + .or(summon[TastySumType[TastyTerm.Pattern.Bind]]) + .or( + new TastySumType( + TastySumType.Variant[LocalReference](DottyTastyFormat.TYPEREFdirect), + TastySumType.Variant[PrefixedLocalReference](DottyTastyFormat.TYPEREFsymbol), + TastySumType.Variant[PackageReference](DottyTastyFormat.TYPEREFpkg), + TastySumType.Variant[NonLocalReference](DottyTastyFormat.TYPEREF), + TastySumType.Variant[NonLocalReferenceIn](DottyTastyFormat.TYPEREFin), + TastySumType.Variant[RecursivelyRefined](DottyTastyFormat.RECtype), + TastySumType.Variant[Super](DottyTastyFormat.SUPERtype), + TastySumType.Variant[Refined](DottyTastyFormat.REFINEDtype), + TastySumType.Variant[Applied](DottyTastyFormat.APPLIEDtype), + TastySumType.Variant[TypeBounds](DottyTastyFormat.TYPEBOUNDS), + TastySumType.Variant[Annotated](DottyTastyFormat.ANNOTATEDtype), + TastySumType.Variant[And](DottyTastyFormat.ANDtype), + TastySumType.Variant[Or](DottyTastyFormat.ORtype), + TastySumType.Variant[Match](DottyTastyFormat.MATCHtype), + TastySumType.Variant[MatchCase](DottyTastyFormat.MATCHCASEtype), + TastySumType.Variant[Flexible](DottyTastyFormat.FLEXIBLEtype), + TastySumType.Variant[ByName](DottyTastyFormat.BYNAMEtype), + TastySumType.Variant[ParameterReference](DottyTastyFormat.PARAMtype), + TastySumType.Variant[PolymorphicMethod](DottyTastyFormat.POLYtype), + TastySumType.Variant[Method](DottyTastyFormat.METHODtype), + TastySumType.Variant[TypeLambda](DottyTastyFormat.TYPELAMBDAtype), + TastySumType.Variant[Shared](DottyTastyFormat.SHAREDtype), + TastySumType.Variant[QualifiedThis](DottyTastyFormat.QUALTHIS), + ), + ) + + given TastyFormat[TastyType] = TastyFormat.forSumType +} + +case class TastyTypeDefinition( + name: TastyNameReference, + value: TastyTypeTree | TastyTemplate, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + modifiers: List[TastyModifier], +) extends TastyStatement + with TastySymbol + +object TastyTypeDefinition { + private given TastySumType[TastyTypeTree | TastyTemplate] = + summon[TastySumType[TastyTypeTree]].or(summon[TastySumType[TastyTemplate]]) + + private given TastyFormat[TastyTypeTree | TastyTemplate] = TastyFormat.forSumType + private given TastyFormat[ + (TastyNameReference, TastyTypeTree | TastyTemplate, TastyReferencableInformation, List[TastyModifier]), + ] = TastyFormat.forVariadic[ + (TastyNameReference, TastyTypeTree | TastyTemplate, TastyReferencableInformation), + TastyModifier, + List[TastyModifier], + ] + + given TastySumType[TastyTypeDefinition] = + TastySumType.withSingleVariant(DottyTastyFormat.TYPEDEF, TastyFormat.forProduct[TastyTypeDefinition]) + + given TastyFormat[TastyTypeDefinition] = TastyFormat.forSumType +} + +sealed trait TastyTypeTree extends TastyReferencable + +object TastyTypeTree { + case class Identifier( + name: TastyNameReference, + `type`: TastyType, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object Identifier { + given TastyFormat[Identifier] = TastyFormat.forProduct + } + + /** + * This isn't formally documented in the TASTy grammar as a type tree, but you can find it here: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala#L1319]] + * + * The difference between this and [[SelectFromType]] is that this selects a type from a term, whereas + * [[SelectFromType]] selects a field from a type. + */ + case class SelectFromTerm( + possiblySignedName: TastyNameReference, + qualified: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object SelectFromTerm { + given TastyFormat[SelectFromTerm] = TastyFormat.forProduct + } + + case class SelectFromType( + possiblySignedName: TastyNameReference, + qualified: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object SelectFromType { + given TastyFormat[SelectFromType] = TastyFormat.forProduct + } + + case class Singleton( + reference: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object Singleton { + given TastyFormat[Singleton] = TastyFormat.forProduct + } + + case class Refined( + underlying: TastyTypeTree, + override var information: TastyReferencableInformation, + refinements: List[TastyStatement], + ) extends TastyTypeTree + + object Refined { + private given TastyFormat[(TastyTypeTree, TastyReferencableInformation, List[TastyStatement])] = + TastyFormat.forVariadic[(TastyTypeTree, TastyReferencableInformation), TastyStatement, List[TastyStatement]] + + given TastyFormat[Refined] = TastyFormat.forProduct + } + + case class Applied( + typeConstructor: TastyTypeTree, + override var information: TastyReferencableInformation, + typeArguments: List[TastyTypeTree], + ) extends TastyTypeTree + + object Applied { + private given TastyFormat[(TastyTypeTree, TastyReferencableInformation, List[TastyTypeTree])] = + TastyFormat.forVariadic[(TastyTypeTree, TastyReferencableInformation), TastyTypeTree, List[TastyTypeTree]] + + given TastyFormat[Applied] = TastyFormat.forProduct + } + + case class Lambda( + typeParameters: List[TastyTypeParameter], + body: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object Lambda { + given TastyFormat[Lambda] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val typeParameters = reader.readWhile( + !reader.isAtEnd && summon[TastySumType[TastyTypeParameter]].peekIsVariant(reader), + )(summon[TastyFormat[TastyTypeParameter]].read(reader)) + + val body = summon[TastyFormat[TastyTypeTree]].read(reader) + + Lambda(typeParameters, body) + }, + (writer, lambda) => + writer.writeWithLengthPrefixed { writer => + lambda.typeParameters.foreach(summon[TastyFormat[TastyTypeParameter]].write(writer, _)) + + summon[TastyFormat[TastyTypeTree]].write(writer, lambda.body) + }, + ) + } + + /** + * @param alias + * The TASTy grammar doesn't mention this, but [[TypeBounds]] can have a third field: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala#L1654]] + */ + case class TypeBounds( + low: TastyTypeTree, + high: Option[TastyTypeTree], + alias: Option[TastyTypeTree], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object TypeBounds { + given TastyFormat[TypeBounds] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val low = summon[TastyFormat[TastyTypeTree]].read(reader) + val high = Option.unless(reader.isAtEnd)(summon[TastyFormat[TastyTypeTree]].read(reader)) + val alias = Option.unless(reader.isAtEnd)(summon[TastyFormat[TastyTypeTree]].read(reader)) + + TypeBounds(low, high, alias) + }, + (writer, typeBounds) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyTypeTree]].write(writer, typeBounds.low) + + typeBounds.high.foreach(summon[TastyFormat[TastyTypeTree]].write(writer, _)) + typeBounds.alias.foreach(summon[TastyFormat[TastyTypeTree]].write(writer, _)) + }, + ) + } + + case class Annotated( + underlying: TastyTypeTree, + annotation: TastyTerm, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object Annotated { + given TastyFormat[Annotated] = TastyFormat.forProduct[Annotated].withLengthPrefixed + } + + case class Match( + bound: Option[TastyTypeTree], + scrutinee: TastyTypeTree, + cases: List[TastyCaseDefinition], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object Match { + given TastyFormat[Match] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val boundOrScrutinee = summon[TastyFormat[TastyTypeTree]].read(reader) + val (bound, scrutinee) = if (!reader.isAtEnd && summon[TastySumType[TastyTypeTree]].peekIsVariant(reader)) { + (Some(boundOrScrutinee), summon[TastyFormat[TastyTypeTree]].read(reader)) + } else { + (None, boundOrScrutinee) + } + + val cases = reader.readUntilEnd(summon[TastyFormat[TastyCaseDefinition]].read(reader)) + + Match(bound, scrutinee, cases) + }, + (writer, value) => + writer.writeWithLengthPrefixed { writer => + value.bound.foreach(summon[TastyFormat[TastyTypeTree]].write(writer, _)) + + summon[TastyFormat[TastyTypeTree]].write(writer, value.scrutinee) + + value.cases.foreach(summon[TastyFormat[TastyCaseDefinition]].write(writer, _)) + }, + ) + } + + case class ByName( + underlying: TastyTypeTree, + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object ByName { + given TastyFormat[ByName] = TastyFormat.forProduct + } + + /** + * This isn't formally documented in the TASTy grammar as a type tree, but you can find it here: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala#L1691]] + * + * It's used to refer to a type tree that's defined elsewhere without duplicating it. + */ + case class Shared( + term: TastyAstReference[TastyTypeTree], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyTypeTree + + object Shared { + given TastyFormat[Shared] = TastyFormat.forProduct + } + + given TastySumType[TastyTypeTree] = new TastySumType( + TastySumType.Variant[Identifier](DottyTastyFormat.IDENTtpt), + TastySumType.Variant[SelectFromTerm](DottyTastyFormat.SELECT), + TastySumType.Variant[SelectFromType](DottyTastyFormat.SELECTtpt), + TastySumType.Variant[Singleton](DottyTastyFormat.SINGLETONtpt), + TastySumType.Variant[Refined](DottyTastyFormat.REFINEDtpt), + TastySumType.Variant[Applied](DottyTastyFormat.APPLIEDtpt), + TastySumType.Variant[Lambda](DottyTastyFormat.LAMBDAtpt), + TastySumType.Variant[TypeBounds](DottyTastyFormat.TYPEBOUNDStpt), + TastySumType.Variant[Annotated](DottyTastyFormat.ANNOTATEDtpt), + TastySumType.Variant[Match](DottyTastyFormat.MATCHtpt), + TastySumType.Variant[ByName](DottyTastyFormat.BYNAMEtpt), + TastySumType.Variant[Shared](DottyTastyFormat.SHAREDterm), + ).or(summon[TastySumType[TastyType]]) + + given TastyFormat[TastyTypeTree] = TastyFormat.forSumType +} + +sealed trait TastyValOrDefDefinition extends TastyStatement with TastySymbol + +object TastyValOrDefDefinition { + case class Val( + name: TastyNameReference, + `type`: TastyTypeTree, + value: Option[TastyTerm], + modifiers: List[TastyModifier], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyValOrDefDefinition + + object Val { + given TastyFormat[Val] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val name = summon[TastyFormat[TastyNameReference]].read(reader) + val `type` = summon[TastyFormat[TastyTypeTree]].read(reader) + val value = Option.when(!reader.isAtEnd && summon[TastySumType[TastyTerm]].peekIsVariant(reader))( + summon[TastyFormat[TastyTerm]].read(reader), + ) + + val modifiers = reader.readUntilEnd(summon[TastyFormat[TastyModifier]].read(reader)) + + Val(name, `type`, value, modifiers) + }, + (writer, definition) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyNameReference]].write(writer, definition.name) + summon[TastyFormat[TastyTypeTree]].write(writer, definition.`type`) + + definition.value.foreach(summon[TastyFormat[TastyTerm]].write(writer, _)) + definition.modifiers.foreach(summon[TastyFormat[TastyModifier]].write(writer, _)) + }, + ) + } + + case class Def( + name: TastyNameReference, + parameters: List[TastyParameter], + `type`: TastyTypeTree, + value: Option[TastyTerm], + modifiers: List[TastyModifier], + override var information: TastyReferencableInformation = TastyReferencableInformation(), + ) extends TastyValOrDefDefinition + + object Def { + private val underlyingTastyFormat: TastyFormat[Def] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val name = summon[TastyFormat[TastyNameReference]].read(reader) + val parameters = reader.readWhile(summon[TastySumType[TastyParameter]].peekIsVariant(reader))( + summon[TastyFormat[TastyParameter]].read(reader), + ) + + val `type` = summon[TastyFormat[TastyTypeTree]].read(reader) + val value = Option.when(!reader.isAtEnd && summon[TastySumType[TastyTerm]].peekIsVariant(reader))( + summon[TastyFormat[TastyTerm]].read(reader), + ) + + val modifiers = reader.readUntilEnd(summon[TastyFormat[TastyModifier]].read(reader)) + + Def(name, parameters, `type`, value, modifiers) + }, + (writer, definition) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[TastyNameReference]].write(writer, definition.name) + + definition.parameters.foreach(summon[TastyFormat[TastyParameter]].write(writer, _)) + + summon[TastyFormat[TastyTypeTree]].write(writer, definition.`type`) + + definition.value.foreach(summon[TastyFormat[TastyTerm]].write(writer, _)) + definition.modifiers.foreach(summon[TastyFormat[TastyModifier]].write(writer, _)) + }, + ) + + /** + * [[Def]] gets its own [[TastySumType]] because it's used in [[TastyTerm.Return]] and its tag needs to be included + * when we're reading or writing a [[TastyTerm.Return]], not just when we're reading or writing a + * [[TastyValOrDefDefinition]]. + */ + given TastySumType[Def] = TastySumType.withSingleVariant(DottyTastyFormat.DEFDEF, underlyingTastyFormat) + given TastyFormat[Def] = TastyFormat.forSumType + } + + given TastySumType[TastyValOrDefDefinition] = + summon[TastySumType[Def]].or(new TastySumType(TastySumType.Variant[Val](DottyTastyFormat.VALDEF))) + + given TastyFormat[TastyValOrDefDefinition] = TastyFormat.forSumType +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/Tasty.spec.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/Tasty.spec.scala new file mode 100644 index 000000000..d2428497d --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/Tasty.spec.scala @@ -0,0 +1,90 @@ +package io.bazel.rules_scala.dottyijar.tasty + +import io.bazel.rules_scala.dottyijar.tasty.format.DebuggingTastyFormat +import java.io.File +import java.nio.file.Files +import org.specs2.execute.Result +import scala.util.control.NonFatal + +class TastySpec extends TastySpecification { + + /** + * [[io.bazel.rules_scala.dottyijar.tasty.format.DebuggingTastyFormat]] uses global state to track the structure of + * the TASTy file being read or written, which means that only one file can be read at a time. If we weren't using + * `DebuggingTastyFormat` (which should be the case everywhere else besides this test), multiple TASTy files could be + * read from and written to in parallel. + */ + sequential + + "Tasty" should { + "Accurately model every TASTy file for the Scala 3 compiler" in { + withTestCases { + Result.foreach(_) { testCase => + println(s"Testing ${testCase.path}") + + val content = testCase.inputStream.readAllBytes() + + def handleTastyFormatFailure(writtenContent: Option[Array[Byte]]): Unit = { + val i = testCase.path.lastIndexOf("/") + val j = testCase.path.lastIndexOf(".") + val baseName = testCase.path.slice(if (i == -1) 0 else i + 1, if (j == -1) testCase.path.length else j) + + /** + * [[File.createTempFile]] requires the temporary file prefix to be at least three characters long. + */ + val prefix = if (baseName.length < 3) baseName + "_" * (3 - baseName.length) else baseName + val extractedTastyFilePath = File.createTempFile(prefix, ".tasty").toPath + + Files.write(extractedTastyFilePath, content) + + println(s"Extracted the incorrectly read TASTy file here: $extractedTastyFilePath") + + writtenContent.foreach { writtenContent => + val path = File.createTempFile(baseName, ".tasty").toPath + + Files.write(path, writtenContent) + + println(s"Extracted the incorrectly written TASTy file here: $path") + } + + println("TastyFormat logs:") + println(DebuggingTastyFormat.logs) + } + + def tryTastyFormatOperation[A](result: => A, writtenContent: Option[Array[Byte]] = None): A = try { + result + } catch { + case NonFatal(exception) => + handleTastyFormatFailure(writtenContent) + + throw exception + } + + try { + val readTasty = tryTastyFormatOperation(Tasty.read(content)) + val writtenContent = tryTastyFormatOperation(readTasty.write) + val writtenTasty = tryTastyFormatOperation(Tasty.read(writtenContent), Some(writtenContent)) + + /** + * See the documentation for [[io.bazel.rules_scala.dottyijar.tasty.format.TastyReference.equals]] to + * understand why we don't just compare [[content]] and [[writtenContent]] directly. + */ + if (readTasty != writtenTasty) { + handleTastyFormatFailure(Some(writtenContent)) + + /** + * `writtenTasty must ===(readTasty)` uses way too much memory, so we only call it if [[readTasty]] and + * [[writtenTasty]] are unequal + */ + writtenTasty must ===(readTasty) + } + + success + } finally { + DebuggingTastyFormat.clearLogs() + } + } + } + } + } +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastyDereferencer.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastyDereferencer.scala new file mode 100644 index 000000000..4f13e0dd2 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastyDereferencer.scala @@ -0,0 +1,25 @@ +package io.bazel.rules_scala.dottyijar.tasty + +import io.bazel.rules_scala.dottyijar.tasty.format.{MarkerType, TastyReferencable, TastyReference} +import scala.collection.mutable + +class TastyDereferencer(tasty: Tasty) { + private lazy val referencablesById = tasty.astsSection + .map { section => + mutable.LongMap( + TastyElement + .collect(section.payload) { case element: TastyReferencable => + element.information.id.map(_.toLong -> element) + } + .flatten + .toSeq*, + ) + } + .getOrElse(mutable.LongMap.empty) + + def dereference[A](reference: TastyReference[? <: MarkerType, A]): A = + referencablesById(reference.referencableId).asInstanceOf[A] + + def isValidReference(reference: TastyReference[? <: MarkerType, ?]): Boolean = + referencablesById.contains(reference.referencableId) +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastyElement.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastyElement.scala new file mode 100644 index 000000000..6a37a9dbe --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastyElement.scala @@ -0,0 +1,451 @@ +package io.bazel.rules_scala.dottyijar.tasty + +import dotty.tools.dotc.util.Spans.Span +import io.bazel.rules_scala.dottyijar.tasty.format.{MarkerType, TastyReferencableInformation, TastyReference} +import io.bazel.rules_scala.dottyijar.tasty.numeric.{SignedInt, SignedLong, UnsignedInt} +import java.util.UUID +import scala.annotation.nowarn +import scala.collection.Factory +import scala.compiletime.summonAll +import scala.deriving.Mirror + +trait TastyElement[A] { + + /** + * Whether the given value is a node in the TASTy AST, and not an iterable, primitive, or + * [[TastyReferenceInformation]]. I tried making this `private[tasty]`, but for some reason, Scala inlines it and a + * [[NoSuchMethodError]] is thrown at runtime in [[TastyElement.forSum]]. + */ + def isNode: Boolean + + def collect[B](value: A)(collector: PartialFunction[Any, B]): Iterable[B] + def map(value: A)(mapper: [B] => B => TastyElement[B] ?=> B): A +} + +object TastyElement + extends TastyElementIterableGivens + with TastyElementAlgebraicGivens + with TastyElementIdentityGivens + with TastyElementTastyGivens { + protected def collectChild[A: TastyElement, B](element: A)(collector: PartialFunction[Any, B]): Iterable[B] = { + val collected = if (summon[TastyElement[A]].isNode && collector.isDefinedAt(element)) { + Iterable(collector(element)) + } else { + Iterable.empty + } + + collected ++ collect(element)(collector) + } + + protected def mapChild[A: TastyElement](element: A)(mapper: [B] => B => TastyElement[B] ?=> B): A = + if (summon[TastyElement[A]].isNode) mapper(element) else map(element)(mapper) + + def collect[A: TastyElement, B](value: A)(collector: PartialFunction[Any, B]): Iterable[B] = + summon[TastyElement[A]].collect(value)(collector) + + def map[A: TastyElement](value: A)(mapper: [B] => B => TastyElement[B] ?=> B): A = + summon[TastyElement[A]].map(value)(mapper) + + def identity[A](isNode: Boolean): TastyElement[A] = { + val _isNode = isNode + + new TastyElement[A] { + override val isNode: Boolean = _isNode + override def collect[B](value: A)(collector: PartialFunction[Any, B]): Iterable[B] = Iterable.empty + override def map(value: A)(mapper: [B] => B => TastyElement[B] ?=> B): A = value + } + } +} + +transparent trait TastyElementIterableGivens { self: TastyElement.type => + inline given [A: TastyElement]: TastyElement[Option[A]] with { + override val isNode: Boolean = false + override def collect[B](value: Option[A])(collector: PartialFunction[Any, B]): Iterable[B] = + value.map(collectChild(_)(collector)).getOrElse(Iterable.empty) + + override def map(value: Option[A])(mapper: [B] => B => TastyElement[B] ?=> B): Option[A] = + value.map(mapChild(_)(mapper)) + } + + inline given [Element: TastyElement, IterableLike[A] <: Iterable[A]](using + Factory[Element, IterableLike[Element]], + ): TastyElement[IterableLike[Element]] with { + override val isNode: Boolean = false + override def collect[A](value: IterableLike[Element])(collector: PartialFunction[Any, A]): Iterable[A] = + value.collect(collectChild(_)(collector)).flatten + + override def map( + value: IterableLike[Element], + )(mapper: [B] => B => TastyElement[B] ?=> B): IterableLike[Element] = + summon[Factory[Element, IterableLike[Element]]].fromSpecific(value.map(mapChild(_)(mapper))) + } +} + +transparent trait TastyElementAlgebraicGivens { self: TastyElement.type => + given TastyElement[EmptyTuple] = identity(isNode = false) + + inline given tastyReferencableInformationTupleTastyElement[ + A <: Tuple: TastyElement, + ]: TastyElement[TastyReferencableInformation *: A] with { + override val isNode: Boolean = false + override def collect[B](value: TastyReferencableInformation *: A)(collector: PartialFunction[Any, B]): Iterable[B] = + TastyElement.collect(value.tail)(collector) + + override def map( + value: TastyReferencableInformation *: A, + )(mapper: [B] => B => TastyElement[B] ?=> B): TastyReferencableInformation *: A = + value.head *: TastyElement.map(value.tail)(mapper) + } + + inline given tupleTastyElement[ + Head: TastyElement, + Tail <: Tuple: TastyElement, + ]: TastyElement[Head *: Tail] with { + override val isNode: Boolean = false + override def collect[A](value: Head *: Tail)(collector: PartialFunction[Any, A]): Iterable[A] = + collectChild(value.head)(collector) ++ TastyElement.collect(value.tail)(collector) + + override def map(value: Head *: Tail)(mapper: [A] => A => TastyElement[A] ?=> A): Head *: Tail = + mapChild(value.head)(mapper) *: TastyElement.map(value.tail)(mapper) + } + + inline def forProduct[A <: Product](using + mirror: Mirror.ProductOf[A], + )(using TastyElement[mirror.MirroredElemTypes]): TastyElement[A] = new TastyElement[A] { + override val isNode: Boolean = true + override def collect[B](value: A)(collector: PartialFunction[Any, B]): Iterable[B] = + TastyElement.collect(Tuple.fromProductTyped(value))(collector) + + override def map(value: A)(mapper: [B] => B => TastyElement[B] ?=> B): A = + mirror.fromTuple(TastyElement.map(Tuple.fromProductTyped(value))(mapper)) + }: @nowarn("msg=New anonymous class definition will be duplicated at each inline site") + + inline def forSum[Value, Elements <: Tuple](using + mirror: Mirror.SumOf[Value] { type MirroredElemTypes = Elements }, + ): TastyElement[Value] = new TastyElement[Value] { + private lazy val tastyElements = summonAll[Tuple.Map[Elements, TastyElement]] + private def getTastyElement(value: Value): TastyElement[Value] = + tastyElements.productElement(mirror.ordinal(value)).asInstanceOf[TastyElement[Value]] + + override val isNode: Boolean = true + override def collect[A](value: Value)(collector: PartialFunction[Any, A]): Iterable[A] = + getTastyElement(value).collect(value)(collector) + + override def map(value: Value)(mapper: [A] => A => TastyElement[A] ?=> A): Value = + getTastyElement(value).map(value)(mapper) + }: @nowarn("msg=New anonymous class definition will be duplicated at each inline site") +} + +transparent trait TastyElementIdentityGivens { self: TastyElement.type => + given TastyElement[Boolean] = identity(isNode = false) + given TastyElement[Int] = identity(isNode = false) + given TastyElement[SignedInt] = identity(isNode = false) + given TastyElement[SignedLong] = identity(isNode = false) + given TastyElement[Span] = identity(isNode = false) + given TastyElement[String] = identity(isNode = false) + given TastyElement[UnsignedInt] = identity(isNode = false) + given TastyElement[UUID] = identity(isNode = false) +} + +transparent trait TastyElementTastyGivens { self: TastyElement.type => + given [RelativeTo <: MarkerType, Value]: TastyElement[TastyReference[RelativeTo, Value]] = identity(isNode = true) + + given TastyElement[Tasty] = forProduct + + given TastyElement[TastyCaseDefinition] = forProduct + + given TastyElement[TastyUnitConstant] = forProduct + given TastyElement[TastyFalseConstant] = forProduct + given TastyElement[TastyTrueConstant] = forProduct + given TastyElement[TastyByteConstant] = forProduct + given TastyElement[TastyShortConstant] = forProduct + given TastyElement[TastyCharConstant] = forProduct + given TastyElement[TastyIntConstant] = forProduct + given TastyElement[TastyLongConstant] = forProduct + given TastyElement[TastyFloatConstant] = forProduct + given TastyElement[TastyDoubleConstant] = forProduct + given TastyElement[TastyStringConstant] = forProduct + given TastyElement[TastyNullConstant] = forProduct + given TastyElement[TastyClassConstant] = forProduct + given TastyElement[TastyConstant] = forSum + + given TastyElement[TastyImplicitArgument] = forProduct + + given TastyElement[TastyModifier.Private] = forProduct + given TastyElement[TastyModifier.Protected] = forProduct + given TastyElement[TastyModifier.PrivateQualified] = forProduct + given TastyElement[TastyModifier.ProtectedQualified] = forProduct + given TastyElement[TastyModifier.Abstract] = forProduct + given TastyElement[TastyModifier.Final] = forProduct + given TastyElement[TastyModifier.Sealed] = forProduct + given TastyElement[TastyModifier.Case] = forProduct + given TastyElement[TastyModifier.Implicit] = forProduct + given TastyElement[TastyModifier.Given] = forProduct + given TastyElement[TastyModifier.Erased] = forProduct + given TastyElement[TastyModifier.Lazy] = forProduct + given TastyElement[TastyModifier.Override] = forProduct + given TastyElement[TastyModifier.Opaque] = forProduct + given TastyElement[TastyModifier.Inline] = forProduct + given TastyElement[TastyModifier.Macro] = forProduct + given TastyElement[TastyModifier.InlineProxy] = forProduct + given TastyElement[TastyModifier.Static] = forProduct + given TastyElement[TastyModifier.Object] = forProduct + given TastyElement[TastyModifier.Trait] = forProduct + given TastyElement[TastyModifier.Enum] = forProduct + given TastyElement[TastyModifier.Local] = forProduct + given TastyElement[TastyModifier.Synthetic] = forProduct + given TastyElement[TastyModifier.Artifact] = forProduct + given TastyElement[TastyModifier.Mutable] = forProduct + given TastyElement[TastyModifier.FieldAccessor] = forProduct + given TastyElement[TastyModifier.CaseAccessor] = forProduct + given tastyModifierCovariantTastyElement: TastyElement[TastyModifier.Covariant] = forProduct + given tastyModifierContravariantTastyElement: TastyElement[TastyModifier.Contravariant] = forProduct + given TastyElement[TastyModifier.HasDefault] = forProduct + given TastyElement[TastyModifier.Stable] = forProduct + given TastyElement[TastyModifier.Extension] = forProduct + given TastyElement[TastyModifier.ParameterSetter] = forProduct + given TastyElement[TastyModifier.ParameterAlias] = forProduct + given TastyElement[TastyModifier.Exported] = forProduct + given TastyElement[TastyModifier.Open] = forProduct + given TastyElement[TastyModifier.Invisible] = forProduct + given TastyElement[TastyModifier.Tracked] = forProduct + given TastyElement[TastyModifier.Annotation] = forProduct + given TastyElement[TastyModifier.Transparent] = forProduct + given TastyElement[TastyModifier.Infix] = forProduct + given TastyElement[TastyModifier] = forSum + + given TastyElement[TastyName.Simple] = forProduct + given TastyElement[TastyName.Qualified] = forProduct + given TastyElement[TastyName.Expanded] = forProduct + given TastyElement[TastyName.ExpandPrefix] = forProduct + given TastyElement[TastyName.Unique] = forProduct + given TastyElement[TastyName.DefaultGetter] = forProduct + given TastyElement[TastyName.SuperAccessor] = forProduct + given TastyElement[TastyName.InlineAccessor] = forProduct + given TastyElement[TastyName.ObjectClass] = forProduct + given TastyElement[TastyName.BodyRetainer] = forProduct + given TastyElement[TastyName.Signed] = forProduct + given TastyElement[TastyName.TargetSigned] = forProduct + given TastyElement[TastyName] = forSum + + given TastyElement[TastyNameReference] = forProduct + + given TastyElement[TastyNameTable] = forProduct + + given TastyElement[TastyTypeParameter] = forProduct + given TastyElement[TastyTermParameter] = forProduct + given TastyElement[TastyParameter.EmptyClause] = forProduct + given TastyElement[TastyParameter.SplitClause] = forProduct + given TastyElement[TastyParameter] = forSum + + given TastyElement[TastyParameterSignature.TypeParameterSectionLength] = forProduct + given TastyElement[TastyParameterSignature.TermParameter] = forProduct + given TastyElement[TastyParameterSignature] = forSum + + given tastyPathLocalReferenceTastyElement: TastyElement[TastyPath.LocalReference] = forProduct + given tastyPathPrefixedLocalReferenceTastyElement: TastyElement[TastyPath.PrefixedLocalReference] = forProduct + given tastyPathPackageReferenceTastyElement: TastyElement[TastyPath.PackageReference] = forProduct + given tastyPathNonLocalReferenceTastyElement: TastyElement[TastyPath.NonLocalReference] = forProduct + given tastyPathNonLocalReferenceInTastyElement: TastyElement[TastyPath.NonLocalReferenceIn] = forProduct + given TastyElement[TastyPath.This] = forProduct + given TastyElement[TastyPath.RecursivelyRefinedThis] = forProduct + given tastyPathSharedTastyElement: TastyElement[TastyPath.Shared] = forProduct + given TastyElement[TastyPath] = forSum + + given [A <: TastySectionPayload: TastyElement]: TastyElement[TastySection[A]] = forProduct + + given TastyElement[TastySectionPayload.Asts] = forProduct + given TastyElement[TastySectionPayload.Positions.Delta] = forProduct + given TastyElement[TastySectionPayload.Positions.Source] = forProduct + given TastyElement[TastySectionPayload.Positions.LineSizes] = forProduct + given TastyElement[TastySectionPayload.Positions] = { + given TastyElement[TastySectionPayload.Positions.Delta | TastySectionPayload.Positions.Source] with { + override val isNode: Boolean = true + override def collect[A]( + value: TastySectionPayload.Positions.Delta | TastySectionPayload.Positions.Source, + )(collector: PartialFunction[Any, A]): Iterable[A] = value match { + case delta: TastySectionPayload.Positions.Delta => TastyElement.collect(delta)(collector) + case source: TastySectionPayload.Positions.Source => TastyElement.collect(source)(collector) + } + + override def map(value: TastySectionPayload.Positions.Delta | TastySectionPayload.Positions.Source)( + mapper: [A] => A => TastyElement[A] ?=> A, + ): TastySectionPayload.Positions.Delta | TastySectionPayload.Positions.Source = + value match { + case delta: TastySectionPayload.Positions.Delta => TastyElement.map(delta)(mapper) + case source: TastySectionPayload.Positions.Source => TastyElement.map(source)(mapper) + } + } + + forProduct + } + + given TastyElement[TastySectionPayload.Comments.Comment] = forProduct + given TastyElement[TastySectionPayload.Comments] = forProduct + given TastyElement[TastySectionPayload.Attributes.Attribute.Scala2StandardLibrary] = forProduct + given TastyElement[TastySectionPayload.Attributes.Attribute.ExplicitNulls] = forProduct + given TastyElement[TastySectionPayload.Attributes.Attribute.CaptureChecked] = forProduct + given TastyElement[TastySectionPayload.Attributes.Attribute.WithPureFunctions] = forProduct + given TastyElement[TastySectionPayload.Attributes.Attribute.Java] = forProduct + given TastyElement[TastySectionPayload.Attributes.Attribute.Outline] = forProduct + given TastyElement[TastySectionPayload.Attributes.Attribute.SourceFile] = forProduct + given TastyElement[TastySectionPayload.Attributes.Attribute] = forSum + given TastyElement[TastySectionPayload.Attributes] = forProduct + + given TastyElement[TastySelector.Imported] = forProduct + given TastyElement[TastySelector.Renamed] = forProduct + given TastyElement[TastySelector.Bounded] = forProduct + given TastyElement[TastySelector] = forSum + + given TastyElement[TastySelf] = forProduct + + given TastyElement[TastyImportStatement] = forProduct + given TastyElement[TastyExportStatement] = forProduct + given TastyElement[TastyStatement] = forSum + + given TastyElement[TastyTemplate] = { + given TastyElement[TastyTerm | TastyTypeTree] with { + override val isNode: Boolean = true + override def collect[A](value: TastyTerm | TastyTypeTree)(collector: PartialFunction[Any, A]): Iterable[A] = + value match { + case term: TastyTerm => TastyElement.collect(term)(collector) + case typeTree: TastyTypeTree => TastyElement.collect(typeTree)(collector) + } + + override def map( + value: TastyTerm | TastyTypeTree, + )(mapper: [A] => A => TastyElement[A] ?=> A): TastyTerm | TastyTypeTree = value match { + case term: TastyTerm => TastyElement.map(term)(mapper) + case typeTree: TastyTypeTree => TastyElement.map(typeTree)(mapper) + } + } + + forProduct + } + + given tastyTermIdentifierTastyElement: TastyElement[TastyTerm.Identifier] = forProduct + given TastyElement[TastyTerm.Select] = forProduct + given TastyElement[TastyTerm.SelectIn] = forProduct + given tastyTermQualifiedThisTastyElement: TastyElement[TastyTerm.QualifiedThis] = forProduct + given TastyElement[TastyTerm.New] = forProduct + given TastyElement[TastyTerm.Elided] = forProduct + given TastyElement[TastyTerm.Throw] = forProduct + given TastyElement[TastyTerm.NamedArgument] = forProduct + given TastyElement[TastyTerm.Apply] = forProduct + given TastyElement[TastyTerm.ApplySignaturePolymorphic] = forProduct + given TastyElement[TastyTerm.TypeApply] = forProduct + given tastyTermSuperTastyElement: TastyElement[TastyTerm.Super] = forProduct + given TastyElement[TastyTerm.TypeAscribed] = forProduct + given TastyElement[TastyTerm.Assignment] = forProduct + given TastyElement[TastyTerm.Block] = forProduct + given TastyElement[TastyTerm.Inlined] = forProduct + given tastyTypeLambdaTastyElement: TastyElement[TastyTerm.Lambda] = forProduct + given TastyElement[TastyTerm.If] = forProduct + given tastyTermMatchTastyElement: TastyElement[TastyTerm.Match] = forProduct + given TastyElement[TastyTerm.Pattern.Bind] = { + given TastyElement[TastyTerm | TastyTypeTree] with { + override val isNode: Boolean = true + override def collect[A](value: TastyTerm | TastyTypeTree)(collector: PartialFunction[Any, A]): Iterable[A] = + value match { + case term: TastyTerm => TastyElement.collect(term)(collector) + case typeTree: TastyTypeTree => TastyElement.collect(typeTree)(collector) + } + + override def map( + value: TastyTerm | TastyTypeTree, + )(mapper: [A] => A => TastyElement[A] ?=> A): TastyTerm | TastyTypeTree = value match { + case term: TastyTerm => TastyElement.map(term)(mapper) + case typeTree: TastyTypeTree => TastyElement.map(typeTree)(mapper) + } + } + + forProduct + } + + given TastyElement[TastyTerm.Pattern.Alternative] = forProduct + given TastyElement[TastyTerm.Pattern.Unapply] = forProduct + given TastyElement[TastyTerm.Pattern.QuotePattern] = forProduct + given TastyElement[TastyTerm.PickledQuoteTree.Explicit] = forProduct + given TastyElement[TastyTerm.PickledQuoteTree.Hole] = forProduct + given TastyElement[TastyTerm.Try] = forProduct + given TastyElement[TastyTerm.Return] = forProduct + given TastyElement[TastyTerm.While] = forProduct + given TastyElement[TastyTerm.RepeatedArgument] = forProduct + given TastyElement[TastyTerm.SelectOuter] = forProduct + given TastyElement[TastyTerm.Quoted] = forProduct + given TastyElement[TastyTerm.Spliced] = forProduct + given TastyElement[TastyTerm.SplicedPattern] = forProduct + given tastyTermSharedTastyElement: TastyElement[TastyTerm.Shared] = forProduct + given TastyElement[TastyTerm] = forSum + + given TastyElement[TastyPackageStatement] = forProduct + given TastyElement[TastyTopLevelStatement] = forSum + + given tastyTypeLocalReferenceTastyElement: TastyElement[TastyType.LocalReference] = forProduct + given tastyTypePrefixedLocalReferenceTastyElement: TastyElement[TastyType.PrefixedLocalReference] = forProduct + given tastyTypePackageReferenceTastyElement: TastyElement[TastyType.PackageReference] = forProduct + given tastyTypeNonLocalReferenceTastyElement: TastyElement[TastyType.NonLocalReference] = forProduct + given tastyTypeNonLocalReferenceInTastyElement: TastyElement[TastyType.NonLocalReferenceIn] = forProduct + given TastyElement[TastyType.RecursivelyRefined] = forProduct + given tastyTypeSuperTastyElement: TastyElement[TastyType.Super] = forProduct + given tastyTypeRefinedTastyElement: TastyElement[TastyType.Refined] = forProduct + given tastyTypeAppliedTastyElement: TastyElement[TastyType.Applied] = forProduct + given tastyTypeTypeBoundsTastyElement: TastyElement[TastyType.TypeBounds] = forProduct + given TastyElement[TastyType.Variance.Invariant] = forProduct + given tastyTypeVarianceCovariantTastyElement: TastyElement[TastyType.Variance.Covariant] = forProduct + given tastyTypeVarianceContravariantTastyElement: TastyElement[TastyType.Variance.Contravariant] = forProduct + given TastyElement[TastyType.Variance] = forSum + given tastyTypeAnnotatedTastyElement: TastyElement[TastyType.Annotated] = forProduct + given TastyElement[TastyType.And] = forProduct + given TastyElement[TastyType.Or] = forProduct + given tastyTypeMatchTastyElement: TastyElement[TastyType.Match] = forProduct + given TastyElement[TastyType.MatchCase] = forProduct + given TastyElement[TastyType.Flexible] = forProduct + given tastyTypeByNameTastyElement: TastyElement[TastyType.ByName] = forProduct + given TastyElement[TastyType.TypeName] = forProduct + given TastyElement[TastyType.ParameterReference] = forProduct + given TastyElement[TastyType.PolymorphicMethod] = forProduct + given TastyElement[TastyType.Method] = forProduct + given TastyElement[TastyType.TypeLambda] = forProduct + given tastyTypeSharedTastyElement: TastyElement[TastyType.Shared] = forProduct + given tastyTypeQualifiedThisTastyElement: TastyElement[TastyType.QualifiedThis] = forProduct + given TastyElement[TastyType] = forSum + + given TastyElement[TastyTypeDefinition] = { + given TastyElement[TastyTypeTree | TastyTemplate] with { + override val isNode: Boolean = true + override def collect[A](value: TastyTypeTree | TastyTemplate)(collector: PartialFunction[Any, A]): Iterable[A] = + value match { + case typeTree: TastyTypeTree => TastyElement.collect(typeTree)(collector) + case template: TastyTemplate => TastyElement.collect(template)(collector) + } + + override def map( + value: TastyTypeTree | TastyTemplate, + )(mapper: [A] => A => TastyElement[A] ?=> A): TastyTypeTree | TastyTemplate = value match { + case typeTree: TastyTypeTree => TastyElement.map(typeTree)(mapper) + case template: TastyTemplate => TastyElement.map(template)(mapper) + } + } + + forProduct + } + + given tastyTypeTreeIdentifierTastyElement: TastyElement[TastyTypeTree.Identifier] = forProduct + given TastyElement[TastyTypeTree.SelectFromTerm] = forProduct + given TastyElement[TastyTypeTree.SelectFromType] = forProduct + given TastyElement[TastyTypeTree.Singleton] = forProduct + given tastyTypeTreeRefinedTastyElement: TastyElement[TastyTypeTree.Refined] = forProduct + given tastyTypeTreeAppliedTastyElement: TastyElement[TastyTypeTree.Applied] = forProduct + given tastyTypeTreeLambdaTastyElement: TastyElement[TastyTypeTree.Lambda] = forProduct + given tastyTypeTreeTypeBoundsTastyElement: TastyElement[TastyTypeTree.TypeBounds] = forProduct + given tastyTypeTreeAnnotatedTastyElement: TastyElement[TastyTypeTree.Annotated] = forProduct + given tastyTypeTreeMatchTastyElement: TastyElement[TastyTypeTree.Match] = forProduct + given tastyTypeTreeByNameTastyElement: TastyElement[TastyTypeTree.ByName] = forProduct + given tastyTypeTreeSharedTastyElement: TastyElement[TastyTypeTree.Shared] = forProduct + given TastyElement[TastyTypeTree] = forSum + + given TastyElement[TastyValOrDefDefinition.Val] = forProduct + given TastyElement[TastyValOrDefDefinition.Def] = forProduct + given TastyElement[TastyValOrDefDefinition] = forSum +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastySpecification.test.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastySpecification.test.scala new file mode 100644 index 000000000..990229eda --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/TastySpecification.test.scala @@ -0,0 +1,40 @@ +package io.bazel.rules_scala.dottyijar.tasty + +import java.io.{File, FileOutputStream, InputStream} +import java.nio.file.Files +import java.util.zip.ZipFile +import org.apache.commons.io.IOUtils +import org.specs2.mutable.SpecificationWithJUnit +import scala.jdk.CollectionConverters.* + +class TastySpecification extends SpecificationWithJUnit { + protected def withTestCases[A](callback: List[TestCase] => A): A = { + val scala3CompilerJar = File.createTempFile("scala3-compiler", ".jar") + val inputStream = getClass.getClassLoader.getResourceAsStream("scala3-compiler.jar") + val outputStream = new FileOutputStream(scala3CompilerJar) + + try { + try { + IOUtils.copy(inputStream, outputStream) + } finally { + inputStream.close() + outputStream.close() + } + + val scala3CompilerZipFile = new ZipFile(scala3CompilerJar) + + callback( + scala3CompilerZipFile + .entries() + .asScala + .filter(_.getName.endsWith(".tasty")) + .map(entry => TestCase(entry.getName, scala3CompilerZipFile.getInputStream(entry))) + .toList, + ) + } finally { + Files.delete(scala3CompilerJar.toPath) + } + } +} + +case class TestCase(path: String, inputStream: InputStream) diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/BUILD b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/BUILD new file mode 100644 index 000000000..de882f625 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/BUILD @@ -0,0 +1,15 @@ +load("//scala:scala.bzl", "scala_library_for_plugin_bootstrapping") + +scala_library_for_plugin_bootstrapping( + name = "format", + srcs = glob(["*.scala"]), + scala_version = "3.6.2", + visibility = ["//visibility:public"], + deps = [ + "//src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric", + "@dev_zio_izumi_reflect_3_6_2", + "@dev_zio_izumi_reflect_thirdparty_boopickle_shaded_3_6_2", + "@io_bazel_rules_scala_scala_compiler_3_6_2", + "@io_bazel_rules_scala_scala_tasty_core_3_6_2", + ], +) diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/Marker.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/Marker.scala new file mode 100644 index 000000000..861aec495 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/Marker.scala @@ -0,0 +1,11 @@ +package io.bazel.rules_scala.dottyijar.tasty.format + +private case class Marker(position: Int, markerType: MarkerType) + +sealed abstract class MarkerType + +object MarkerType { + case object AstSection extends MarkerType +} + +class MarkerNotSetException(markerType: MarkerType) extends Exception(s"A marker of type $markerType hasn't been set.") diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyFormat.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyFormat.scala new file mode 100644 index 000000000..cc37a6d3a --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyFormat.scala @@ -0,0 +1,280 @@ +package io.bazel.rules_scala.dottyijar.tasty.format + +import io.bazel.rules_scala.dottyijar.tasty.numeric.{SignedInt, SignedLong, UnsignedInt, UnsignedLong} +import dotty.tools.dotc.util.Spans.Span +import izumi.reflect.Tag +import java.util.UUID +import scala.collection.{mutable, Factory} +import scala.deriving.Mirror +import scala.reflect.ClassTag + +trait TastyFormat[A] private[format] { + def read(reader: TastyReader): A + def write(writer: TastyWriter, value: A): Unit + + final def bimap[B: Tag](to: A => B, from: B => A): TastyFormat[B] = + TastyFormat(reader => to(read(reader)), (writer, value) => write(writer, from(value))) + + final def marked(markerType: MarkerType): TastyFormat[A] = new TastyFormat[A] { + override def read(reader: TastyReader): A = { + reader.setMarker(markerType) + + TastyFormat.this.read(reader) + } + + override def write(writer: TastyWriter, value: A): Unit = { + writer.setMarker(markerType) + + TastyFormat.this.write(writer, value) + } + } + + final def withLengthPrefixed: TastyFormat[A] = new TastyFormat[A] { + override def read(reader: TastyReader): A = + reader.readWithLength(reader.readUnsignedInt().value)(TastyFormat.this.read) + + override def write(writer: TastyWriter, value: A): Unit = + writer.writeWithLengthPrefixed(TastyFormat.this.write(_, value)) + } +} + +object TastyFormat { + private val isDebuggingEnabled: Boolean = Option(System.getProperty("DEBUG_TASTYFORMAT")).contains("true") + + def apply[A: Tag](read: TastyReader => A, write: (TastyWriter, A) => Unit): TastyFormat[A] = { + val _read = read + val _write = write + + if (isDebuggingEnabled) { + new DebuggingTastyFormat[A] { + override protected def readUnderlying(reader: TastyReader): A = _read(reader) + override protected def writeUnderlying(writer: TastyWriter, value: A): Unit = _write(writer, value) + } + } else { + new TastyFormat[A] { + override def read(reader: TastyReader): A = _read(reader) + override def write(writer: TastyWriter, value: A): Unit = _write(writer, value) + } + } + } + + inline def forIterableWithLengthPrefixed[Element: TastyFormat, SeqLike <: Seq[Element]](using + factory: Factory[Element, SeqLike], + ): TastyFormat[SeqLike] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + factory.fromSpecific(reader.readUntilEnd(summon[TastyFormat[Element]].read(reader))) + }, + (writer, value) => + writer.writeWithLengthPrefixed(writer => value.foreach(summon[TastyFormat[Element]].write(writer, _))), + ) + + inline def forIterableWithoutLengthPrefixed[Element: TastyFormat, SeqLike <: Seq[Element]](using + factory: Factory[Element, SeqLike], + ): TastyFormat[SeqLike] = TastyFormat( + reader => factory.fromSpecific(reader.readUntilEnd(summon[TastyFormat[Element]].read(reader))), + (writer, value) => value.foreach(summon[TastyFormat[Element]].write(writer, _)), + ) + + inline def forOptional[Init <: Tuple: TastyFormat, Last: TastyFormat](using + evidence1: Tuple.Init[Tuple.Append[Init, Option[Last]]] =:= Init, + evidence2: Tuple.Last[Tuple.Append[Init, Option[Last]]] =:= Option[Last], + ): TastyFormat[Tuple.Append[Init, Option[Last]]] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val init = summon[TastyFormat[Init]].read(reader) + + init :* Option.unless(reader.isAtEnd)(summon[TastyFormat[Last]].read(reader)) + }, + (writer, value) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[Init]].write(writer, value.init) + + value.last.foreach(summon[TastyFormat[Last]].write(writer, _)) + }, + ) + + inline def forProduct[A <: Product](using + mirror: Mirror.ProductOf[A], + )(using underlying: TastyFormat[mirror.MirroredElemTypes]): TastyFormat[A] = TastyFormat( + reader => mirror.fromTuple(underlying.read(reader)), + (writer, record) => underlying.write(writer, Tuple.fromProductTyped(record)), + ) + + inline def forSumType[A <: TastyReferencable: TastySumType]: TastyFormat[A] = + TastyFormat( + reader => + reader.readReferencable { + val tag = reader.readByte() + + summon[TastySumType[A]].variantsByTag + .getOrElse( + tag, + throw new Exception( + s"Unknown tag: $tag. Expected one of ${summon[TastySumType[A]].variants.map(_.tag).mkString(", ")}", + ), + ) + .read(reader) + }, + (writer, value) => { + + /** + * Previously, we declared a `TastyReferencableFormat` type that wrapped an underlying [[TastyFormat]] value by + * recording the position of the value to be written (which is necessary to write references to that value). We + * ended up scrapping this and instead made all sum types "referencable" because it wasn't guaranteed that the + * positions of sum type values would be recorded. This is due to two reasons: + * + * 1. [[TastySumType]]s can be composed, so even if the [[TastyFormat]] for a type `A` was referencable, it + * may have been passed to [[TastySumType.or]] to create a [[TastySumType]] for a supertype of `A`. Unless + * that supertype's [[TastyFormat]] was wrapped in a `TastyReferencableFormat`, it wouldn't be + * "referencable", meaning that we couldn't write references to values of type `A` unless those values were + * written as "`A`"s, and not some supertype of `A`. + * 1. [[TastySumType]] composition also means that if a value of type `A` were written using a more specific + * [[TastyFormat]] for one of its subclasses that wasn't wrapped in a `TastyReferencableFormat`, the + * position for that value wouldn't have been recorded. + */ + writer.writeReferencable(value)( + summon[TastySumType[A]].variants + .find(_.maybeWrite(writer, value)) + .getOrElse(throw new Exception(s"Couldn't find the variant of $value")), + ) + }, + ) + + def forValue[A: ValueOf: Tag]: TastyFormat[A] = TastyFormat(_ => summon[ValueOf[A]].value, (_, _) => {}) + + inline def forVariadic[Init <: Tuple: TastyFormat, LastElement: TastyFormat, Last <: Iterable[LastElement]](using + factory: Factory[LastElement, Last], + evidence1: Tuple.Init[Tuple.Append[Init, Last]] =:= Init, + evidence2: Tuple.Last[Tuple.Append[Init, Last]] =:= Last, + ): TastyFormat[Tuple.Append[Init, Last]] = TastyFormat( + reader => + reader.readWithLength(reader.readUnsignedInt().value) { reader => + val init = summon[TastyFormat[Init]].read(reader) + + init :* summon[Factory[LastElement, Last]] + .fromSpecific(reader.readUntilEnd(summon[TastyFormat[LastElement]].read(reader))) + }, + (writer, value) => + writer.writeWithLengthPrefixed { writer => + summon[TastyFormat[Init]].write(writer, value.init) + + value.last.foreach(summon[TastyFormat[LastElement]].write(writer, _)) + }, + ) + + given TastyFormat[SignedInt] = TastyFormat(_.readSignedInt(), (writer, value) => writer.writeSignedInt(value)) + given TastyFormat[SignedLong] = TastyFormat(_.readSignedLong(), (writer, value) => writer.writeSignedLong(value)) + given TastyFormat[Span] = + summon[TastyFormat[SignedLong]].bimap(long => new Span(long.value), span => SignedLong(span.coords)) + + given TastyFormat[String] = TastyFormat(_.readUtf8String(), (writer, value) => writer.writeUtf8String(value)) + given TastyFormat[UnsignedInt] = TastyFormat(_.readUnsignedInt(), (writer, value) => writer.writeUnsignedInt(value)) + given TastyFormat[UnsignedLong] = + TastyFormat(_.readUnsignedLong(), (writer, value) => writer.writeUnsignedLong(value)) + + given TastyFormat[UUID] = TastyFormat(_.readUuid(), (writer, value) => writer.writeUuid(value)) + + inline given [A: TastyFormat]: TastyFormat[Tuple1[A]] with { + override def read(reader: TastyReader): Tuple1[A] = Tuple1(summon[TastyFormat[A]].read(reader)) + override def write(writer: TastyWriter, value: Tuple1[A]): Unit = summon[TastyFormat[A]].write(writer, value._1) + } + + inline given [Head: TastyFormat, Tail <: Tuple: TastyFormat]: TastyFormat[Head *: Tail] with { + override def read(reader: TastyReader): Head *: Tail = + summon[TastyFormat[Head]].read(reader) *: summon[TastyFormat[Tail]].read(reader) + + override def write(writer: TastyWriter, value: Head *: Tail): Unit = { + summon[TastyFormat[Head]].write(writer, value.head) + summon[TastyFormat[Tail]].write(writer, value.tail) + } + } +} + +private abstract class DebuggingTastyFormat[A: Tag] extends TastyFormat[A] { + protected def readUnderlying(reader: TastyReader): A + protected def writeUnderlying(writer: TastyWriter, value: A): Unit + + private val typeName = summon[Tag[A]].tag.shortName + + override def read(reader: TastyReader): A = { + DebuggingTastyFormat.depth += 1 + + try { + DebuggingTastyFormat.logBuffer ++= + s"${" " * DebuggingTastyFormat.depth}TastyFormat: Reading $typeName at ${reader.start}\n" + + val position = reader.start + val result = readUnderlying(reader) + + DebuggingTastyFormat.logBuffer ++= + s"${" " * DebuggingTastyFormat.depth}TastyFormat: Read $result at $position\n" + + result + } finally { + DebuggingTastyFormat.depth -= 1 + } + } + + override def write(writer: TastyWriter, value: A): Unit = { + DebuggingTastyFormat.depth += 1 + + try { + DebuggingTastyFormat.logBuffer ++= + s"${" " * DebuggingTastyFormat.depth}TastyFormat: Writing $value at ${writer.start}\n" + + val position = writer.start + + writeUnderlying(writer, value) + + DebuggingTastyFormat.logBuffer ++= + s"${" " * DebuggingTastyFormat.depth}TastyFormat: Wrote $typeName at $position\n" + } finally { + DebuggingTastyFormat.depth -= 1 + } + } +} + +private[tasty] object DebuggingTastyFormat { + private var depth = -1 + private val logBuffer = new mutable.StringBuilder() + + def clearLogs(): Unit = logBuffer.clear() + def logs: String = logBuffer.toString +} + +class TastySumType[A](private[format] val variants: TastySumType.Variant[? <: A]*) { + private[format] val variantsByTag: Map[Byte, TastySumType.Variant[? <: A]] = + variants.map(variant => variant.tag -> variant).toMap + + def peekIsVariant(reader: TastyReader): Boolean = variantsByTag.contains(reader.peek(_.readByte())) + def or[B](other: TastySumType[B]): TastySumType[A | B] = + new TastySumType(variants ++ other.variants*) +} + +object TastySumType { + case class Variant[A: ClassTag](tag: Byte)(using TastyFormat[A]) { + private[format] def maybeWrite(writer: TastyWriter, value: Any): Boolean = value match { + case value: A => + writer.writeByte(tag) + + summon[TastyFormat[A]].write(writer, value) + + true + + case _ => false + } + + private[format] def read(reader: TastyReader): A = summon[TastyFormat[A]].read(reader) + } + + object Variant { + def apply[A: ClassTag](tag: Int)(using TastyFormat[A]): Variant[A] = new Variant(tag.toByte) + } + + def withSingleVariant[A: ClassTag](tag: Byte, format: TastyFormat[A]): TastySumType[A] = + new TastySumType(Variant(tag)(using summon, format)) + + def withSingleVariant[A: ClassTag](tag: Int, format: TastyFormat[A]): TastySumType[A] = + withSingleVariant(tag.toByte, format) +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReader.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReader.scala new file mode 100644 index 000000000..0a973079b --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReader.scala @@ -0,0 +1,180 @@ +package io.bazel.rules_scala.dottyijar.tasty.format + +import dotty.tools.tasty.TastyFormat as DottyTastyFormat +import io.bazel.rules_scala.dottyijar.tasty.numeric.{SignedInt, SignedLong, UnsignedInt, UnsignedLong} +import java.nio.charset.StandardCharsets +import java.util.UUID +import scala.annotation.tailrec +import scala.collection.mutable + +case class TastyReader private ( + input: Array[Byte], + private[format] var start: Int, + end: Int, + markers: mutable.Map[MarkerType, Marker], + private var nextReferencableId: Int, + referencesByTargetPosition: mutable.LongMap[TastyReference[? <: MarkerType, ?]], + referencablesByPosition: mutable.LongMap[TastyReferencable], +) { + private def readNBytes(length: Int): Array[Byte] = { + val result = input.slice(start, start + length) + + start += result.length + + result + } + + private def readUncompressedLong(): Long = Range(0, 8).foldLeft(0L) { case (current, _) => + (current << 8) | (readByte() & 0xff /* This prevents sign extension */ ) + } + + def isAtEnd: Boolean = start >= end + def linkReferences(): Unit = referencesByTargetPosition.foreach { case (position, reference) => + val referencable = + referencablesByPosition.getOrElse(position, throw new Exception(s"No referencable value read at $position")) + + referencable.information = TastyReferencableInformation(Some(reference.referencableId)) + } + + def peek[A](read: TastyReader => A): A = { + + /** + * We have [[reader]] and [[this]] share [[nextReferencableId]], [[referencesByTargetPosition]], and + * [[referencablesByPosition]] values to increase efficiency and reduce allocations. Although the value peeked will + * eventually be re-read, if it's a [[TastyReference]], it should be re-read with the same ID because we cache + * references in [[referencesByTargetPosition]]. + */ + val reader = copy() + val result = read(reader) + + nextReferencableId = reader.nextReferencableId + + result + } + + def readByte(): Byte = { + val result = input(start) + + start += 1 + + result + } + + def readMagicNumber(): Unit = { + val actual = readNBytes(4) + val expected = DottyTastyFormat.header.map(_.toByte) + + if (!java.util.Arrays.equals(actual, expected)) { + throw new Exception( + s"Expected a magic number of ${expected.toList}, but got ${actual.toList}", + ) + } + } + + def readReferencable[A <: TastyReferencable](read: => A): A = { + val position = start + val result = read + + referencablesByPosition(position) = result + + result + } + + def readReference[RelativeTo <: MarkerType, Value](relativeTo: RelativeTo): TastyReference[RelativeTo, Value] = { + val marker = markers.getOrElse(relativeTo, throw new MarkerNotSetException(relativeTo)) + val position = marker.position + readUnsignedInt().value + + referencesByTargetPosition + .getOrElseUpdate( + position, { + val result = TastyReference(relativeTo, nextReferencableId) + + nextReferencableId += 1 + + result + }, + ) + .asInstanceOf[TastyReference[RelativeTo, Value]] + } + + def readSignedInt(): SignedInt = SignedInt(readSignedLong().value.toInt) + def readSignedLong(): SignedLong = { + var currentByte = readByte() + var result: Long = (currentByte << 1).toByte >> 1 // Sign extend the first byte, using bit 6 as the sign + + while ((currentByte & 0x80) == 0) { + currentByte = readByte() + result = (result << 7) | (currentByte & 0x7f) + } + + SignedLong(result) + } + + def readUnsignedInt(): UnsignedInt = UnsignedInt(readUnsignedLong().value.toInt) + def readUnsignedLong(): UnsignedLong = { + var currentByte = readByte() + var result = 0L + + while { + result = (result << 7) | (currentByte & 0x7f) + + (currentByte & 0x80) == 0 + } do { + currentByte = readByte() + } + + UnsignedLong(result) + } + + def readUntilEnd[A](read: => A): List[A] = { + val result = readWhile(!isAtEnd)(read) + + assert(start == end, s"Expected to read until $end, but stopped at $start") + + result + } + + def readUtf8String(): String = new String(readNBytes(readUnsignedInt().value), StandardCharsets.UTF_8) + def readUuid(): UUID = new UUID(readUncompressedLong(), readUncompressedLong()) + def readWithLength[A](length: Int)(read: TastyReader => A): A = { + val reader = copy(end = start + length) + val result = read(reader) + + assert( + reader.start == reader.end, + s"Given a length of $length, expected to read until $end, but stopped at ${reader.start}", + ) + + start = reader.start + nextReferencableId = reader.nextReferencableId + + result + } + + def readWhile[A](condition: => Boolean)(read: => A): List[A] = { + @tailrec + def withExisting(existing: List[A])(condition: => Boolean)(read: => A): List[A] = { + if (condition) { + withExisting(read +: existing)(condition)(read) + } else { + existing + } + } + + withExisting(List.empty)(condition)(read).reverse + } + + def setMarker(markerType: MarkerType): Unit = markers(markerType) = Marker(start, markerType) +} + +object TastyReader { + def apply(input: Array[Byte]): TastyReader = new TastyReader( + input, + start = 0, + end = input.length, + markers = mutable.Map.empty, + nextReferencableId = 0, + referencesByTargetPosition = mutable.LongMap.empty, + referencablesByPosition = mutable.LongMap.empty, + ) +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReferencable.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReferencable.scala new file mode 100644 index 000000000..439f8031c --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReferencable.scala @@ -0,0 +1,15 @@ +package io.bazel.rules_scala.dottyijar.tasty.format + +trait TastyReferencable { + def information: TastyReferencableInformation + def information_=(newInformation: TastyReferencableInformation): Unit +} + +case class TastyReferencableInformation(id: Option[Int] = None) + +object TastyReferencableInformation { + given TastyFormat[TastyReferencableInformation] = new TastyFormat[TastyReferencableInformation] { + def read(reader: TastyReader): TastyReferencableInformation = TastyReferencableInformation() + def write(writer: TastyWriter, value: TastyReferencableInformation): Unit = {} + } +} diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReference.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReference.scala new file mode 100644 index 000000000..6c3c4493e --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyReference.scala @@ -0,0 +1,35 @@ +package io.bazel.rules_scala.dottyijar.tasty.format + +import izumi.reflect.Tag + +/** + * References another value in the TASTy file. In their serialized form, [[TastyReference]]s are represented as an + * offset from some marker in the TASTy file. In reality, the only marker is the AST section payload, but I wanted to + * make [[TastyReference]] indifferent to the marker type to enforce a separation between the infrastructure for reading + * to and writing from TASTy files ([[TastyReference]], [[TastyFormat]], [[TastyReader]], and [[TastyWriter]]) and the + * TASTy types. + * + * In their deserialized form, [[TastyReference]]s are assigned IDs that uniquely identify the value they're + * referencing. These values should extend [[TastyReferencable]], which has an `information` field containing this ID. + * We opted to represent references in this way because it makes it far easier to update the values they reference. If, + * instead, we stored the address to which they point, we'd have to update those addresses whenever they change, which + * is very difficult to do in automated fashion. + */ +case class TastyReference[RelativeTo <: MarkerType, Value](relativeTo: RelativeTo, referencableId: Int) + +object TastyReference { + + /** + * [[Value]] must have a [[TastySumType]] because values that have [[TastySumType]]s are the only values whose + * positions we record during writing. See the comment in the implementation of [[TastyFormat.forSumType]] to + * understand why. + */ + given [RelativeTo <: MarkerType: ValueOf: Tag, Value: Tag](using + => TastySumType[Value], + ): TastyFormat[TastyReference[RelativeTo, Value]] = TastyFormat( + reader => reader.readReference(summon[ValueOf[RelativeTo]].value), + (writer, reference) => writer.writeReference(reference), + ) +} + +type TastyAstReference[A] = TastyReference[MarkerType.AstSection.type, A] diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyWriter.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyWriter.scala new file mode 100644 index 000000000..c579a2c6a --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/format/TastyWriter.scala @@ -0,0 +1,170 @@ +package io.bazel.rules_scala.dottyijar.tasty.format + +import dotty.tools.tasty.TastyFormat as DottyTastyFormat +import io.bazel.rules_scala.dottyijar.tasty.numeric.{SignedInt, SignedLong, UnsignedInt, UnsignedLong} +import java.nio.charset.StandardCharsets +import java.util.UUID +import scala.collection.mutable + +case class TastyWriter private ( + output: mutable.ArrayBuffer[Byte], + private[format] var start: Int, + private val markers: mutable.Map[MarkerType, Marker], + private val references: mutable.ArrayBuffer[Reference], + private val referencablePositionsById: mutable.LongMap[Int], +) { + private def writeUncompressedLong(long: Long): Unit = + Range(56, -8, -8).foreach(i => writeByte((long >>> i).toByte)) + + private def writeUnsignedLongWithWidth(long: Long, width: Int): Unit = { + Range(width * 7, 7, -7).foreach(remainingBits => writeByte(((long >>> (remainingBits - 7)) & 0x7f).toByte)) + + writeByte(((long & 0x7f) | 0x80).toByte) + } + + def fillInReferences(): Unit = { + references.foreach { reference => + val markerType = reference.reference.relativeTo + val marker = markers.getOrElse(markerType, throw new MarkerNotSetException(markerType)) + val relativePosition = referencablePositionsById(reference.reference.referencableId) - marker.position + + copy(start = reference.position).writeUnsignedLongWithWidth(relativePosition, TastyWriter.referenceWidth) + } + + references.clear() + } + + def setMarker(markerType: MarkerType): Unit = markers(markerType) = Marker(start, markerType) + def toArray: Array[Byte] = output.toArray + def writeByte(byte: Byte): Unit = { + if (start == output.length) { + output += byte + } else { + output(start) = byte + } + + start += 1 + } + + def writeBytes(bytes: Array[Byte]): Unit = { + output ++= bytes + start += bytes.length + } + + def writeMagicNumber(): Unit = writeBytes(DottyTastyFormat.header.map(_.toByte)) + def writeReferencable(value: TastyReferencable)(write: => Unit): Unit = { + value.information.id.foreach(referencablePositionsById(_) = start) + + write + } + + def writeReference(reference: TastyReference[? <: MarkerType, ?]): Unit = { + references += Reference(reference, start) + + writeUnsignedLongWithWidth(0, TastyWriter.referenceWidth) + } + + def writeSignedInt(int: SignedInt): Unit = writeSignedLong(SignedLong(int.value.toLong)) + + /** + * This method is copied from this one in Dotty: + * [[https://github.com/scala/scala3/blob/4d3f7576ccae724e6f83d2f3d68bd4c4e1dd5a14/tasty/src/dotty/tools/tasty/TastyBuffer.scala]] + * + * I can't, for the life of me, understand how it works. + */ + def writeSignedLong(long: SignedLong): Unit = { + def writePrefix(long: Long): Unit = { + val prefix = long >> 7 + + if (prefix != 0L - ((long >> 6) & 1)) { + writePrefix(prefix) + } + + writeByte((long & 0x7f).toByte) + } + + val prefix = long.value >> 7 + + if (prefix != 0L - ((long.value >> 6) & 1)) { + writePrefix(prefix) + } + + writeByte(((long.value & 0x7f) | 0x80).toByte) + } + + def writeUnsignedInt(int: UnsignedInt): Unit = writeUnsignedLong(UnsignedLong(int.value.toLong)) + def writeUnsignedLong(long: UnsignedLong): Unit = { + def writePrefix(long: Long): Unit = { + val prefix = long >> 7 + + if (prefix != 0) { + writePrefix(prefix) + } + + writeByte((long & 0x7f).toByte) + } + + val prefix = long.value >> 7 + + if (prefix != 0) { + writePrefix(prefix) + } + + writeByte(((long.value & 0x7f) | 0x80).toByte) + } + + def writeUtf8String(string: String): Unit = { + val bytes = string.getBytes(StandardCharsets.UTF_8) + + writeUnsignedInt(UnsignedInt(bytes.length)) + writeBytes(bytes) + } + + def writeUuid(uuid: UUID): Unit = { + writeUncompressedLong(uuid.getMostSignificantBits) + writeUncompressedLong(uuid.getLeastSignificantBits) + } + + def writeWithLengthPrefixed(write: TastyWriter => Unit): Unit = { + val bufferWriter = copy( + output = mutable.ArrayBuffer.empty, + start = 0, + markers = mutable.Map.empty, + references = mutable.ArrayBuffer.empty, + referencablePositionsById = mutable.LongMap.empty, + ) + + write(bufferWriter) + + val buffer = bufferWriter.toArray + + writeUnsignedInt(UnsignedInt(buffer.length)) + + markers ++= bufferWriter.markers.view.map { case (markerType, marker) => + (markerType, marker.copy(position = start + marker.position)) + } + + references ++= + bufferWriter.references.view.map(reference => reference.copy(position = start + reference.position)) + + referencablePositionsById ++= + bufferWriter.referencablePositionsById.view.map { case (id, position) => (id, start + position) } + + writeBytes(buffer) + } +} + +object TastyWriter { + private val referenceWidth = 4 + + def empty: TastyWriter = new TastyWriter( + output = mutable.ArrayBuffer.empty, + start = 0, + markers = mutable.Map.empty, + references = mutable.ArrayBuffer.empty, + referencablePositionsById = mutable.LongMap.empty, + ) +} + +private case class ReferencedValue(relativeTo: MarkerType, value: Any) +private case class Reference(reference: TastyReference[? <: MarkerType, ?], position: Int) diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/BUILD b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/BUILD new file mode 100644 index 000000000..6328ffb26 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/BUILD @@ -0,0 +1,8 @@ +load("//scala:scala.bzl", "scala_library_for_plugin_bootstrapping") + +scala_library_for_plugin_bootstrapping( + name = "numeric", + srcs = glob(["*.scala"]), + scala_version = "3.6.2", + visibility = ["//visibility:public"], +) diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/SignedInt.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/SignedInt.scala new file mode 100644 index 000000000..c3405fa7e --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/SignedInt.scala @@ -0,0 +1,3 @@ +package io.bazel.rules_scala.dottyijar.tasty.numeric + +case class SignedInt(value: Int) extends AnyVal diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/SignedLong.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/SignedLong.scala new file mode 100644 index 000000000..32a80c967 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/SignedLong.scala @@ -0,0 +1,3 @@ +package io.bazel.rules_scala.dottyijar.tasty.numeric + +case class SignedLong(value: Long) extends AnyVal diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/UnsignedInt.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/UnsignedInt.scala new file mode 100644 index 000000000..de7d91e49 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/UnsignedInt.scala @@ -0,0 +1,3 @@ +package io.bazel.rules_scala.dottyijar.tasty.numeric + +case class UnsignedInt(value: Int) extends AnyVal diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/UnsignedLong.scala b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/UnsignedLong.scala new file mode 100644 index 000000000..36ef7abc0 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tasty/numeric/UnsignedLong.scala @@ -0,0 +1,3 @@ +package io.bazel.rules_scala.dottyijar.tasty.numeric + +case class UnsignedLong(value: Long) extends AnyVal diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tastyinspector/BUILD b/src/scala/io/bazel/rules_scala/dottyijar/tastyinspector/BUILD new file mode 100644 index 000000000..f2de05f27 --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tastyinspector/BUILD @@ -0,0 +1,24 @@ +load("@rules_java//java:defs.bzl", "java_binary") +load("//scala:scala.bzl", "scala_binary") + +java_binary( + name = "compiler", + main_class = "dotty.tools.dotc.Main", + runtime_deps = [ + "@io_bazel_rules_scala_scala_compiler_3_6_2", + "@io_bazel_rules_scala_scala_library_3_6_2", + ], +) + +scala_binary( + name = "tastyinspector", + srcs = glob(["*.scala"]), + data = [":compiler"], + main_class = "io.bazel.rules_scala.dottyijar.tastyinspector.TastyInspector", + scala_version = "3.6.2", + deps = [ + "//src/scala/io/bazel/rules_scala/dottyijar/tasty", + "//src/scala/io/bazel/rules_scala/dottyijar/tasty/format", + "@dev_zio_izumi_reflect_3_6_2", + ], +) diff --git a/src/scala/io/bazel/rules_scala/dottyijar/tastyinspector/TastyInspector.scala b/src/scala/io/bazel/rules_scala/dottyijar/tastyinspector/TastyInspector.scala new file mode 100644 index 000000000..67eb46f2f --- /dev/null +++ b/src/scala/io/bazel/rules_scala/dottyijar/tastyinspector/TastyInspector.scala @@ -0,0 +1,106 @@ +package io.bazel.rules_scala.dottyijar.tastyinspector + +import io.bazel.rules_scala.dottyijar.tasty.format.{TastyFormat, TastyReader, TastyReferencableInformation} +import io.bazel.rules_scala.dottyijar.tasty.{Tasty, TastyNameTable} +import java.nio.file.{Files, Paths} +import scala.collection.mutable +import scala.jdk.CollectionConverters.* +import scala.sys.process.* +import scala.util.Try + +object TastyInspector { + private val maximumLineLength = 120 + private val prettyPrintCache = mutable.Map[(Any, Int), String]() + private def getIndentation(depth: Int): String = " " * depth + private def prettyPrintTasty(tasty: Tasty): String = prettyPrintTastyValue(tasty, depth = 0) + private def prettyPrintTastyProductLike(prefix: String, elements: => Iterator[Any], depth: Int): String = { + val indentation = getIndentation(depth) + val singleLineJoinedElements = elements.map(prettyPrintTastyValue(_, depth = 0)).mkString(", ") + val singleLine = s"$indentation$prefix($singleLineJoinedElements)" + + if (singleLine.length <= maximumLineLength) { + singleLine + } else { + val lines = elements.map(element => s"${prettyPrintTastyValue(element, depth = depth + 1)},\n").mkString + + s"$indentation$prefix(\n$lines$indentation)" + } + } + + private def prettyPrintTastyValue(value: Any, depth: Int): String = prettyPrintCache.getOrElseUpdate( + (value, depth), + value match { + case iterable: Iterable[?] => + prettyPrintTastyProductLike( + iterable match { + case _: List[?] => "List" + case _: Vector[?] => "Vector" + case _ => iterable.getClass.getSimpleName + }, + iterable.iterator, + depth, + ) + + /** + * [[TastyNameTable]] is the only type that overrides `toString`. + */ + case product: Product if !product.isInstanceOf[TastyNameTable] => + if (product.productArity == 0) { + s"${getIndentation(depth)}${product.productPrefix}" + } else { + prettyPrintTastyProductLike(product.productPrefix, product.productIterator, depth) + } + + case _ => value.toString.split('\n').view.map(line => s"${getIndentation(depth)}$line").mkString("\n") + }, + ) + + def main(arguments: Array[String]): Unit = ( + for { + argument <- arguments.headOption.toRight("Please provide a path to a `.scala` file.\n") + tastyPath <- + if (argument.endsWith(".tasty")) { + Right(Paths.get(argument)) + } else { + val outputDirectory = Files.createTempDirectory("tasty-inspector") + val compilerReturnCode = List( + "src/scala/io/bazel/rules_scala/dottyijar/tastyinspector/compiler", + "-d", + outputDirectory.toString, + "-usejavacp", + argument, + ).! + + if (compilerReturnCode == 0) { + val outputtedFileStream = Files.walk(outputDirectory) + + try { + val outputtedFiles = outputtedFileStream.iterator.asScala + .filter(path => path.toFile.isFile && path.toString.endsWith(".tasty")) + .toList + + outputtedFiles match { + case List(path) => Right(path) + case Nil => Left("The compiler didn't produce any `.tasty` files.\n") + case _ => Left(s"The compiler produced multiple `.tasty` files: ${outputtedFiles.mkString(", ")}\n") + } + } finally { + outputtedFileStream.close() + } + } else { + Left("") + } + } + } yield tastyPath + ) match { + case Left(errorMessage) => + println(errorMessage) + + sys.exit(1) + + case Right(tastyPath) => + val tasty = Tasty.read(Files.readAllBytes(tastyPath)) + + println(prettyPrintTasty(tasty)) + } +} diff --git a/test/dottyijar/BUILD b/test/dottyijar/BUILD new file mode 100644 index 000000000..97386a83e --- /dev/null +++ b/test/dottyijar/BUILD @@ -0,0 +1,33 @@ +load("//scala:scala.bzl", "scala_binary", "scala_library") + +scala_library( + name = "private-members", + srcs = ["PrivateMembers.scala"], + scala_version = "3.6.2", + tags = ["manual"], +) + +scala_binary( + name = "private-members-dependent", + srcs = ["PrivateMembersDependent.scala"], + main_class = "dottyijar.Main", + scala_version = "3.6.2", + tags = ["manual"], + deps = [":private-members"], +) + +scala_library( + name = "definition-values", + srcs = ["DefinitionValues.scala"], + scala_version = "3.6.2", + tags = ["manual"], +) + +scala_library( + name = "definition-values-dependent", + srcs = ["DefinitionValuesDependent.scala"], + main_class = "dottyijar.Main", + scala_version = "3.6.2", + tags = ["manual"], + deps = [":definition-values"], +) diff --git a/test/dottyijar/DefinitionValues1.scala b/test/dottyijar/DefinitionValues1.scala new file mode 100644 index 000000000..93be7e48c --- /dev/null +++ b/test/dottyijar/DefinitionValues1.scala @@ -0,0 +1,5 @@ +package dottyijar + +object DefinitionValues { + val greeting: String = s"Hello, Bob!" +} diff --git a/test/dottyijar/DefinitionValues2.scala b/test/dottyijar/DefinitionValues2.scala new file mode 100644 index 000000000..e56b012c1 --- /dev/null +++ b/test/dottyijar/DefinitionValues2.scala @@ -0,0 +1,5 @@ +package dottyijar + +object DefinitionValues { + val greeting: String = s"Hello, Bob!".replace("Bob", "Alice") +} diff --git a/test/dottyijar/DefinitionValuesDependent.scala b/test/dottyijar/DefinitionValuesDependent.scala new file mode 100644 index 000000000..6ce24b32a --- /dev/null +++ b/test/dottyijar/DefinitionValuesDependent.scala @@ -0,0 +1,5 @@ +package dottyijar + +object Main { + def main(arguments: Array[String]): Unit = println(DefinitionValues.greeting) +} diff --git a/test/dottyijar/PrivateMembers1.scala b/test/dottyijar/PrivateMembers1.scala new file mode 100644 index 000000000..ba62873e4 --- /dev/null +++ b/test/dottyijar/PrivateMembers1.scala @@ -0,0 +1,13 @@ +package dottyijar + +object PrivateMembers { + private val privateField = () + private lazy val privateLazyField = () + + val publicField = () + lazy val publicLazyField = () + + private def privateMethod: Unit = {} + + def publicMethod: Unit = {} +} diff --git a/test/dottyijar/PrivateMembers2.scala b/test/dottyijar/PrivateMembers2.scala new file mode 100644 index 000000000..bcb70e6fd --- /dev/null +++ b/test/dottyijar/PrivateMembers2.scala @@ -0,0 +1,8 @@ +package dottyijar + +object PrivateMembers { + val publicField = () + lazy val publicLazyField = () + + def publicMethod: Unit = {} +} diff --git a/test/dottyijar/PrivateMembersDependent.scala b/test/dottyijar/PrivateMembersDependent.scala new file mode 100644 index 000000000..f5af8138a --- /dev/null +++ b/test/dottyijar/PrivateMembersDependent.scala @@ -0,0 +1,5 @@ +package dottyijar + +object Main { + def main(arguments: Array[String]): Unit = println(PrivateMembers.publicField) +} \ No newline at end of file diff --git a/test/shell/test_dottyijar.sh b/test/shell/test_dottyijar.sh new file mode 100755 index 000000000..7cb67b19d --- /dev/null +++ b/test/shell/test_dottyijar.sh @@ -0,0 +1,32 @@ +# shellcheck source=./test_runner.sh +dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +. "${dir}"/test_runner.sh +. "${dir}"/test_helper.sh +runner=$(get_test_runner "${1:-local}") + +ijar_invariant() { + cp "test/dottyijar/$3" "test/dottyijar/$2" + trap "rm 'test/dottyijar/$2'" EXIT + + bazel build "//test/dottyijar:$1-dependent" + + ijar1_path="$(mktemp)" + + cp "bazel-bin/test/dottyijar/$1-ijar.jar" "$ijar1_path" + + cp "test/dottyijar/$4" "test/dottyijar/$2" + bazel build "//test/dottyijar:$1-dependent" + + diff "$ijar1_path" "bazel-bin/test/dottyijar/$1-ijar.jar" +} + +ijar_invariant_to_private_members() { + ijar_invariant private-members PrivateMembers.scala PrivateMembers1.scala PrivateMembers2.scala +} + +ijar_invariant_to_definition_values() { + ijar_invariant definition-values DefinitionValues.scala DefinitionValues1.scala DefinitionValues2.scala +} + +$runner ijar_invariant_to_private_members +$runner ijar_invariant_to_definition_values diff --git a/test/shell/test_scala_config.sh b/test/shell/test_scala_config.sh index ab17f661d..6c53fdc0e 100755 --- a/test/shell/test_scala_config.sh +++ b/test/shell/test_scala_config.sh @@ -7,17 +7,23 @@ runner=$(get_test_runner "${1:-local}") test_classpath_contains_2_12() { bazel aquery 'mnemonic("Javac", //src/java/io/bazel/rulesscala/scalac:scalac)' \ --repo_env=SCALA_VERSION=2.12.20 \ + --repo_env=SCALA_VERSIONS=2.12.20 \ | grep scala-library-2.12 } test_classpath_contains_2_13() { bazel aquery 'mnemonic("Javac", //src/java/io/bazel/rulesscala/scalac:scalac)' \ --repo_env=SCALA_VERSION=2.13.15 \ + --repo_env=SCALA_VERSIONS=2.13.15 \ | grep scala-library-2.13 } test_scala_config_content() { - bazel build --repo_env=SCALA_VERSION=0.0.0 @io_bazel_rules_scala_config//:all 2> /dev/null + bazel build \ + --repo_env=SCALA_VERSION=0.0.0 \ + --repo_env=SCALA_VERSIONS=0.0.0 \ + @io_bazel_rules_scala_config//:all 2> /dev/null + grep "SCALA_MAJOR_VERSION='0.0'" \ "$(bazel info output_base)"/external/*io_bazel_rules_scala_config/config.bzl } diff --git a/third_party/repositories/scala_2_11.bzl b/third_party/repositories/scala_2_11.bzl index e4c882cc6..e346eaf59 100644 --- a/third_party/repositories/scala_2_11.bzl +++ b/third_party/repositories/scala_2_11.bzl @@ -110,6 +110,10 @@ artifacts = { "artifact": "com.typesafe:config:1.2.1", "sha256": "c160fbd78f51a0c2375a794e435ce2112524a6871f64d0331895e9e26ee8b9ee", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "io_bazel_rules_scala_failureaccess": { "artifact": "com.google.guava:failureaccess:1.0.2", "sha256": "8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064", @@ -130,8 +134,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.1", + "sha256": "c30719db974d6452793fe191b3638a5777005485bae145924044530ffa5f6122", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -166,36 +173,53 @@ artifacts = { "sha256": "0df97574914aee92fd349d0cb4e00f3345d45b2c239e0bb50f0a90ead47888e0", }, "io_bazel_rules_scala_org_specs2_specs2_common": { - "artifact": "org.specs2:specs2-common_2.11:4.4.1", - "sha256": "52d7c0da58725606e98c6e8c81d2efe632053520a25da9140116d04a4abf9d2c", + "artifact": "org.specs2:specs2-common_2.11:4.10.6", + "sha256": "823bdd776b3c4759506b527e0e58d31f9eebbba7b16858ad0fef4b29559dcb5d", "deps": [ "@io_bazel_rules_scala_org_specs2_specs2_fp", + "@io_bazel_rules_scala_scala_library", + "@io_bazel_rules_scala_scala_parser_combinators", + "@io_bazel_rules_scala_scala_reflect", + "@io_bazel_rules_scala_scala_xml", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_core": { - "artifact": "org.specs2:specs2-core_2.11:4.4.1", - "sha256": "8e95cb7e347e7a87e7a80466cbd88419ece1aaacb35c32e8bd7d299a623b31b9", + "artifact": "org.specs2:specs2-core_2.11:4.10.6", + "sha256": "dbd85edf0b399f98a4494c7e5a2ff1868a5c2c6f06e34e56d0e2e2b5b9d9431e", "deps": [ "@io_bazel_rules_scala_org_specs2_specs2_common", "@io_bazel_rules_scala_org_specs2_specs2_matcher", + "@io_bazel_rules_scala_scala_library", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_fp": { - "artifact": "org.specs2:specs2-fp_2.11:4.4.1", - "sha256": "e43006fdd0726ffcd1e04c6c4d795176f5f765cc787cc09baebe1fcb009e4462", + "artifact": "org.specs2:specs2-fp_2.11:4.10.6", + "sha256": "afe794801e0adb93c353bf1c8b4a10b44d5f8ead52449ee17b3284613eac0f5e", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], }, "io_bazel_rules_scala_org_specs2_specs2_junit": { - "artifact": "org.specs2:specs2-junit_2.11:4.4.1", - "sha256": "a8549d52e87896624200fe35ef7b841c1c698a8fb5d97d29bf082762aea9bb72", + "artifact": "org.specs2:specs2-junit_2.11:4.10.6", + "sha256": "b09bb5324b339b022ccf23a669c815084249299e5014097195a0f671d4b89eb3", "deps": [ + "@io_bazel_rules_scala_junit_junit", "@io_bazel_rules_scala_org_specs2_specs2_core", + "@io_bazel_rules_scala_scala_library", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_matcher": { - "artifact": "org.specs2:specs2-matcher_2.11:4.4.1", - "sha256": "448e5ab89d4d650d23030fdbee66a010a07dcac5e4c3e73ef5fe39ca1aace1cd", + "artifact": "org.specs2:specs2-matcher_2.11:4.10.6", + "sha256": "c788968cfef1377bc9025f96a4ff86fc2a44c5fd36762683a6c7d597b14692ef", "deps": [ "@io_bazel_rules_scala_org_specs2_specs2_common", + "@io_bazel_rules_scala_scala_library", ], }, "io_bazel_rules_scala_scala_compiler": { @@ -424,6 +448,14 @@ artifacts = { "artifact": "org.codehaus.mojo:animal-sniffer-annotations:1.24", "sha256": "c720e6e5bcbe6b2f48ded75a47bccdb763eede79d14330102e0d352e3d89ed92", }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.11:1.0.0", + "sha256": "c26ed70888b620232b2dbea98449f1169b5074224c63202e82ff3f4e0f6030f4", + "deps": [ + "@io_bazel_rules_scala_scala_library", + "@io_bazel_rules_scala_scala_reflect", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.11:2.1.2", "sha256": "e9667b8b7276aeb42599f536fe4d7caab06eabc55e9995572267ad60c7a11c8b", diff --git a/third_party/repositories/scala_2_12.bzl b/third_party/repositories/scala_2_12.bzl index b25066a41..86566751c 100644 --- a/third_party/repositories/scala_2_12.bzl +++ b/third_party/repositories/scala_2_12.bzl @@ -116,6 +116,10 @@ artifacts = { "artifact": "com.typesafe:config:1.4.1", "sha256": "4c0aa7e223c75c8840c41fc183d4cd3118140a1ee503e3e08ce66ed2794c948f", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "dev_dirs_directories": { "artifact": "dev.dirs:directories:26", "sha256": "6d18fe25aa30b7e08b908cd21151d8f96e22965c640acd7751add9bbfe6137d4", @@ -140,8 +144,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.2", + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -176,36 +183,52 @@ artifacts = { "sha256": "0df97574914aee92fd349d0cb4e00f3345d45b2c239e0bb50f0a90ead47888e0", }, "io_bazel_rules_scala_org_specs2_specs2_common": { - "artifact": "org.specs2:specs2-common_2.12:4.4.1", - "sha256": "7b7d2497bfe10ad552f5ab3780537c7db9961d0ae841098d5ebd91c78d09438a", + "artifact": "org.specs2:specs2-common_2.12:4.20.9", + "sha256": "cb4c1354b70824df6583fbc22f1565679f04e350f026fe3f243b190661afce82", "deps": [ "@io_bazel_rules_scala_org_specs2_specs2_fp", + "@io_bazel_rules_scala_scala_library", + "@io_bazel_rules_scala_scala_parser_combinators", + "@io_bazel_rules_scala_scala_reflect", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_core": { - "artifact": "org.specs2:specs2-core_2.12:4.4.1", - "sha256": "f92c3c83844aac13250acec4eb247a2a26a2b3f04e79ef1bf42c56de4e0bb2e7", + "artifact": "org.specs2:specs2-core_2.12:4.20.9", + "sha256": "1a4c428cb57d501ebd184307e3c9dbdfce44c227e201b21bed9ba4a00cd003ec", "deps": [ - "@io_bazel_rules_scala_org_specs2_specs2_common", "@io_bazel_rules_scala_org_specs2_specs2_matcher", + "@io_bazel_rules_scala_scala_library", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_fp": { - "artifact": "org.specs2:specs2-fp_2.12:4.4.1", - "sha256": "834a145b28dbf57ba6d96f02a3862522e693b5aeec44d4cb2f305ef5617dc73f", + "artifact": "org.specs2:specs2-fp_2.12:4.20.9", + "sha256": "42afcd59093a2f416c62b00c6b95a37ccac09cc55bf4429dbe214c76a4c94db5", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], }, "io_bazel_rules_scala_org_specs2_specs2_junit": { - "artifact": "org.specs2:specs2-junit_2.12:4.4.1", - "sha256": "c867824801da5cccf75354da6d12d406009c435865ecd08a881b799790e9ffec", + "artifact": "org.specs2:specs2-junit_2.12:4.20.9", + "sha256": "148cf6e4bda1f9ad707fea73d9cb4f0b7cbe50d90424b087b029e91c41b6572f", "deps": [ + "@io_bazel_rules_scala_junit_junit", "@io_bazel_rules_scala_org_specs2_specs2_core", + "@io_bazel_rules_scala_scala_library", + "@io_bazel_rules_scala_scala_xml", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_matcher": { - "artifact": "org.specs2:specs2-matcher_2.12:4.4.1", - "sha256": "78c699001c307dcc5dcbec8a80cd9f14e9bdaa047579c3d1010ee4bea66805fe", + "artifact": "org.specs2:specs2-matcher_2.12:4.20.9", + "sha256": "ce3da49a66f1d50b0b622d1d980088869115778f06a582542ed2f508e8d9cd3e", "deps": [ "@io_bazel_rules_scala_org_specs2_specs2_common", + "@io_bazel_rules_scala_scala_library", ], }, "io_bazel_rules_scala_scala_compiler": { @@ -436,6 +459,13 @@ artifacts = { "artifact": "org.codehaus.mojo:animal-sniffer-annotations:1.24", "sha256": "c720e6e5bcbe6b2f48ded75a47bccdb763eede79d14330102e0d352e3d89ed92", }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.12:1.1.3", + "sha256": "647cb184209f821296b48acbed4836e9b1dbde2ac2c921a400e9da2576edb497", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.12:2.12.0", "sha256": "1619c5e4399e1e4793667970aae232652db0549e795c90abf91e44c55ec37cb3", @@ -450,6 +480,10 @@ artifacts = { "@io_bazel_rules_scala_scala_compiler", ], }, + "org_scala_sbt_test_interface": { + "artifact": "org.scala-sbt:test-interface:1.0", + "sha256": "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + }, "org_scalameta_common": { "artifact": "org.scalameta:common_2.12:4.9.9", "sha256": "8b85032d1fd8cb33c091cf560362b5a9ce5cb507ab38e6968d04f7978d18f600", diff --git a/third_party/repositories/scala_2_13.bzl b/third_party/repositories/scala_2_13.bzl index 891fc654d..9e888ed25 100644 --- a/third_party/repositories/scala_2_13.bzl +++ b/third_party/repositories/scala_2_13.bzl @@ -120,6 +120,10 @@ artifacts = { "artifact": "com.typesafe:config:1.4.1", "sha256": "4c0aa7e223c75c8840c41fc183d4cd3118140a1ee503e3e08ce66ed2794c948f", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "dev_dirs_directories": { "artifact": "dev.dirs:directories:26", "sha256": "6d18fe25aa30b7e08b908cd21151d8f96e22965c640acd7751add9bbfe6137d4", @@ -144,8 +148,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.2", + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -180,36 +187,52 @@ artifacts = { "sha256": "0df97574914aee92fd349d0cb4e00f3345d45b2c239e0bb50f0a90ead47888e0", }, "io_bazel_rules_scala_org_specs2_specs2_common": { - "artifact": "org.specs2:specs2-common_2.13:4.10.3", - "sha256": "51636fb6a904b3c807de0673f283a971379c9886e03aedbecbf5d787b22346b0", + "artifact": "org.specs2:specs2-common_2.13:4.20.9", + "sha256": "d6ef04e754ed9c86cef53e215815dbe14fa9462d6d242619661d353ed9fb9e1a", "deps": [ "@io_bazel_rules_scala_org_specs2_specs2_fp", + "@io_bazel_rules_scala_scala_library", + "@io_bazel_rules_scala_scala_parser_combinators", + "@io_bazel_rules_scala_scala_reflect", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_core": { - "artifact": "org.specs2:specs2-core_2.13:4.10.3", - "sha256": "9cc55eb11781c9b77689cf8175795fad34b060718b04a225fffb0613a181256b", + "artifact": "org.specs2:specs2-core_2.13:4.20.9", + "sha256": "43a5aa9958c260403914e5127af55f5108ab107854d4a1828d2c92ccfdac8193", "deps": [ - "@io_bazel_rules_scala_org_specs2_specs2_common", "@io_bazel_rules_scala_org_specs2_specs2_matcher", + "@io_bazel_rules_scala_scala_library", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_fp": { - "artifact": "org.specs2:specs2-fp_2.13:4.10.3", - "sha256": "48a908b345c93a3387ddd157ab338686513f450c7dd8afe0f32b6edc7ff15239", + "artifact": "org.specs2:specs2-fp_2.13:4.20.9", + "sha256": "76104d161fc7fe7a685448fb2662eeadc039fff6bf5ed40943f2d03316ed72b7", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], }, "io_bazel_rules_scala_org_specs2_specs2_junit": { - "artifact": "org.specs2:specs2-junit_2.13:4.10.3", - "sha256": "49c4e7cf5483aada90852314983fc046f72092da1a4e7900ace6574444f581ea", + "artifact": "org.specs2:specs2-junit_2.13:4.20.9", + "sha256": "a5476802cfce4c2ff2d09661a7629d0cdfa29af2c41949bc07c40a60f629809e", "deps": [ + "@io_bazel_rules_scala_junit_junit", "@io_bazel_rules_scala_org_specs2_specs2_core", + "@io_bazel_rules_scala_scala_library", + "@io_bazel_rules_scala_scala_xml", + "@org_portable_scala_portable_scala_reflect", + "@org_scala_sbt_test_interface", ], }, "io_bazel_rules_scala_org_specs2_specs2_matcher": { - "artifact": "org.specs2:specs2-matcher_2.13:4.10.3", - "sha256": "754465f58dad8f59b3bb299d5dc127027bf0c0c9ad25250260fc95abd705363b", + "artifact": "org.specs2:specs2-matcher_2.13:4.20.9", + "sha256": "9dd9b7ee0a895c2d3231cf4b820380f419ec6c06afac6c3b31caf1ad3b362dd7", "deps": [ "@io_bazel_rules_scala_org_specs2_specs2_common", + "@io_bazel_rules_scala_scala_library", ], }, "io_bazel_rules_scala_scala_compiler": { @@ -458,6 +481,13 @@ artifacts = { "artifact": "org.jline:jline:3.27.1", "sha256": "72fcbc58da05092067739ded62ed6b1ba9075ecd0fee1caa634c2cbf1a16fe7a", }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.13:1.1.3", + "sha256": "920f62979293069cf721865f931e42f9f7b0b2720ee9f6a9ddff76a19ecf8d4e", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.13:2.12.0", "sha256": "befff482233cd7f9a7ca1e1f5a36ede421c018e6ce82358978c475d45532755f", @@ -472,6 +502,10 @@ artifacts = { "@io_bazel_rules_scala_scala_compiler", ], }, + "org_scala_sbt_test_interface": { + "artifact": "org.scala-sbt:test-interface:1.0", + "sha256": "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + }, "org_scalameta_common": { "artifact": "org.scalameta:common_2.13:4.9.9", "sha256": "be66ba789863c65abfc9c1e448339ce915f2bc778daf348d884a967e8eb473ee", diff --git a/third_party/repositories/scala_3_1.bzl b/third_party/repositories/scala_3_1.bzl index e8bda4e22..a538969f9 100644 --- a/third_party/repositories/scala_3_1.bzl +++ b/third_party/repositories/scala_3_1.bzl @@ -124,10 +124,29 @@ artifacts = { "artifact": "com.typesafe:config:1.4.1", "sha256": "4c0aa7e223c75c8840c41fc183d4cd3118140a1ee503e3e08ce66ed2794c948f", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "dev_dirs_directories": { "artifact": "dev.dirs:directories:26", "sha256": "6d18fe25aa30b7e08b908cd21151d8f96e22965c640acd7751add9bbfe6137d4", }, + "dev_zio_izumi_reflect": { + "artifact": "dev.zio:izumi-reflect_3:2.2.1", + "sha256": "1228bff91fa585b227427b3b8b93bffaf64b22727ea6a2c160476f00434d3a10", + "deps": [ + "@dev_zio_izumi_reflect_thirdparty_boopickle_shaded", + "@io_bazel_rules_scala_scala_library", + ], + }, + "dev_zio_izumi_reflect_thirdparty_boopickle_shaded": { + "artifact": "dev.zio:izumi-reflect-thirdparty-boopickle-shaded_3:2.2.1", + "sha256": "34c34cbf4c18f725ba3860bc9f09dfeaee13430cccc72711cbac711bb379b290", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], + }, "io_bazel_rules_scala_failureaccess": { "artifact": "com.google.guava:failureaccess:1.0.2", "sha256": "8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064", @@ -148,8 +167,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.2", + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -271,6 +293,13 @@ artifacts = { "artifact": "org.scala-lang:tasty-core_3:3.1.3", "sha256": "0a183e880575bcc97a2047761880241784734e7ee094dc6fafd6a8f2109ff1da", }, + "io_bazel_rules_scala_scala_tasty_inspector": { + "artifact": "org.scala-lang:scala3-tasty-inspector_3:3.1.3", + "sha256": "b9c6e3c196cb0ea15cff650c8fd430a81bfb149336443921b3bcbdcb50243138", + "deps": [ + "@io_bazel_rules_scala_scala_compiler", + ], + }, "io_bazel_rules_scala_scala_xml": { "artifact": "org.scala-lang.modules:scala-xml_3:2.1.0", "sha256": "48f22343575f4b1d6550eecc42d4b7f0a0d30223c72f015d8d893feab4cbeecd", @@ -495,6 +524,13 @@ artifacts = { "@org_jline_jline_terminal", ], }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.13:1.1.1", + "sha256": "11f2f59d0c228912811095025b36ce58a025a8397851d773295c8ad7862d8488", + "deps": [ + "@io_bazel_rules_scala_scala_library_2", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.13:2.12.0", "sha256": "befff482233cd7f9a7ca1e1f5a36ede421c018e6ce82358978c475d45532755f", @@ -516,6 +552,10 @@ artifacts = { "@org_scala_sbt_util_interface", ], }, + "org_scala_sbt_test_interface": { + "artifact": "org.scala-sbt:test-interface:1.0", + "sha256": "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + }, "org_scala_sbt_util_interface": { "artifact": "org.scala-sbt:util-interface:1.10.5", "sha256": "a2f3eb27a3312acda18dc57ea617d443048e816a9803bde723643254f0b66e5a", diff --git a/third_party/repositories/scala_3_2.bzl b/third_party/repositories/scala_3_2.bzl index 7d16e7242..6b3a3796f 100644 --- a/third_party/repositories/scala_3_2.bzl +++ b/third_party/repositories/scala_3_2.bzl @@ -124,10 +124,29 @@ artifacts = { "artifact": "com.typesafe:config:1.4.1", "sha256": "4c0aa7e223c75c8840c41fc183d4cd3118140a1ee503e3e08ce66ed2794c948f", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "dev_dirs_directories": { "artifact": "dev.dirs:directories:26", "sha256": "6d18fe25aa30b7e08b908cd21151d8f96e22965c640acd7751add9bbfe6137d4", }, + "dev_zio_izumi_reflect": { + "artifact": "dev.zio:izumi-reflect_3:2.2.1", + "sha256": "1228bff91fa585b227427b3b8b93bffaf64b22727ea6a2c160476f00434d3a10", + "deps": [ + "@dev_zio_izumi_reflect_thirdparty_boopickle_shaded", + "@io_bazel_rules_scala_scala_library", + ], + }, + "dev_zio_izumi_reflect_thirdparty_boopickle_shaded": { + "artifact": "dev.zio:izumi-reflect-thirdparty-boopickle-shaded_3:2.2.1", + "sha256": "34c34cbf4c18f725ba3860bc9f09dfeaee13430cccc72711cbac711bb379b290", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], + }, "io_bazel_rules_scala_failureaccess": { "artifact": "com.google.guava:failureaccess:1.0.2", "sha256": "8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064", @@ -148,8 +167,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.2", + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -271,6 +293,13 @@ artifacts = { "artifact": "org.scala-lang:tasty-core_3:3.2.2", "sha256": "df0690721532323a3c533385a06a4f532231d012d38f65bd75864718cfabace4", }, + "io_bazel_rules_scala_scala_tasty_inspector": { + "artifact": "org.scala-lang:scala3-tasty-inspector_3:3.2.2", + "sha256": "7ccaba0ad2551f840cf2779a6624b93656eb91205f21067b424d749f7b421824", + "deps": [ + "@io_bazel_rules_scala_scala_compiler", + ], + }, "io_bazel_rules_scala_scala_xml": { "artifact": "org.scala-lang.modules:scala-xml_3:2.1.0", "sha256": "48f22343575f4b1d6550eecc42d4b7f0a0d30223c72f015d8d893feab4cbeecd", @@ -495,6 +524,13 @@ artifacts = { "@org_jline_jline_terminal", ], }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.13:1.1.1", + "sha256": "11f2f59d0c228912811095025b36ce58a025a8397851d773295c8ad7862d8488", + "deps": [ + "@io_bazel_rules_scala_scala_library_2", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.13:2.12.0", "sha256": "befff482233cd7f9a7ca1e1f5a36ede421c018e6ce82358978c475d45532755f", @@ -516,6 +552,10 @@ artifacts = { "@org_scala_sbt_util_interface", ], }, + "org_scala_sbt_test_interface": { + "artifact": "org.scala-sbt:test-interface:1.0", + "sha256": "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + }, "org_scala_sbt_util_interface": { "artifact": "org.scala-sbt:util-interface:1.10.5", "sha256": "a2f3eb27a3312acda18dc57ea617d443048e816a9803bde723643254f0b66e5a", diff --git a/third_party/repositories/scala_3_3.bzl b/third_party/repositories/scala_3_3.bzl index 62079504c..693016775 100644 --- a/third_party/repositories/scala_3_3.bzl +++ b/third_party/repositories/scala_3_3.bzl @@ -124,10 +124,29 @@ artifacts = { "artifact": "com.typesafe:config:1.4.1", "sha256": "4c0aa7e223c75c8840c41fc183d4cd3118140a1ee503e3e08ce66ed2794c948f", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "dev_dirs_directories": { "artifact": "dev.dirs:directories:26", "sha256": "6d18fe25aa30b7e08b908cd21151d8f96e22965c640acd7751add9bbfe6137d4", }, + "dev_zio_izumi_reflect": { + "artifact": "dev.zio:izumi-reflect_3:2.2.1", + "sha256": "1228bff91fa585b227427b3b8b93bffaf64b22727ea6a2c160476f00434d3a10", + "deps": [ + "@dev_zio_izumi_reflect_thirdparty_boopickle_shaded", + "@io_bazel_rules_scala_scala_library", + ], + }, + "dev_zio_izumi_reflect_thirdparty_boopickle_shaded": { + "artifact": "dev.zio:izumi-reflect-thirdparty-boopickle-shaded_3:2.2.1", + "sha256": "34c34cbf4c18f725ba3860bc9f09dfeaee13430cccc72711cbac711bb379b290", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], + }, "io_bazel_rules_scala_failureaccess": { "artifact": "com.google.guava:failureaccess:1.0.2", "sha256": "8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064", @@ -148,8 +167,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.2", + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -272,6 +294,13 @@ artifacts = { "artifact": "org.scala-lang:tasty-core_3:3.3.4", "sha256": "13d44693d6f2d38e0595954d11234c0373d6d0e689a8151b06878c5b631d57d5", }, + "io_bazel_rules_scala_scala_tasty_inspector": { + "artifact": "org.scala-lang:scala3-tasty-inspector_3:3.3.4", + "sha256": "8c289ef4e155edf28245ac44b6652bd498566643160a31713a6bfd0777272fd0", + "deps": [ + "@io_bazel_rules_scala_scala_compiler", + ], + }, "io_bazel_rules_scala_scala_xml": { "artifact": "org.scala-lang.modules:scala-xml_3:2.1.0", "sha256": "48f22343575f4b1d6550eecc42d4b7f0a0d30223c72f015d8d893feab4cbeecd", @@ -496,6 +525,13 @@ artifacts = { "@org_jline_jline_terminal", ], }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.13:1.1.1", + "sha256": "11f2f59d0c228912811095025b36ce58a025a8397851d773295c8ad7862d8488", + "deps": [ + "@io_bazel_rules_scala_scala_library_2", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.13:2.12.0", "sha256": "befff482233cd7f9a7ca1e1f5a36ede421c018e6ce82358978c475d45532755f", @@ -517,6 +553,10 @@ artifacts = { "@org_scala_sbt_util_interface", ], }, + "org_scala_sbt_test_interface": { + "artifact": "org.scala-sbt:test-interface:1.0", + "sha256": "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + }, "org_scala_sbt_util_interface": { "artifact": "org.scala-sbt:util-interface:1.10.5", "sha256": "a2f3eb27a3312acda18dc57ea617d443048e816a9803bde723643254f0b66e5a", diff --git a/third_party/repositories/scala_3_4.bzl b/third_party/repositories/scala_3_4.bzl index 7d7fe65bd..8ab050088 100644 --- a/third_party/repositories/scala_3_4.bzl +++ b/third_party/repositories/scala_3_4.bzl @@ -124,10 +124,29 @@ artifacts = { "artifact": "com.typesafe:config:1.4.1", "sha256": "4c0aa7e223c75c8840c41fc183d4cd3118140a1ee503e3e08ce66ed2794c948f", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "dev_dirs_directories": { "artifact": "dev.dirs:directories:26", "sha256": "6d18fe25aa30b7e08b908cd21151d8f96e22965c640acd7751add9bbfe6137d4", }, + "dev_zio_izumi_reflect": { + "artifact": "dev.zio:izumi-reflect_3:2.2.1", + "sha256": "1228bff91fa585b227427b3b8b93bffaf64b22727ea6a2c160476f00434d3a10", + "deps": [ + "@dev_zio_izumi_reflect_thirdparty_boopickle_shaded", + "@io_bazel_rules_scala_scala_library", + ], + }, + "dev_zio_izumi_reflect_thirdparty_boopickle_shaded": { + "artifact": "dev.zio:izumi-reflect-thirdparty-boopickle-shaded_3:2.2.1", + "sha256": "34c34cbf4c18f725ba3860bc9f09dfeaee13430cccc72711cbac711bb379b290", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], + }, "io_bazel_rules_scala_failureaccess": { "artifact": "com.google.guava:failureaccess:1.0.2", "sha256": "8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064", @@ -148,8 +167,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.2", + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -272,6 +294,13 @@ artifacts = { "artifact": "org.scala-lang:tasty-core_3:3.4.3", "sha256": "e3b5bdb3bbb3038e290d85e6e4f528c9d7fe1c7b1274695e3140ec6b86a84097", }, + "io_bazel_rules_scala_scala_tasty_inspector": { + "artifact": "org.scala-lang:scala3-tasty-inspector_3:3.4.3", + "sha256": "45188bd5b2b985aaf6cab56aa7484fbcdc488c7b0ce39bba39f3c80f43b5c58c", + "deps": [ + "@io_bazel_rules_scala_scala_compiler", + ], + }, "io_bazel_rules_scala_scala_xml": { "artifact": "org.scala-lang.modules:scala-xml_3:2.1.0", "sha256": "48f22343575f4b1d6550eecc42d4b7f0a0d30223c72f015d8d893feab4cbeecd", @@ -496,6 +525,13 @@ artifacts = { "@org_jline_jline_terminal", ], }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.13:1.1.1", + "sha256": "11f2f59d0c228912811095025b36ce58a025a8397851d773295c8ad7862d8488", + "deps": [ + "@io_bazel_rules_scala_scala_library_2", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.13:2.12.0", "sha256": "befff482233cd7f9a7ca1e1f5a36ede421c018e6ce82358978c475d45532755f", @@ -517,6 +553,10 @@ artifacts = { "@org_scala_sbt_util_interface", ], }, + "org_scala_sbt_test_interface": { + "artifact": "org.scala-sbt:test-interface:1.0", + "sha256": "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + }, "org_scala_sbt_util_interface": { "artifact": "org.scala-sbt:util-interface:1.10.5", "sha256": "a2f3eb27a3312acda18dc57ea617d443048e816a9803bde723643254f0b66e5a", diff --git a/third_party/repositories/scala_3_5.bzl b/third_party/repositories/scala_3_5.bzl index 061f8c20f..28a69ce24 100644 --- a/third_party/repositories/scala_3_5.bzl +++ b/third_party/repositories/scala_3_5.bzl @@ -124,10 +124,29 @@ artifacts = { "artifact": "com.typesafe:config:1.4.1", "sha256": "4c0aa7e223c75c8840c41fc183d4cd3118140a1ee503e3e08ce66ed2794c948f", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "dev_dirs_directories": { "artifact": "dev.dirs:directories:26", "sha256": "6d18fe25aa30b7e08b908cd21151d8f96e22965c640acd7751add9bbfe6137d4", }, + "dev_zio_izumi_reflect": { + "artifact": "dev.zio:izumi-reflect_3:2.2.1", + "sha256": "1228bff91fa585b227427b3b8b93bffaf64b22727ea6a2c160476f00434d3a10", + "deps": [ + "@dev_zio_izumi_reflect_thirdparty_boopickle_shaded", + "@io_bazel_rules_scala_scala_library", + ], + }, + "dev_zio_izumi_reflect_thirdparty_boopickle_shaded": { + "artifact": "dev.zio:izumi-reflect-thirdparty-boopickle-shaded_3:2.2.1", + "sha256": "34c34cbf4c18f725ba3860bc9f09dfeaee13430cccc72711cbac711bb379b290", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], + }, "io_bazel_rules_scala_failureaccess": { "artifact": "com.google.guava:failureaccess:1.0.2", "sha256": "8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064", @@ -148,8 +167,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.2", + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -272,6 +294,13 @@ artifacts = { "artifact": "org.scala-lang:tasty-core_3:3.5.2", "sha256": "b380158748e147f4e44654ad16003c89599ddd456eac29f9686cb6d5515067f3", }, + "io_bazel_rules_scala_scala_tasty_inspector": { + "artifact": "org.scala-lang:scala3-tasty-inspector_3:3.5.2", + "sha256": "8bfe15d8804f6e2d72cdb4a3bc284ec6cb974291f327270db37e7af5128d1660", + "deps": [ + "@io_bazel_rules_scala_scala_compiler", + ], + }, "io_bazel_rules_scala_scala_xml": { "artifact": "org.scala-lang.modules:scala-xml_3:2.1.0", "sha256": "48f22343575f4b1d6550eecc42d4b7f0a0d30223c72f015d8d893feab4cbeecd", @@ -496,6 +525,13 @@ artifacts = { "@org_jline_jline_terminal", ], }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.13:1.1.1", + "sha256": "11f2f59d0c228912811095025b36ce58a025a8397851d773295c8ad7862d8488", + "deps": [ + "@io_bazel_rules_scala_scala_library_2", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.13:2.12.0", "sha256": "befff482233cd7f9a7ca1e1f5a36ede421c018e6ce82358978c475d45532755f", @@ -517,6 +553,10 @@ artifacts = { "@org_scala_sbt_util_interface", ], }, + "org_scala_sbt_test_interface": { + "artifact": "org.scala-sbt:test-interface:1.0", + "sha256": "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + }, "org_scala_sbt_util_interface": { "artifact": "org.scala-sbt:util-interface:1.10.5", "sha256": "a2f3eb27a3312acda18dc57ea617d443048e816a9803bde723643254f0b66e5a", diff --git a/third_party/repositories/scala_3_6.bzl b/third_party/repositories/scala_3_6.bzl index 6e91ba7ed..d6456808b 100644 --- a/third_party/repositories/scala_3_6.bzl +++ b/third_party/repositories/scala_3_6.bzl @@ -124,10 +124,29 @@ artifacts = { "artifact": "com.typesafe:config:1.4.1", "sha256": "4c0aa7e223c75c8840c41fc183d4cd3118140a1ee503e3e08ce66ed2794c948f", }, + "commons_io_commons_io": { + "artifact": "commons-io:commons-io:2.18.0", + "sha256": "f3ca0f8d63c40e23a56d54101c60d5edee136b42d84bfb85bc7963093109cf8b", + }, "dev_dirs_directories": { "artifact": "dev.dirs:directories:26", "sha256": "6d18fe25aa30b7e08b908cd21151d8f96e22965c640acd7751add9bbfe6137d4", }, + "dev_zio_izumi_reflect": { + "artifact": "dev.zio:izumi-reflect_3:2.2.1", + "sha256": "1228bff91fa585b227427b3b8b93bffaf64b22727ea6a2c160476f00434d3a10", + "deps": [ + "@dev_zio_izumi_reflect_thirdparty_boopickle_shaded", + "@io_bazel_rules_scala_scala_library", + ], + }, + "dev_zio_izumi_reflect_thirdparty_boopickle_shaded": { + "artifact": "dev.zio:izumi-reflect-thirdparty-boopickle-shaded_3:2.2.1", + "sha256": "34c34cbf4c18f725ba3860bc9f09dfeaee13430cccc72711cbac711bb379b290", + "deps": [ + "@io_bazel_rules_scala_scala_library", + ], + }, "io_bazel_rules_scala_failureaccess": { "artifact": "com.google.guava:failureaccess:1.0.2", "sha256": "8a8f81cf9b359e3f6dfa691a1e776985c061ef2f223c9b2c80753e1b458e8064", @@ -148,8 +167,11 @@ artifacts = { "sha256": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b", }, "io_bazel_rules_scala_junit_junit": { - "artifact": "junit:junit:4.12", - "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", + "artifact": "junit:junit:4.13.2", + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "deps": [ + "@io_bazel_rules_scala_org_hamcrest_hamcrest_core", + ], }, "io_bazel_rules_scala_mustache": { "artifact": "com.github.spullara.mustache.java:compiler:0.8.18", @@ -281,6 +303,13 @@ artifacts = { "@io_bazel_rules_scala_scala_library", ], }, + "io_bazel_rules_scala_scala_tasty_inspector": { + "artifact": "org.scala-lang:scala3-tasty-inspector_3:3.6.2", + "sha256": "0075e4bb5289facf54004060ea0f3aa60f60a03ec9dcb83c2fc01b06ffced6e7", + "deps": [ + "@io_bazel_rules_scala_scala_compiler", + ], + }, "io_bazel_rules_scala_scala_xml": { "artifact": "org.scala-lang.modules:scala-xml_3:2.1.0", "sha256": "48f22343575f4b1d6550eecc42d4b7f0a0d30223c72f015d8d893feab4cbeecd", @@ -505,6 +534,13 @@ artifacts = { "@org_jline_jline_terminal", ], }, + "org_portable_scala_portable_scala_reflect": { + "artifact": "org.portable-scala:portable-scala-reflect_2.13:1.1.1", + "sha256": "11f2f59d0c228912811095025b36ce58a025a8397851d773295c8ad7862d8488", + "deps": [ + "@io_bazel_rules_scala_scala_library_2", + ], + }, "org_scala_lang_modules_scala_collection_compat": { "artifact": "org.scala-lang.modules:scala-collection-compat_2.13:2.12.0", "sha256": "befff482233cd7f9a7ca1e1f5a36ede421c018e6ce82358978c475d45532755f", @@ -526,6 +562,10 @@ artifacts = { "@org_scala_sbt_util_interface", ], }, + "org_scala_sbt_test_interface": { + "artifact": "org.scala-sbt:test-interface:1.0", + "sha256": "15f70b38bb95f3002fec9aea54030f19bb4ecfbad64c67424b5e5fea09cd749e", + }, "org_scala_sbt_util_interface": { "artifact": "org.scala-sbt:util-interface:1.10.5", "sha256": "a2f3eb27a3312acda18dc57ea617d443048e816a9803bde723643254f0b66e5a",